From a4b34bb7756d1c36cd64e1389628b233e6d9c6f8 Mon Sep 17 00:00:00 2001 From: Satvik Kumar Date: Wed, 3 Sep 2014 10:50:51 +1000 Subject: [PATCH] Operations for merging and splitting lists --- webodf/lib/manifest.json | 9 ++ webodf/lib/ops/OpMergeList.js | 110 +++++++++++++++++++++++ webodf/lib/ops/OpSplitList.js | 132 ++++++++++++++++++++++++++++ webodf/lib/ops/OperationFactory.js | 4 +- webodf/tests/ops/operationtests.xml | 62 +++++++++++++ webodf/tools/karma.conf.js | 2 + 6 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 webodf/lib/ops/OpMergeList.js create mode 100644 webodf/lib/ops/OpSplitList.js diff --git a/webodf/lib/manifest.json b/webodf/lib/manifest.json index 3ee251228..7a5f44070 100644 --- a/webodf/lib/manifest.json +++ b/webodf/lib/manifest.json @@ -444,6 +444,9 @@ "ops.OpInsertText": [ "ops.OdtDocument" ], + "ops.OpMergeList": [ + "ops.OdtDocument" + ], "ops.OpMergeParagraph": [ "odf.CollapsingRules", "ops.OdtDocument" @@ -482,6 +485,10 @@ "ops.OpSetParagraphStyle": [ "ops.OdtDocument" ], + "ops.OpSplitList": [ + "odf.CollapsingRules", + "ops.OdtDocument" + ], "ops.OpSplitParagraph": [ "ops.OdtDocument" ], @@ -509,6 +516,7 @@ "ops.OpInsertImage", "ops.OpInsertTable", "ops.OpInsertText", + "ops.OpMergeList", "ops.OpMergeParagraph", "ops.OpMoveCursor", "ops.OpRemoveAnnotation", @@ -521,6 +529,7 @@ "ops.OpRemoveText", "ops.OpSetBlob", "ops.OpSetParagraphStyle", + "ops.OpSplitList", "ops.OpSplitParagraph", "ops.OpUpdateMember", "ops.OpUpdateMetadata", diff --git a/webodf/lib/ops/OpMergeList.js b/webodf/lib/ops/OpMergeList.js new file mode 100644 index 000000000..854b5f7e4 --- /dev/null +++ b/webodf/lib/ops/OpMergeList.js @@ -0,0 +1,110 @@ +/** + * Copyright (C) 2010-2014 KO GmbH + * + * @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 . + * @licend + * + * @source: http://www.webodf.org/ + * @source: https://github.com/kogmbh/WebODF/ + */ + +/*global ops, runtime, odf, core */ + +/** + * + * @constructor + * @implements ops.Operation + */ +ops.OpMergeList = function OpMergeList() { + "use strict"; + + var memberid, + timestamp, + /**@type{!number}*/ + sourceStartPosition, + /**@type{!number}*/ + destinationStartPosition, + odfUtils = odf.OdfUtils; + + /** + * @param {!ops.OpMergeList.InitSpec} data + */ + this.init = function (data) { + memberid = data.memberid; + timestamp = data.timestamp; + sourceStartPosition = data.sourceStartPosition; + destinationStartPosition = data.destinationStartPosition; + }; + + this.isEdit = true; + this.group = undefined; + + /** + * @return {!ops.OpMergeList.Spec} + */ + this.spec = function () { + return { + optype: "MergeList", + memberid: memberid, + timestamp: timestamp, + sourceStartPosition: sourceStartPosition, + destinationStartPosition: destinationStartPosition + }; + }; + + /** + * @param {!ops.Document} document + */ + this.execute = function (document) { + var odtDocument = /**@type{ops.OdtDocument}*/(document), + rootNode = odtDocument.getRootNode(), + sourceDomPosition = odtDocument.convertCursorStepToDomPoint(sourceStartPosition), + destinationDomPosition = odtDocument.convertCursorStepToDomPoint(destinationStartPosition), + sourceList, + destinationList, + childListItem; + + sourceList = odfUtils.getTopLevelListElement(sourceDomPosition.node, rootNode); + destinationList = odfUtils.getTopLevelListElement(destinationDomPosition.node, rootNode); + childListItem = sourceList.firstElementChild; + + while (childListItem) { + destinationList.appendChild(childListItem); + childListItem = sourceList.firstElementChild; + } + + sourceList.parentNode.removeChild(sourceList); + return true; + }; +}; + +/**@typedef{{ + optype: !string, + memberid: !string, + timestamp: !number, + sourceStartPosition: !number, + destinationStartPosition: !number +}}*/ +ops.OpMergeList.Spec; + +/**@typedef{{ + memberid: !string, + timestamp:(!number|undefined), + sourceStartPosition: !number, + destinationStartPosition: !number +}}*/ +ops.OpMergeList.InitSpec; \ No newline at end of file diff --git a/webodf/lib/ops/OpSplitList.js b/webodf/lib/ops/OpSplitList.js new file mode 100644 index 000000000..6818717e3 --- /dev/null +++ b/webodf/lib/ops/OpSplitList.js @@ -0,0 +1,132 @@ +/** + * Copyright (C) 2010-2014 KO GmbH + * + * @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 . + * @licend + * + * @source: http://www.webodf.org/ + * @source: https://github.com/kogmbh/WebODF/ + */ + +/*global ops, runtime, odf, core, Range*/ + +/** + * + * @constructor + * @implements ops.Operation + */ +ops.OpSplitList = function OpSplitList() { + "use strict"; + + var memberid, + timestamp, + /**@type{!number}*/ + sourceStartPosition, + /**@type{!number}*/ + splitPosition, + odfUtils = odf.OdfUtils, + /**@const*/ + xmlns = odf.Namespaces.xmlns; + + /** + * @param {!ops.OpSplitList.InitSpec} data + */ + this.init = function (data) { + memberid = data.memberid; + timestamp = data.timestamp; + sourceStartPosition = data.sourceStartPosition; + splitPosition = data.splitPosition; + }; + + this.isEdit = true; + this.group = undefined; + + /** + * @return {!ops.OpSplitList.Spec} + */ + this.spec = function () { + return { + optype: "SplitList", + memberid: memberid, + timestamp: timestamp, + sourceStartPosition: sourceStartPosition, + splitPosition: splitPosition + }; + }; + + /** + * @param {!ops.Document} document + */ + this.execute = function (document) { + var odtDocument = /**@type{ops.OdtDocument}*/(document), + ownerDocument = odtDocument.getDOMDocument(), + rootNode = odtDocument.getRootNode(), + collapseRules = new odf.CollapsingRules(rootNode), + sourceDomPosition = odtDocument.convertCursorStepToDomPoint(sourceStartPosition), + splitDomPosition = odtDocument.convertCursorStepToDomPoint(splitPosition), + sourceParagraph = /**@type{!Element}*/(odfUtils.getParagraphElement(sourceDomPosition.node, sourceDomPosition.offset)), + splitParagraph = /**@type{!Element}*/(odfUtils.getParagraphElement(splitDomPosition.node, splitDomPosition.offset)), + sourceList = odfUtils.getTopLevelListElement(sourceParagraph, rootNode), + destinationList, + splitPositionParentList, + range = ownerDocument.createRange(), + fragment; + + if (!sourceList || !splitParagraph) { + return false; + } + + destinationList = sourceList.cloneNode(false); + destinationList.removeAttributeNS(xmlns, "id"); + + // create a range starting at before the list item element we split at and + // ending at just after the last list item in the list + range.setStartBefore(splitParagraph.parentNode); + range.setEndAfter(sourceList.lastElementChild); + + // extract the range to get all list items from the split position to the end of the list + // then collapse any empty nodes at the parent text:list element of the paragraph at the split position + splitPositionParentList = /**@type{!Node}*/(splitParagraph.parentNode.parentNode); + fragment = range.extractContents(); + + // don't collapse nodes if its the top level list + if (splitPositionParentList !== sourceList) { + collapseRules.mergeChildrenIntoParent(splitPositionParentList); + } + + destinationList.appendChild(fragment); + sourceList.parentNode.insertBefore(destinationList, sourceList.nextElementSibling); + return true; + }; +}; + +/**@typedef{{ + optype: !string, + memberid: !string, + timestamp: !number, + sourceStartPosition: !number, + splitPosition: !number +}}*/ +ops.OpSplitList.Spec; + +/**@typedef{{ + memberid: !string, + timestamp:(number|undefined), + sourceStartPosition: !number, + splitPosition: !number +}}*/ +ops.OpSplitList.InitSpec; \ No newline at end of file diff --git a/webodf/lib/ops/OperationFactory.js b/webodf/lib/ops/OperationFactory.js index af2911de9..4fc31577e 100644 --- a/webodf/lib/ops/OperationFactory.js +++ b/webodf/lib/ops/OperationFactory.js @@ -103,7 +103,9 @@ ops.OperationFactory = function OperationFactory() { RemoveHyperlink: construct(ops.OpRemoveHyperlink), AddList: construct(ops.OpAddList), RemoveList: construct(ops.OpRemoveList), - AddListStyle: construct(ops.OpAddListStyle) + AddListStyle: construct(ops.OpAddListStyle), + MergeList: construct(ops.OpMergeList), + SplitList: construct(ops.OpSplitList) }; } diff --git a/webodf/tests/ops/operationtests.xml b/webodf/tests/ops/operationtests.xml index 12efbd01f..628b3e846 100644 --- a/webodf/tests/ops/operationtests.xml +++ b/webodf/tests/ops/operationtests.xml @@ -2205,4 +2205,66 @@ Sample Text + + + + + + SampleText1 + + + + + SampleText2 + + + + + + + + + + + + SampleText1 + + + SampleText2 + + + + + + + + + + + SampleText1 + + + SampleText2 + + + + + + + + + + + + SampleText1 + + + + + SampleText2 + + + + + diff --git a/webodf/tools/karma.conf.js b/webodf/tools/karma.conf.js index 578654ab7..fec9d9f23 100644 --- a/webodf/tools/karma.conf.js +++ b/webodf/tools/karma.conf.js @@ -93,6 +93,7 @@ module.exports = function (config) { 'lib/ops/OpInsertImage.js', 'lib/ops/OpInsertTable.js', 'lib/ops/OpInsertText.js', + 'lib/ops/OpMergeList.js', 'lib/odf/CollapsingRules.js', 'lib/ops/OpMergeParagraph.js', 'lib/ops/OpMoveCursor.js', @@ -106,6 +107,7 @@ module.exports = function (config) { 'lib/ops/OpRemoveText.js', 'lib/ops/OpSetBlob.js', 'lib/ops/OpSetParagraphStyle.js', + 'lib/ops/OpSplitList.js', 'lib/ops/OpSplitParagraph.js', 'lib/ops/OpUpdateMember.js', 'lib/ops/OpUpdateMetadata.js',