node-webodf
Version:
WebODF - JavaScript Document Engine http://webodf.org/
212 lines (185 loc) • 7.75 kB
JavaScript
/**
* Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
*
* @licstart
* This file is part of WebODF.
*
* WebODF is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License (GNU AGPL)
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* WebODF is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
* @licend
*
* @source: http://www.webodf.org/
* @source: https://github.com/kogmbh/WebODF/
*/
/*global ops, core, odf, runtime*/
/**
* @constructor
* @implements ops.Operation
*/
ops.OpAddAnnotation = function OpAddAnnotation() {
"use strict";
var memberid, timestamp,
/**@type{number}*/
position,
/**@type{!number|undefined}*/
length,
/**@type{string}*/
name,
/**@type{!Document}*/
doc;
/**
* @param {!ops.OpAddAnnotation.InitSpec} data
*/
this.init = function (data) {
memberid = data.memberid;
timestamp = parseInt(data.timestamp, 10);
position = parseInt(data.position, 10);
length = (data.length !== undefined) ? (parseInt(data.length, 10) || 0) : undefined;
name = data.name;
};
this.isEdit = true;
this.group = undefined;
/**
* Creates an office:annotation node with a dc:creator, dc:date, and a paragraph wrapped within
* a list, inside it; and with the given annotation name
* @param {!ops.OdtDocument} odtDocument
* @param {!Date} date
* @return {!odf.AnnotationElement}
*/
function createAnnotationNode(odtDocument, date) {
var annotationNode, creatorNode, dateNode,
listNode, listItemNode, paragraphNode;
// Create an office:annotation node with the calculated name, and an attribute with the memberid
// for SessionView styling
annotationNode = /**@type{!odf.AnnotationElement}*/(doc.createElementNS(odf.Namespaces.officens, 'office:annotation'));
annotationNode.setAttributeNS(odf.Namespaces.officens, 'office:name', name);
creatorNode = doc.createElementNS(odf.Namespaces.dcns, 'dc:creator');
creatorNode.setAttributeNS('urn:webodf:names:editinfo', 'editinfo:memberid', memberid);
creatorNode.textContent = odtDocument.getMember(memberid).getProperties().fullName;
// Date.toISOString return the current Dublin Core representation
dateNode = doc.createElementNS(odf.Namespaces.dcns, 'dc:date');
dateNode.appendChild(doc.createTextNode(date.toISOString()));
// Add a text:list > text:list-item > text:p hierarchy as a child of the annotation node
listNode = doc.createElementNS(odf.Namespaces.textns, 'text:list');
listItemNode = doc.createElementNS(odf.Namespaces.textns, 'text:list-item');
paragraphNode = doc.createElementNS(odf.Namespaces.textns, 'text:p');
listItemNode.appendChild(paragraphNode);
listNode.appendChild(listItemNode);
annotationNode.appendChild(creatorNode);
annotationNode.appendChild(dateNode);
annotationNode.appendChild(listNode);
return annotationNode;
}
/**
* Creates an office:annotation-end node with the given annotation name
* @return {!Element}
*/
function createAnnotationEnd() {
var annotationEnd;
// Create an office:annotation-end node with the calculated name
annotationEnd = doc.createElementNS(odf.Namespaces.officens, 'office:annotation-end');
annotationEnd.setAttributeNS(odf.Namespaces.officens, 'office:name', name);
return annotationEnd;
}
/**
* Inserts the element at a given position
* @param {!ops.OdtDocument} odtDocument
* @param {!Element} node
* @param {!number} insertPosition
* @return {undefined}
*/
function insertNodeAtPosition(odtDocument, node, insertPosition) {
var previousNode,
parentNode,
domPosition = odtDocument.getTextNodeAtStep(insertPosition, memberid);
if (domPosition) {
previousNode = domPosition.textNode;
parentNode = previousNode.parentNode;
if (domPosition.offset !== previousNode.length) {
previousNode.splitText(domPosition.offset);
}
parentNode.insertBefore(node, previousNode.nextSibling);
// clean up any empty text node which was created by odtDocument.getTextNodeAtStep or previousNode.splitText
if (previousNode.length === 0) {
parentNode.removeChild(previousNode);
}
}
}
/**
* @param {!ops.Document} document
*/
this.execute = function (document) {
var odtDocument = /**@type{ops.OdtDocument}*/(document),
annotation, annotationEnd,
cursor = odtDocument.getCursor(memberid),
selectedRange,
paragraphElement;
doc = odtDocument.getDOMDocument();
annotation = createAnnotationNode(odtDocument, new Date(timestamp));
if (length !== undefined) {
annotationEnd = createAnnotationEnd();
// link annotation end to start
annotation.annotationEndElement = annotationEnd;
// Insert the end node before inserting the annotation node, so we don't
// affect the addressing, and length is always positive
insertNodeAtPosition(odtDocument, annotationEnd, position + length);
}
insertNodeAtPosition(odtDocument, annotation, position);
odtDocument.emit(ops.OdtDocument.signalStepsInserted, {position: position});
// Move the cursor inside the new annotation,
// by selecting the paragraph's range.
if (cursor) {
selectedRange = doc.createRange();
paragraphElement = /**@type{!Element}*/(annotation.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0]);
selectedRange.selectNodeContents(paragraphElement);
cursor.setSelectedRange(selectedRange, false);
cursor.setSelectionType(ops.OdtCursor.RangeSelection);
odtDocument.emit(ops.Document.signalCursorMoved, cursor);
}
// Track this annotation
odtDocument.getOdfCanvas().addAnnotation(annotation);
odtDocument.fixCursorPositions();
odtDocument.emit(ops.OdtDocument.signalAnnotationAdded, { memberId: memberid, annotation: annotation });
return true;
};
/**
* @return {!ops.OpAddAnnotation.Spec}
*/
this.spec = function () {
return {
optype: "AddAnnotation",
memberid: memberid,
timestamp: timestamp,
position: position,
length: length,
name: name
};
};
};
/**@typedef{{
optype:string,
memberid:string,
timestamp:number,
position:number,
length:(!number|undefined),
name:string
}}*/
ops.OpAddAnnotation.Spec;
/**@typedef{{
memberid:string,
timestamp:(number|undefined),
position:number,
length:(!number|undefined),
name:string
}}*/
ops.OpAddAnnotation.InitSpec;