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