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, odf*/ 26 /*jslint nomen: true, evil: true, bitwise: true */ 27 28 /** 29 * This operation splits the paragraph at the given 30 * position. If the `moveCursor` flag is specified 31 * and is set as true, the cursor is moved to the 32 * beginning of the next paragraph. Otherwise, it 33 * remains in it's original position. 34 * The paragraph style for the new paragraph is specified by the 35 * paragraphStyleName. If blank or empty, the new paragraph will 36 * have no specified style. 37 * 38 * @constructor 39 * @implements ops.Operation 40 */ 41 ops.OpSplitParagraph = function OpSplitParagraph() { 42 "use strict"; 43 44 var memberid, timestamp, 45 /**@type{number}*/ 46 sourceParagraphPosition, 47 /**@type{number}*/ 48 position, 49 /**@type{boolean}*/ 50 moveCursor, 51 /**@type{!string}*/ 52 paragraphStyleName, 53 /**@type{!odf.OdfUtils}*/ 54 odfUtils, 55 /**@const*/ 56 textns = odf.Namespaces.textns; 57 58 /** 59 * @param {!ops.OpSplitParagraph.InitSpec} data 60 */ 61 this.init = function (data) { 62 memberid = data.memberid; 63 timestamp = data.timestamp; 64 position = data.position; 65 sourceParagraphPosition = data.sourceParagraphPosition; 66 paragraphStyleName = data.paragraphStyleName; 67 moveCursor = data.moveCursor === 'true' || data.moveCursor === true; 68 odfUtils = new odf.OdfUtils(); 69 }; 70 71 this.isEdit = true; 72 this.group = undefined; 73 74 /** 75 * @param {!ops.Document} document 76 */ 77 this.execute = function (document) { 78 var odtDocument = /**@type{!ops.OdtDocument}*/(document), 79 domPosition, paragraphNode, targetNode, 80 node, splitNode, splitChildNode, keptChildNode, 81 cursor = odtDocument.getCursor(memberid); 82 83 odtDocument.upgradeWhitespacesAtPosition(position); 84 domPosition = odtDocument.getTextNodeAtStep(position); 85 if (!domPosition) { 86 return false; 87 } 88 89 paragraphNode = odtDocument.getParagraphElement(domPosition.textNode); 90 if (!paragraphNode) { 91 return false; 92 } 93 94 if (odfUtils.isListItem(paragraphNode.parentNode)) { 95 targetNode = paragraphNode.parentNode; 96 } else { 97 targetNode = paragraphNode; 98 } 99 100 // There can be a chain of multiple nodes between the text node 101 // where the split is done and the containing paragraph nodes, 102 // e.g. text:span nodes 103 // So all nodes in this chain need to be split up, i.e. they need 104 // to be cloned, and then the clone and any next siblings have to 105 // be moved to the new paragraph node, which is also cloned from 106 // the current one. 107 108 // start with text node the cursor is in, needs special treatment 109 // if text node is split at the beginning, do not split but simply 110 // move the whole text node 111 if (domPosition.offset === 0) { 112 keptChildNode = domPosition.textNode.previousSibling; 113 splitChildNode = null; 114 } else { 115 keptChildNode = domPosition.textNode; 116 // if text node is to be split at the end, don't split at all 117 if (domPosition.offset >= domPosition.textNode.length) { 118 splitChildNode = null; 119 } else { 120 // splitText always returns {!Text} here 121 splitChildNode = /**@type{!Text}*/( 122 domPosition.textNode.splitText(domPosition.offset) 123 ); 124 } 125 } 126 127 // then handle all nodes until (incl.) the paragraph node: 128 // create a clone and add as childs the split node of the node below 129 // and any next siblings of it 130 node = domPosition.textNode; 131 while (node !== targetNode) { 132 node = node.parentNode; 133 134 // split off the node copy 135 // TODO: handle unique attributes, e.g. xml:id 136 splitNode = node.cloneNode(false); 137 // add the split child node 138 if (splitChildNode) { 139 splitNode.appendChild(splitChildNode); 140 } 141 if (keptChildNode) { 142 // Move all child nodes that should appear after the split to the new node 143 while (keptChildNode && keptChildNode.nextSibling) { 144 splitNode.appendChild(keptChildNode.nextSibling); 145 } 146 } else { 147 // All children of the original node should be moved after the split 148 while (node.firstChild) { 149 splitNode.appendChild(node.firstChild); 150 } 151 } 152 node.parentNode.insertBefore(splitNode, node.nextSibling); 153 154 // prepare next level 155 keptChildNode = node; 156 splitChildNode = splitNode; 157 } 158 159 if (odfUtils.isListItem(splitChildNode)) { 160 splitChildNode = splitChildNode.childNodes.item(0); 161 } 162 163 if (paragraphStyleName) { 164 /**@type{!Element}*/(splitChildNode).setAttributeNS(textns, "text:style-name", paragraphStyleName); 165 } else { 166 /**@type{!Element}*/(splitChildNode).removeAttributeNS(textns, "style-name"); 167 } 168 169 // clean up any empty text node which was created by odtDocument.getTextNodeAtStep 170 if (domPosition.textNode.length === 0) { 171 domPosition.textNode.parentNode.removeChild(domPosition.textNode); 172 } 173 odtDocument.emit(ops.OdtDocument.signalStepsInserted, {position: position}); 174 175 if (cursor && moveCursor) { 176 odtDocument.moveCursor(memberid, position + 1, 0); 177 odtDocument.emit(ops.Document.signalCursorMoved, cursor); 178 } 179 180 odtDocument.fixCursorPositions(); 181 odtDocument.getOdfCanvas().refreshSize(); 182 // mark both paragraphs as edited 183 odtDocument.emit(ops.OdtDocument.signalParagraphChanged, { 184 paragraphElement: paragraphNode, 185 memberId: memberid, 186 timeStamp: timestamp 187 }); 188 odtDocument.emit(ops.OdtDocument.signalParagraphChanged, { 189 paragraphElement: splitChildNode, 190 memberId: memberid, 191 timeStamp: timestamp 192 }); 193 194 odtDocument.getOdfCanvas().rerenderAnnotations(); 195 return true; 196 }; 197 198 /** 199 * @return {!ops.OpSplitParagraph.Spec} 200 */ 201 this.spec = function () { 202 return { 203 optype: "SplitParagraph", 204 memberid: memberid, 205 timestamp: timestamp, 206 position: position, 207 sourceParagraphPosition: sourceParagraphPosition, 208 paragraphStyleName: paragraphStyleName, 209 moveCursor: moveCursor 210 }; 211 }; 212 }; 213 /**@typedef{{ 214 optype:string, 215 memberid:string, 216 timestamp:number, 217 position:number, 218 sourceParagraphPosition:number, 219 paragraphStyleName:string, 220 moveCursor:boolean 221 }}*/ 222 ops.OpSplitParagraph.Spec; 223 /**@typedef{{ 224 memberid:string, 225 timestamp:(number|undefined), 226 position:number, 227 sourceParagraphPosition:number, 228 paragraphStyleName:string, 229 moveCursor:(string|boolean|undefined) 230 }}*/ 231 ops.OpSplitParagraph.InitSpec; 232