1 /**
  2  * Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
  3  *
  4  * @licstart
  5  * This file is part of WebODF.
  6  *
  7  * WebODF is free software: you can redistribute it and/or modify it
  8  * under the terms of the GNU Affero General Public License (GNU AGPL)
  9  * as published by the Free Software Foundation, either version 3 of
 10  * the License, or (at your option) any later version.
 11  *
 12  * WebODF is distributed in the hope that it will be useful, but
 13  * WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU Affero General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU Affero General Public License
 18  * along with WebODF.  If not, see <http://www.gnu.org/licenses/>.
 19  * @licend
 20  *
 21  * @source: http://www.webodf.org/
 22  * @source: https://github.com/kogmbh/WebODF/
 23  */
 24 
 25 /*global ops, core, odf, runtime*/
 26 
 27 /**
 28  * @constructor
 29  * @implements ops.Operation
 30  */
 31 ops.OpAddAnnotation = function OpAddAnnotation() {
 32     "use strict";
 33 
 34     var memberid, timestamp,
 35         /**@type{number}*/
 36         position,
 37         /**@type{!number|undefined}*/
 38         length,
 39         /**@type{string}*/
 40         name,
 41         /**@type{!Document}*/
 42         doc;
 43 
 44     /**
 45      * @param {!ops.OpAddAnnotation.InitSpec} data
 46      */
 47     this.init = function (data) {
 48         memberid = data.memberid;
 49         timestamp = parseInt(data.timestamp, 10);
 50         position = parseInt(data.position, 10);
 51         length = (data.length !== undefined) ? (parseInt(data.length, 10) || 0) : undefined;
 52         name = data.name;
 53     };
 54 
 55     this.isEdit = true;
 56     this.group = undefined;
 57 
 58     /**
 59      * Creates an office:annotation node with a dc:creator, dc:date, and a paragraph wrapped within
 60      * a list, inside it; and with the given annotation name
 61      * @param {!ops.OdtDocument} odtDocument
 62      * @param {!Date} date
 63      * @return {!odf.AnnotationElement}
 64      */
 65     function createAnnotationNode(odtDocument, date) {
 66         var annotationNode, creatorNode, dateNode,
 67             listNode, listItemNode, paragraphNode;
 68 
 69         // Create an office:annotation node with the calculated name, and an attribute with the memberid
 70         // for SessionView styling
 71         annotationNode = /**@type{!odf.AnnotationElement}*/(doc.createElementNS(odf.Namespaces.officens, 'office:annotation'));
 72         annotationNode.setAttributeNS(odf.Namespaces.officens, 'office:name', name);
 73 
 74         creatorNode = doc.createElementNS(odf.Namespaces.dcns, 'dc:creator');
 75         creatorNode.setAttributeNS('urn:webodf:names:editinfo', 'editinfo:memberid', memberid);
 76         creatorNode.textContent = odtDocument.getMember(memberid).getProperties().fullName;
 77 
 78         // Date.toISOString return the current Dublin Core representation
 79         dateNode = doc.createElementNS(odf.Namespaces.dcns, 'dc:date');
 80         dateNode.appendChild(doc.createTextNode(date.toISOString()));
 81 
 82         // Add a text:list > text:list-item > text:p hierarchy as a child of the annotation node
 83         listNode = doc.createElementNS(odf.Namespaces.textns, 'text:list');
 84         listItemNode = doc.createElementNS(odf.Namespaces.textns, 'text:list-item');
 85         paragraphNode = doc.createElementNS(odf.Namespaces.textns, 'text:p');
 86         listItemNode.appendChild(paragraphNode);
 87         listNode.appendChild(listItemNode);
 88 
 89         annotationNode.appendChild(creatorNode);
 90         annotationNode.appendChild(dateNode);
 91         annotationNode.appendChild(listNode);
 92 
 93         return annotationNode;
 94     }
 95 
 96     /**
 97      * Creates an office:annotation-end node with the given annotation name
 98      * @return {!Element}
 99      */
100     function createAnnotationEnd() {
101         var annotationEnd;
102 
103         // Create an office:annotation-end node with the calculated name
104         annotationEnd = doc.createElementNS(odf.Namespaces.officens, 'office:annotation-end');
105         annotationEnd.setAttributeNS(odf.Namespaces.officens, 'office:name', name);
106 
107         return annotationEnd;
108     }
109 
110     /**
111      * Inserts the element at a given position
112      * @param {!ops.OdtDocument} odtDocument
113      * @param {!Element} node
114      * @param {!number} insertPosition
115      * @return {undefined}
116      */
117     function insertNodeAtPosition(odtDocument, node, insertPosition) {
118         var previousNode,
119             parentNode,
120             domPosition = odtDocument.getTextNodeAtStep(insertPosition, memberid);
121 
122         if (domPosition) {
123             previousNode = domPosition.textNode;
124             parentNode = previousNode.parentNode;
125 
126             if (domPosition.offset !== previousNode.length) {
127                 previousNode.splitText(domPosition.offset);
128             }
129 
130             parentNode.insertBefore(node, previousNode.nextSibling);
131             // clean up any empty text node which was created by odtDocument.getTextNodeAtStep or previousNode.splitText
132             if (previousNode.length === 0) {
133                 parentNode.removeChild(previousNode);
134             }
135         }
136     }
137 
138     /**
139      * @param {!ops.Document} document
140      */
141     this.execute = function (document) {
142         var odtDocument = /**@type{ops.OdtDocument}*/(document),
143             annotation, annotationEnd,
144             cursor = odtDocument.getCursor(memberid),
145             selectedRange,
146             paragraphElement;
147 
148         doc = odtDocument.getDOMDocument();
149 
150         annotation = createAnnotationNode(odtDocument, new Date(timestamp));
151 
152         if (length !== undefined) {
153             annotationEnd = createAnnotationEnd();
154             // link annotation end to start
155             annotation.annotationEndElement = annotationEnd;
156             // Insert the end node before inserting the annotation node, so we don't
157             // affect the addressing, and length is always positive
158             insertNodeAtPosition(odtDocument, annotationEnd, position + length);
159         }
160         insertNodeAtPosition(odtDocument, annotation, position);
161         odtDocument.emit(ops.OdtDocument.signalStepsInserted, {position: position});
162 
163         // Move the cursor inside the new annotation,
164         // by selecting the paragraph's range.
165         if (cursor) {
166             selectedRange = doc.createRange();
167             paragraphElement = /**@type{!Element}*/(annotation.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]);
168             selectedRange.selectNodeContents(paragraphElement);
169             cursor.setSelectedRange(selectedRange, false);
170             cursor.setSelectionType(ops.OdtCursor.RangeSelection);
171             odtDocument.emit(ops.Document.signalCursorMoved, cursor);
172         }
173         // Track this annotation
174         odtDocument.getOdfCanvas().addAnnotation(annotation);
175         odtDocument.fixCursorPositions();
176         odtDocument.emit(ops.OdtDocument.signalAnnotationAdded, { memberId: memberid, annotation: annotation });
177 
178         return true;
179     };
180 
181     /**
182      * @return {!ops.OpAddAnnotation.Spec}
183      */
184     this.spec = function () {
185         return {
186             optype: "AddAnnotation",
187             memberid: memberid,
188             timestamp: timestamp,
189             position: position,
190             length: length,
191             name: name
192         };
193     };
194 };
195 /**@typedef{{
196     optype:string,
197     memberid:string,
198     timestamp:number,
199     position:number,
200     length:(!number|undefined),
201     name:string
202 }}*/
203 ops.OpAddAnnotation.Spec;
204 /**@typedef{{
205     memberid:string,
206     timestamp:(number|undefined),
207     position:number,
208     length:(!number|undefined),
209     name:string
210 }}*/
211 ops.OpAddAnnotation.InitSpec;
212