From 0bd5926e9f6c5057d80e829ca5af83b19a58ecfc Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Mon, 25 Nov 2013 17:07:13 +0100 Subject: [PATCH 01/16] undoredo wip --- src/smartxml/smartxml.js | 151 ++++++++++++++++++++++++++++++++++ src/smartxml/smartxml.test.js | 25 ++++++ 2 files changed, 176 insertions(+) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 021a116..fa5f551 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -25,6 +25,14 @@ var INSERTION = function(implementation) { return toret; }; +// var TRANSFORMATION = function(name, implementation) { +// //implementation._isTransformation = true; + +// createDumbTransformation(name, implementation, ) + +// return implementation; +// }; + var DocumentNode = function(nativeNode, document) { if(!document) { throw new Error('undefined document for a node'); @@ -446,6 +454,8 @@ var parseXML = function(xml) { var Document = function(xml) { this.loadXML(xml); + this.undoStack = []; + this.redoStack = []; }; $.extend(Document.prototype, Backbone.Events, { @@ -620,6 +630,40 @@ $.extend(Document.prototype, Backbone.Events, { defineDocumentProperties(this, insertion.ofNode._$); insertion.ofNode.triggerChangeEvent('nodeAdded'); return insertion.ofNode; + }, + + transform: function(transformationName, args) { + var Transformation = transformations[transformationName], + transformation; + if(Transformation) { + transformation = new Transformation(args); + transformation.run(); + this.undoStack.push(transformation); + } else { + throw new Error('Transformation ' + transformationName + ' doesn\'t exist!'); + } + }, + undo: function() { + var transformation = this.undoStack.pop(); + if(transformation) { + transformation.undo(); + this.redoStack.push(transformation); + } + }, + redo: function() { + var transformation = this.redoStack.pop(); + if(transformation) { + transformation.run(); + this.undoStack.push(transformation); + } + }, + + getNodeByPath: function(path) { + var toret = this.root; + path.forEach(function(idx) { + toret = toret.contents()[idx]; + }); + return toret; } }); @@ -632,6 +676,113 @@ var defineDocumentProperties = function(doc, $document) { }, configurable: true}); }; + +// var registerTransformationsFromObject = function(object) { +// _.values(object).filter(function(val) { +// return typeof val === 'function' && val._isTransformation; +// }) +// .forEach(function(val) { +// registerTransformation(val._transformationName, val, object); +// }); +// }; +// registerTransformationsFromObject(ElementNode.prototype); +// registerTransformationsFromObject(TextNode.prototype); +// registerTransformationsFromObject(Document.prototype); + +// var Transformation = function() { +// }; +// $.extend(Transformation.prototype, { + +// }); + + +// var createDumbTransformation = function(impl, contextObject) { +// var DumbTransformation = function(args) { +// this.args = this.args; +// }; +// DumbTransformation.prototype = Object.create(Transformation.prototype); +// $.extend(DumbTransformation.prototype, { +// run: function() { +// impl.apply(contextObject, this.args); +// } +// }); + +// return DumbTransformation; + + +// }; + +var transformations = {}; +// var registerTransformation = function(name, impl, contextObject) { +// if(typeof impl === 'function') { +// transformations[name] = createDumbTransformation(impl, contextObject); +// } +// }; + +// registerTransformation('detachx', DocumentNode.prototype.detach, ) + + +// 1. detach via totalny fallback +var DetachNodeTransformation = function(args) { + this.node = args.node; + this.document = this.node.document; +}; +$.extend(DetachNodeTransformation.prototype, { + run: function() { + this.oldRoot = this.node.document.root.clone(); + this.path = this.node.getPath(); + this.node.detach(); // @TS + + }, + undo: function() { + this.document.root.replaceWith(this.oldRoot); // this.getDocument? + this.node = this.document.getNodeByPath(this.path); + } +}); +transformations['detach'] = DetachNodeTransformation; + +//2. detach via wskazanie changeroot + +var Detach2NodeTransformation = function(args) { + this.node = args.node; + this.document = this.node.document; +}; +$.extend(Detach2NodeTransformation.prototype, { + run: function() { + this.root = this.node.parent() ? this.node.parent() : this.node.document.root; + this.oldRoot = (this.root).clone(); + this.path = this.node.getPath(); + this.node.detach(); + + }, + undo: function() { + this.root.replaceWith(this.oldRoot); // this.getDocument? + this.node = this.document.getNodeByPath(this.path); + } +}); +transformations['detach2'] = Detach2NodeTransformation; + +//3. detach z pełnym własnym redo + +var Detach3NodeTransformation = function(args) { + this.node = args.node; + this.document = this.node.document; +}; +$.extend(Detach3NodeTransformation.prototype, { + run: function() { + this.index = this.node.getIndex(); + this.parent = this.node.parent(); + this.node.detach(); + }, + undo: function() { + var contents = this.parent.contents(); + if(contents.length === 0) { + this.parent.append(this.node) + } + } +}); +transformations['detach3'] = Detach3NodeTransformation; + return { documentFromXML: function(xml) { return new Document(xml); diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 0b85383..64e633f 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -814,6 +814,31 @@ describe('smartxml', function() { }); }); + describe('Undo/redo', function() { + it('does work', function() { + var doc = getDocumentFromXML('
Alice
'), + span = doc.root.contents()[0]; + + doc.transform('detach3', {node: span}); + + + doc.undo(); + + expect(doc.root.contents()).to.have.length(1); + expect(doc.root.contents()[0].getTagName()).to.equal('span'); + expect(doc.root.contents()[0].contents()[0].getText()).to.equal('Alice'); + + doc.redo(); + expect(doc.root.contents()).to.have.length(0); + + doc.undo(); + expect(doc.root.contents()).to.have.length(1); + expect(doc.root.contents()[0].getTagName()).to.equal('span'); + expect(doc.root.contents()[0].contents()[0].getText()).to.equal('Alice'); + + }); + }); + }); }); \ No newline at end of file -- 2.20.1 From e1f340cca43eafb33256f439cac74689d783f292 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 27 Nov 2013 10:55:22 +0100 Subject: [PATCH 02/16] wip: detach2 ok --- .../modules/documentCanvas/canvas/canvas.js | 2 + .../modules/documentCanvas/canvas/utils.js | 2 +- src/editor/modules/documentCanvas/commands.js | 22 ++++++++ .../modules/documentToolbar/template.html | 6 ++ src/smartxml/smartxml.js | 56 ++++++++++++++----- src/smartxml/smartxml.test.js | 35 +++++++++++- 6 files changed, 107 insertions(+), 16 deletions(-) diff --git a/src/editor/modules/documentCanvas/canvas/canvas.js b/src/editor/modules/documentCanvas/canvas/canvas.js index cf63c47..05dfbad 100644 --- a/src/editor/modules/documentCanvas/canvas/canvas.js +++ b/src/editor/modules/documentCanvas/canvas/canvas.js @@ -33,6 +33,8 @@ $.extend(Canvas.prototype, { reloadRoot: function() { var canvasDOM = this.generateCanvasDOM(this.wlxmlDocument.root); + //var canvasDOM = this.wlxmlDocument.root.getData('canvasElement') ? this.wlxmlDocument.root.getData('canvasElement').dom() : this.generateCanvasDOM(this.wlxmlDocument.root); + this.wrapper.empty(); this.wrapper.append(canvasDOM); this.d = this.wrapper.children(0); diff --git a/src/editor/modules/documentCanvas/canvas/utils.js b/src/editor/modules/documentCanvas/canvas/utils.js index caa9af4..e1f0a55 100644 --- a/src/editor/modules/documentCanvas/canvas/utils.js +++ b/src/editor/modules/documentCanvas/canvas/utils.js @@ -45,7 +45,7 @@ var findCanvasElementInParent = function(wlxmlChildNode, wlxmlParentNode) { } else { parentElement = findCanvasElement(wlxmlParentNode); parentElement.children().forEach(function(child) { - if(child.data('wlxmlNode').sameNode(wlxmlChildNode)) { + if(child.data('wlxmlNode').sameNode(wlxmlChildNode)) { // czemu tu, przy drugim undo child nie mial data? toret = child; } }); diff --git a/src/editor/modules/documentCanvas/commands.js b/src/editor/modules/documentCanvas/commands.js index ac56a31..57a5130 100644 --- a/src/editor/modules/documentCanvas/commands.js +++ b/src/editor/modules/documentCanvas/commands.js @@ -19,6 +19,28 @@ var commands = { } }; +commands.register('undo', function(canvas) { + var doc = canvas.wlxmlDocument; + + doc.undo(); +}); + +commands.register('redo', function(canvas) { + var doc = canvas.wlxmlDocument; + + doc.redo(); +}); + +commands.register('remove-node', function(canvas) { + var cursor = canvas.getCursor(), + selectionStart = cursor.getSelectionStart(), + selectionEnd = cursor.getSelectionEnd(), + parent1 = selectionStart.element.parent() || undefined, + parent2 = selectionEnd.element.parent() || undefined; + + canvas.wlxmlDocument.transform('detach2', {node:canvas.getCurrentNodeElement().data('wlxmlNode')}); +}); + commands.register('unwrap-node', function(canvas) { var cursor = canvas.getCursor(), selectionStart = cursor.getSelectionStart(), diff --git a/src/editor/modules/documentToolbar/template.html b/src/editor/modules/documentToolbar/template.html index 7ba2bc5..0572bb5 100644 --- a/src/editor/modules/documentToolbar/template.html +++ b/src/editor/modules/documentToolbar/template.html @@ -26,6 +26,12 @@ + +
+ + + +
diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index fa5f551..4e8e575 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -49,7 +49,15 @@ $.extend(DocumentNode.prototype, { }, clone: function() { - return this.document.createDocumentNode(this._$.clone(true, true)[0]); + var clone = this._$.clone(true, true); + // clone.find('*').addBack().each(function() { + // var n = $(this); + // if(n.data('canvasElement')) { + // n.data('canvasElement', $.extend(true, {}, n.data('canvasElement'))); + // n.data('canvasElement').$element = n.data('canvasElement').$element.clone(true, true); + // } + // }); + return this.document.createDocumentNode(clone[0]); }, getPath: function(ancestor) { @@ -639,6 +647,7 @@ $.extend(Document.prototype, Backbone.Events, { transformation = new Transformation(args); transformation.run(); this.undoStack.push(transformation); + this.redoStack = []; } else { throw new Error('Transformation ' + transformationName + ' doesn\'t exist!'); } @@ -744,20 +753,20 @@ transformations['detach'] = DetachNodeTransformation; //2. detach via wskazanie changeroot var Detach2NodeTransformation = function(args) { - this.node = args.node; - this.document = this.node.document; + this.nodePath = args.node.getPath(); + this.document = args.node.document; }; $.extend(Detach2NodeTransformation.prototype, { run: function() { - this.root = this.node.parent() ? this.node.parent() : this.node.document.root; - this.oldRoot = (this.root).clone(); - this.path = this.node.getPath(); - this.node.detach(); + var node = this.document.getNodeByPath(this.nodePath), + root = node.parent() ? node.parent() : this.document.root; + this.rootPath = root.getPath(); + this.oldRoot = (root).clone(); + node.detach(); }, undo: function() { - this.root.replaceWith(this.oldRoot); // this.getDocument? - this.node = this.document.getNodeByPath(this.path); + this.document.getNodeByPath(this.rootPath).replaceWith(this.oldRoot); } }); transformations['detach2'] = Detach2NodeTransformation; @@ -770,14 +779,33 @@ var Detach3NodeTransformation = function(args) { }; $.extend(Detach3NodeTransformation.prototype, { run: function() { - this.index = this.node.getIndex(); - this.parent = this.node.parent(); + //this.index = this.node.getIndex(); + //this.parent = this.node.parent(); + + this.path = this.node.getPath(); + if(this.node.isSurroundedByTextElements()) { + this.prevText = this.node.prev().getText(); + this.nextText = this.node.next().getText(); + this.merge = true; + } else { + this.prevText = this.nextText = null; + this.merge = false; + } + this.node.detach(); }, undo: function() { - var contents = this.parent.contents(); - if(contents.length === 0) { - this.parent.append(this.node) + var parent = this.document.getNodeByPath(this.path.slice(0,-1)), + idx = _.last(this.path); + var inserted = parent.insertAtIndex(this.node, idx); + if(this.merge) { + if(inserted.next()) { + inserted.before({text: this.prevText}); + inserted.next().setText(this.nextText); + } else { + inserted.prev().setText(this.prevText); + inserted.after({text: this.nextText}); + } } } }); diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 64e633f..8e67d74 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -815,11 +815,12 @@ describe('smartxml', function() { }); describe('Undo/redo', function() { + it('does work', function() { var doc = getDocumentFromXML('
Alice
'), span = doc.root.contents()[0]; - doc.transform('detach3', {node: span}); + doc.transform('detach2', {node: span}); doc.undo(); @@ -837,6 +838,38 @@ describe('smartxml', function() { expect(doc.root.contents()[0].contents()[0].getText()).to.equal('Alice'); }); + it('does work - merged text nodes case', function() { + var doc = getDocumentFromXML('
Alice has a cat.
'), + span = doc.root.contents()[1]; + + doc.transform('detach2', {node: span}); + + + doc.undo(); + + expect(doc.root.contents().length).to.equal(3); + console.log(doc.toXML()); + expect(doc.root.contents()[1].contents()[0].getText()).to.equal('has'); + + }); + it('dbg - don not store nodes in tranformation state!', function() { + var doc = getDocumentFromXML('
'), + a = doc.root.contents()[0], + b = doc.root.contents()[1]; + + doc.transform('detach2', {node: a}); + doc.transform('detach2', {node: b}); + doc.undo(); + doc.undo(); + expect(doc.root.contents().length).to.equal(2); + expect(doc.root.contents()[0].getTagName()).to.equal('a'); + expect(doc.root.contents()[1].getTagName()).to.equal('b'); + + doc.redo(); + doc.redo(); + expect(doc.root.contents().length).to.equal(0); + + }); }); }); -- 2.20.1 From 0c978bca67983e93096a8e744b53e50861a9333d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 27 Nov 2013 12:45:15 +0100 Subject: [PATCH 03/16] dzialacy remove+text --- .../modules/documentCanvas/canvas/canvas.js | 44 +++++++++++++- .../documentCanvas/canvas/documentElement.js | 2 +- src/smartxml/smartxml.js | 58 ++++++++++++++++++- src/smartxml/smartxml.test.js | 2 +- 4 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/editor/modules/documentCanvas/canvas/canvas.js b/src/editor/modules/documentCanvas/canvas/canvas.js index 05dfbad..b6ddf71 100644 --- a/src/editor/modules/documentCanvas/canvas/canvas.js +++ b/src/editor/modules/documentCanvas/canvas/canvas.js @@ -10,12 +10,48 @@ define([ 'use strict'; +var TextHandler = function(canvas) {this.canvas = canvas; this.buffer = null}; +$.extend(TextHandler.prototype, { + handle: function(node, text) { + console.log('canvas text handler: ' + text); + this.setText(text, node); + return; + if(!this.node) { + this.node = node; + } + if(this.node.sameNode(node)) { + this._ping(text); + } else { + this.flush(); + this.node = node; + this._ping(text); + } + }, + _ping: _.throttle(function(text) { + this.buffer = text; + this.flush(); + }, 1000), + flush: function() { + if(this.buffer != null) { + this.setText(this.buffer, this.node); + this.buffer = null; + } + }, + setText: function(text, node) { + this.canvas.wlxmlDocument.transform('setText', {node:node, text: text}); + + } + +}); + + var Canvas = function(wlxmlDocument, publisher) { this.eventBus = _.extend({}, Backbone.Events); this.wrapper = $('
').addClass('canvas-wrapper').attr('contenteditable', true); this.wlxmlListener = wlxmlListener.create(this); this.loadWlxmlDocument(wlxmlDocument); this.publisher = publisher ? publisher : function() {}; + this.textHandler = new TextHandler(this); }; $.extend(Canvas.prototype, { @@ -56,6 +92,8 @@ $.extend(Canvas.prototype, { canvas.setCurrentElement(canvas.getDocumentElement(e.currentTarget), {caretTo: false}); }); + + var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if(documentElement.DocumentTextElement.isContentContainer(mutation.target)) { @@ -72,7 +110,11 @@ $.extend(Canvas.prototype, { var textElement = canvas.getDocumentElement(mutation.target), toSet = mutation.target.data !== utils.unicode.ZWS ? mutation.target.data : ''; - textElement.data('wlxmlNode').setText(toSet); + //textElement.data('wlxmlNode').setText(toSet); + //textElement.data('wlxmlNode').document.transform('setText', {node: textElement.data('wlxmlNode'), text: toSet}); + if(textElement.data('wlxmlNode').getText() != toSet) { + canvas.textHandler.handle(textElement.data('wlxmlNode'), toSet); + } } }); }); diff --git a/src/editor/modules/documentCanvas/canvas/documentElement.js b/src/editor/modules/documentCanvas/canvas/documentElement.js index 116ebf4..1c0719f 100644 --- a/src/editor/modules/documentCanvas/canvas/documentElement.js +++ b/src/editor/modules/documentCanvas/canvas/documentElement.js @@ -396,7 +396,7 @@ $.extend(DocumentTextElement.prototype, { if(params instanceof DocumentNodeElement) { element = params; } else { - element = DocumentNodeElement.create(params, this.canvas); + element = DocumentElement.create(params, this.canvas); } this.dom().wrap('
'); this.dom().parent().after(element.dom()); diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 4e8e575..7b59b18 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -383,6 +383,7 @@ $.extend(TextNode.prototype, { }, setText: function(text) { + console.log('smartxml: ' + text); this.nativeNode.data = text; this.triggerTextChangeEvent(); }, @@ -641,12 +642,14 @@ $.extend(Document.prototype, Backbone.Events, { }, transform: function(transformationName, args) { + console.log('transform'); var Transformation = transformations[transformationName], transformation; if(Transformation) { transformation = new Transformation(args); transformation.run(); this.undoStack.push(transformation); + console.log('clearing redo stack'); this.redoStack = []; } else { throw new Error('Transformation ' + transformationName + ' doesn\'t exist!'); @@ -769,7 +772,60 @@ $.extend(Detach2NodeTransformation.prototype, { this.document.getNodeByPath(this.rootPath).replaceWith(this.oldRoot); } }); -transformations['detach2'] = Detach2NodeTransformation; +//transformations['detach2'] = Detach2NodeTransformation; + +//2a. generyczna transformacja + +var createTransformation = function(desc) { + + var NodeTransformation = function(args) { + this.nodePath = args.node.getPath(); + this.document = args.node.document; + this.args = args; + }; + $.extend(NodeTransformation.prototype, { + run: function() { + var node = this.document.getNodeByPath(this.nodePath), + root; + + if(desc.getRoot) { + root = desc.getRoot(node); + } else { + root = this.document.root; + } + + this.rootPath = root.getPath(); + this.oldRoot = (root).clone(); + desc.impl.call(node, this.args); + }, + undo: function() { + this.document.getNodeByPath(this.rootPath).replaceWith(this.oldRoot); + } + }); + + return NodeTransformation; +} + +transformations['detach2'] = createTransformation({ + // impl: function() { + // //this.setAttr('class', 'cite'); // + // }, + impl: ElementNode.prototype.detach, + getRoot: function(node) { + return node.parent(); + } + +}); + +transformations['setText'] = createTransformation({ + impl: function(args) { + this.setText(args.text) + }, + getRoot: function(node) { + return node; + } + +}); //3. detach z pełnym własnym redo diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 8e67d74..d2ac4a9 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -848,7 +848,7 @@ describe('smartxml', function() { doc.undo(); expect(doc.root.contents().length).to.equal(3); - console.log(doc.toXML()); + //console.log(doc.toXML()); expect(doc.root.contents()[1].contents()[0].getText()).to.equal('has'); }); -- 2.20.1 From 6757e2f4fe80bfdc197d4963c464bf2b3a47f66b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 27 Nov 2013 16:10:11 +0100 Subject: [PATCH 04/16] pre d: setattr, settext, split as ContextTransformations; detach in old format; pre auto mapper --- .../modules/documentCanvas/canvas/canvas.js | 7 +- .../modules/documentCanvas/canvas/keyboard.js | 5 +- .../documentCanvas/canvas/wlxmlListener.js | 1 + .../documentToolbar/documentToolbar.js | 6 + src/editor/modules/nodePane/nodePane.js | 6 +- src/smartxml/smartxml.js | 159 +++++++++++++++++- 6 files changed, 172 insertions(+), 12 deletions(-) diff --git a/src/editor/modules/documentCanvas/canvas/canvas.js b/src/editor/modules/documentCanvas/canvas/canvas.js index b6ddf71..e181195 100644 --- a/src/editor/modules/documentCanvas/canvas/canvas.js +++ b/src/editor/modules/documentCanvas/canvas/canvas.js @@ -38,7 +38,8 @@ $.extend(TextHandler.prototype, { } }, setText: function(text, node) { - this.canvas.wlxmlDocument.transform('setText', {node:node, text: text}); + //this.canvas.wlxmlDocument.transform('setText', {node:node, text: text}); + node.transform('setText', {text: text}); } @@ -104,7 +105,7 @@ $.extend(Canvas.prototype, { mutation.target.data = mutation.target.data.replace(utils.unicode.ZWS, ''); canvas._moveCaretToTextElement(canvas.getDocumentElement(mutation.target), 'end'); } - observer.observe(canvas.d[0], config); + observer.observe(canvas.wrapper[0], config); canvas.publisher('contentChanged'); var textElement = canvas.getDocumentElement(mutation.target), @@ -119,7 +120,7 @@ $.extend(Canvas.prototype, { }); }); var config = { attributes: false, childList: false, characterData: true, subtree: true, characterDataOldValue: true}; - observer.observe(this.d[0], config); + observer.observe(this.wrapper[0], config); this.wrapper.on('mouseover', '[document-node-element], [document-text-element]', function(e) { diff --git a/src/editor/modules/documentCanvas/canvas/keyboard.js b/src/editor/modules/documentCanvas/canvas/keyboard.js index 16f2f68..dfe3f66 100644 --- a/src/editor/modules/documentCanvas/canvas/keyboard.js +++ b/src/editor/modules/documentCanvas/canvas/keyboard.js @@ -74,7 +74,8 @@ handlers.push({key: KEYS.ENTER, return false; // top level element is unsplittable } - var nodes = position.element.data('wlxmlNode').split({offset: position.offset}), + //var nodes = position.element.data('wlxmlNode').split({offset: position.offset}), + var nodes = position.element.data('wlxmlNode').transform('split', {offset: position.offset}), newEmpty, goto, gotoOptions; @@ -85,7 +86,7 @@ handlers.push({key: KEYS.ENTER, newEmpty = nodes.second; if(newEmpty) { - goto = newEmpty.append({text: ''}); + //goto = newEmpty.append({text: ''}); gotoOptions = {}; } else { goto = nodes.second; diff --git a/src/editor/modules/documentCanvas/canvas/wlxmlListener.js b/src/editor/modules/documentCanvas/canvas/wlxmlListener.js index 80da027..ffabfec 100644 --- a/src/editor/modules/documentCanvas/canvas/wlxmlListener.js +++ b/src/editor/modules/documentCanvas/canvas/wlxmlListener.js @@ -68,6 +68,7 @@ var handlers = { canvasNode.detach(); }, nodeTextChange: function(event) { + console.log('wlxmlListener: ' + event.meta.node.getText()); var canvasElement = utils.findCanvasElement(event.meta.node), toSet = event.meta.node.getText(); if(toSet === '') { diff --git a/src/editor/modules/documentToolbar/documentToolbar.js b/src/editor/modules/documentToolbar/documentToolbar.js index 7fbf6c1..75fcc72 100644 --- a/src/editor/modules/documentToolbar/documentToolbar.js +++ b/src/editor/modules/documentToolbar/documentToolbar.js @@ -36,6 +36,12 @@ return function(sandbox) { params.meta = meta; } + if(command === 'undo' || command === 'redo') { + params.callback = function(disable) { + btn.attr('disabled', !disable); + } + } + sandbox.publish('command', command, params); }); }, diff --git a/src/editor/modules/nodePane/nodePane.js b/src/editor/modules/nodePane/nodePane.js index 7473c2e..dbdd951 100644 --- a/src/editor/modules/nodePane/nodePane.js +++ b/src/editor/modules/nodePane/nodePane.js @@ -18,7 +18,11 @@ return function(sandbox) { var attr = target.attr('class').split('-')[3] === 'tagSelect' ? 'Tag' : 'Class', value = target.val().replace(/-/g, '.'); - currentNode['set' + attr](value); + if(attr === 'Class') { + //currentNode.document.transform('setClass', {node: currentNode, klass: value}); + currentNode.transform('setAttr', {name: 'class', value: value}); + } + //currentNode['set' + attr](value); }); return { diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 7b59b18..1379153 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -43,6 +43,16 @@ var DocumentNode = function(nativeNode, document) { }; $.extend(DocumentNode.prototype, { + + transform: function(name, args) { + var Transformation = contextTransformations[name], + transformation; + if(Transformation) { + transformation = new Transformation(this.document, this, args); + } + return this.document.transform(transformation); + }, + _setNativeNode: function(nativeNode) { this.nativeNode = nativeNode; this._$ = $(nativeNode); @@ -643,14 +653,21 @@ $.extend(Document.prototype, Backbone.Events, { transform: function(transformationName, args) { console.log('transform'); - var Transformation = transformations[transformationName], - transformation; - if(Transformation) { - transformation = new Transformation(args); - transformation.run(); + var Transformation, transformation, toret; + if(typeof transformationName === 'string') { + Transformation = transformations[transformationName]; + if(Transformation) { + transformation = new Transformation(args); + } + } else { + transformation = transformationName; + } + if(transformation) { + toret = transformation.run(); this.undoStack.push(transformation); console.log('clearing redo stack'); this.redoStack = []; + return toret; } else { throw new Error('Transformation ' + transformationName + ' doesn\'t exist!'); } @@ -806,6 +823,111 @@ var createTransformation = function(desc) { return NodeTransformation; } +/// +var Transformation = function(args) { + this.args = args; +}; +$.extend(Transformation.prototype, { + run: function() { + throw new Error('not implemented'); + }, + undo: function() { + throw new Error('not implemented'); + }, +}); + +var createGenericTransformation = function(desc) { + var GenericTransformation = function(document, args) { + //document.getNodeByPath(contextPath).call(this, args); + this.args = args; + this.document = document; + }; + $.extend(GenericTransformation.prototype, { + run: function() { + var changeRoot = desc.getChangeRoot ? desc.getChangeRoot.call(this) : this.document.root; + this.snapshot = changeRoot.clone(); + this.changeRootPath = changeRoot.getPath(); + return desc.impl.call(this.context, this.args); // a argumenty do metody? + }, + undo: function() { + this.document.getNodeByPath(this.changeRootPath).replaceWith(this.snapshot); + }, + }); + + return GenericTransformation; +}; + +// var T = createGenericTransformation({impl: function() {}}); +// var t = T(doc, {a:1,b:2,c3:3}); + + +var createContextTransformation = function(desc) { + // mozna sie pozbyc przez przeniesienie object/context na koniec argumentow konstruktora generic transformation + var GenericTransformation = createGenericTransformation(desc); + + var ContextTransformation = function(document, object, args) { + var contextPath = object.getPath(); + + GenericTransformation.call(this, document, args); + + Object.defineProperty(this, 'context', { + get: function() { + return document.getNodeByPath(contextPath); + } + }); + } + ContextTransformation.prototype = Object.create(GenericTransformation.prototype); + return ContextTransformation; +} +// var T = createContextTransformation({impl: function() {}}); +// var t = T(doc, node, {a:1,b:2,c3:3}); +/// + +var contextTransformations = {}; +contextTransformations['setText'] = createContextTransformation({ + impl: function(args) { + this.setText(args.text); + }, + getChangeRoot: function() { + return this.context; + } +}); + +contextTransformations['setAttr'] = createContextTransformation({ + impl: function(args) { + this.setAttr(args.name, args.value); + }, + getChangeRoot: function() { + return this.context; + } +}); + +contextTransformations['split'] = createContextTransformation({ + impl: function(args) { + return this.split({offset: args.offset}); + }//, + // getChangeRoot: function() { + // return this.context.parent().parent(); + // } +}); + +// var TRANSFORMATION2 = function(f, getChangeRoot, undo) { +// var context = this, + + +// var transformation = createContextTransformation({ +// impl: f, +// getChangeRoot: getChangeRoot, + +// }); + +// var toret = function() { +// var +// f.apply(context, createArgs ? createArgs(arguments) : arguments) +// }; +// return toret; +// } + transformations['detach2'] = createTransformation({ // impl: function() { // //this.setAttr('class', 'cite'); // @@ -817,7 +939,7 @@ transformations['detach2'] = createTransformation({ }); -transformations['setText'] = createTransformation({ +transformations['setText-old'] = createTransformation({ impl: function(args) { this.setText(args.text) }, @@ -827,6 +949,15 @@ transformations['setText'] = createTransformation({ }); +transformations['setClass-old'] = createTransformation({ + impl: function(args) { + this.setClass(args.klass); + }, + getRoot: function(node) { + return node; + } +}) + //3. detach z pełnym własnym redo var Detach3NodeTransformation = function(args) { @@ -867,6 +998,22 @@ $.extend(Detach3NodeTransformation.prototype, { }); transformations['detach3'] = Detach3NodeTransformation; + +var registerTransformationsFromObject = function(object) { + _.pairs(object).filter(function(pair) { + var property = pair[1]; + return typeof property === 'function' && property._isTransformation; + }) + .forEach(function(pair) { + var name = pair[0], + method = pair[1]; + object.registerTransformation(name, createContextTransformation(method)); + }); +}; +registerTransformationsFromObject(ElementNode.prototype); +registerTransformationsFromObject(TextNode.prototype); +registerTransformationsFromObject(Document.prototype); + return { documentFromXML: function(xml) { return new Document(xml); -- 2.20.1 From 8464a511c74dff643095ce419659df60b0580b7a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Fri, 29 Nov 2013 16:26:49 +0100 Subject: [PATCH 05/16] wip: breakContent & others transformations - still need to confirm the api --- .../modules/documentCanvas/canvas/canvas.js | 2 +- .../modules/documentCanvas/canvas/keyboard.js | 36 +- src/editor/modules/documentCanvas/commands.js | 36 +- src/editor/modules/nodePane/nodePane.js | 16 +- src/editor/plugins/core.js | 86 ++++ src/smartxml/smartxml.js | 431 +++++------------- src/smartxml/transformation_api_exp.js | 288 ++++++++++++ src/smartxml/transformations.js | 142 ++++++ src/wlxml/wlxml.js | 15 +- 9 files changed, 701 insertions(+), 351 deletions(-) create mode 100644 src/editor/plugins/core.js create mode 100644 src/smartxml/transformation_api_exp.js create mode 100644 src/smartxml/transformations.js diff --git a/src/editor/modules/documentCanvas/canvas/canvas.js b/src/editor/modules/documentCanvas/canvas/canvas.js index e181195..ae75d61 100644 --- a/src/editor/modules/documentCanvas/canvas/canvas.js +++ b/src/editor/modules/documentCanvas/canvas/canvas.js @@ -39,7 +39,7 @@ $.extend(TextHandler.prototype, { }, setText: function(text, node) { //this.canvas.wlxmlDocument.transform('setText', {node:node, text: text}); - node.transform('setText', {text: text}); + node.transform('smartxml.setText', {text: text}); } diff --git a/src/editor/modules/documentCanvas/canvas/keyboard.js b/src/editor/modules/documentCanvas/canvas/keyboard.js index dfe3f66..418dc93 100644 --- a/src/editor/modules/documentCanvas/canvas/keyboard.js +++ b/src/editor/modules/documentCanvas/canvas/keyboard.js @@ -75,22 +75,32 @@ handlers.push({key: KEYS.ENTER, } //var nodes = position.element.data('wlxmlNode').split({offset: position.offset}), - var nodes = position.element.data('wlxmlNode').transform('split', {offset: position.offset}), - newEmpty, - goto, - gotoOptions; - - if(position.offsetAtBeginning) - newEmpty = nodes.first; - else if(position.offsetAtEnd) - newEmpty = nodes.second; + // var nodes = position.element.data('wlxmlNode').transform('split', {offset: position.offset}), + // newEmpty, + // goto, + // gotoOptions; + + // if(position.offsetAtBeginning) + // newEmpty = nodes.first; + // else if(position.offsetAtEnd) + // newEmpty = nodes.second; - if(newEmpty) { - //goto = newEmpty.append({text: ''}); + // if(newEmpty) { + // //goto = newEmpty.append({text: ''}); + // gotoOptions = {}; + // } else { + // goto = nodes.second; + // gotoOptions = {caretTo: 'start'}; + // } + + var result = position.element.data('wlxmlNode').transform('rng.breakContent', {offset: position.offset}), + goto, gotoOptions; + if(result.emptyText) { + goto = result.createdEmpty; gotoOptions = {}; } else { - goto = nodes.second; - gotoOptions = {caretTo: 'start'}; + goto = result.second; + gotoOptions = {caretTo: 'start'}; } canvas.setCurrentElement(utils.findCanvasElement(goto), gotoOptions); diff --git a/src/editor/modules/documentCanvas/commands.js b/src/editor/modules/documentCanvas/commands.js index 57a5130..8793b9c 100644 --- a/src/editor/modules/documentCanvas/commands.js +++ b/src/editor/modules/documentCanvas/commands.js @@ -38,7 +38,9 @@ commands.register('remove-node', function(canvas) { parent1 = selectionStart.element.parent() || undefined, parent2 = selectionEnd.element.parent() || undefined; - canvas.wlxmlDocument.transform('detach2', {node:canvas.getCurrentNodeElement().data('wlxmlNode')}); +// canvas.wlxmlDocument.transform('detach2', {node:canvas.getCurrentNodeElement().data('wlxmlNode')}); + canvas.getCurrentNodeElement().data('wlxmlNode').transform('smartxml.detach'); + }); commands.register('unwrap-node', function(canvas) { @@ -108,15 +110,23 @@ commands.register('newNodeRequested', function(canvas, params) { if(cursor.isSelectingWithinElement()) { var wlxmlNode = selectionStart.element.data('wlxmlNode'), caretTo = selectionStart.offset < selectionEnd.offset ? 'start' : 'end', - wrapper = wlxmlNode.wrapWith({tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}, start: selectionStart.offset, end: selectionEnd.offset}), - wrapperCanvasElement = utils.findCanvasElement(wrapper); + //wrapper = wlxmlNode.wrapWith({tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}, start: selectionStart.offset, end: selectionEnd.offset}), + wrapper = wlxmlNode.transform('smartxml.wrapWith', {tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}, start: selectionStart.offset, end: selectionEnd.offset}) + ; + var wrapperCanvasElement = utils.findCanvasElement(wrapper); canvas.setCurrentElement(wrapperCanvasElement.children()[0], {caretTo: caretTo}); } else { var wlxmlNode = selectionStart.element.data('wlxmlNode').parent(), caretTo = selectionStart.element.sameNode(cursor.getSelectionAnchor().element) ? 'end' : 'start'; - var wrapper = wlxmlNode.wrapText({ + // var wrapper = wlxmlNode.wrapText({ + // _with: {tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}}, + // offsetStart: selectionStart.offset, + // offsetEnd: selectionEnd.offset, + // textNodeIdx: [wlxmlNode.indexOf(selectionStart.element.data('wlxmlNode')), wlxmlNode.indexOf(selectionEnd.element.data('wlxmlNode'))] //parent.childIndex(selectionEnd.element)] + // }), + var wrapper = wlxmlNode.transform('smartxml.wrapText', { _with: {tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}}, offsetStart: selectionStart.offset, offsetEnd: selectionEnd.offset, @@ -131,17 +141,23 @@ commands.register('newNodeRequested', function(canvas, params) { siblingParents = canvas.wlxmlDocument.getSiblingParents({node1: node1, node2: node2}); if(siblingParents) { - canvas.wlxmlDocument.wrapNodes({ - element1: siblingParents.node1, - element2: siblingParents.node2, + // canvas.wlxmlDocument.wrapNodes({ + // element1: siblingParents.node1, + // element2: siblingParents.node2, + // _with: {tagName: params.wlxmlTag, attrs: {klass: params.wlxmlClass}} + // }); + canvas.wlxmlDocument.transform('smartxml.wrapNodes', { + node1: siblingParents.node1, + node2: siblingParents.node2, _with: {tagName: params.wlxmlTag, attrs: {klass: params.wlxmlClass}} }); } } } else if(canvas.getCurrentNodeElement()) { - var node = findCanvasElement(canvas.getCurrentNodeElement), - wrapper = node.wrapWith({tagName: params.wlxmlTag, attrs: {klass: params.wlxmlClass}}); - canvas.setCurrentElement(findCanvasElement(wrapper)); + var node = canvas.getCurrentNodeElement().data('wlxmlNode'), + // wrapper = node.wrapWith({tagName: params.wlxmlTag, attrs: {klass: params.wlxmlClass}}); + wrapper = node.transform('smartxml.wrapWith', {tagName: params.wlxmlTag, attrs: {klass: params.wlxmlClass}}); + canvas.setCurrentElement(utils.findCanvasElement(wrapper)); } diff --git a/src/editor/modules/nodePane/nodePane.js b/src/editor/modules/nodePane/nodePane.js index dbdd951..1ecafe1 100644 --- a/src/editor/modules/nodePane/nodePane.js +++ b/src/editor/modules/nodePane/nodePane.js @@ -20,11 +20,11 @@ return function(sandbox) { if(attr === 'Class') { //currentNode.document.transform('setClass', {node: currentNode, klass: value}); - currentNode.transform('setAttr', {name: 'class', value: value}); + currentNode.transform('smartxml.setAttr', {name: 'class', value: value}); } //currentNode['set' + attr](value); }); - + return { start: function() { sandbox.publish('ready'); @@ -33,6 +33,15 @@ return function(sandbox) { return view; }, setNodeElement: function(wlxmlNodeElement) { + var module = this; + if(!currentNode) { + wlxmlNodeElement.document.on('change', function(event) { + if(event.type === 'nodeAttrChange' && event.meta.node.sameNode(currentNode)) { + module.setNodeElement(currentNode); + } + }); + } + view.find('.rng-module-nodePane-tagSelect').val(wlxmlNodeElement.getTagName()); var escapedClassName = (wlxmlNodeElement.getClass() || '').replace(/\./g, '-'); @@ -40,7 +49,8 @@ return function(sandbox) { var widget = metaWidget.create({attrs:wlxmlNodeElement.getMetaAttributes()}); widget.on('valueChanged', function(key, value) { - wlxmlNodeElement.setMetaAttribute(key, value); + wlxmlNodeElement.transform('wlxml.setMetaAttribute', {name: key, value: value}); + //wlxmlNodeElement.setMetaAttribute(key, value); }); view.find('.metaFields').empty().append(widget.el); diff --git a/src/editor/plugins/core.js b/src/editor/plugins/core.js new file mode 100644 index 0000000..d8b01c8 --- /dev/null +++ b/src/editor/plugins/core.js @@ -0,0 +1,86 @@ +define([ + +], function() { + +'use strict'; + +var breakContentTransformation = { + impl: function(args) { + var node = this.context, + newNodes, emptyNode, emptyText; + newNodes = node.transform('smartxml.split', {offset: args.offset}); + [newNodes.first, newNodes.second].some(function(newNode) { + if(!(newNode.contents().length)) { + newNode.transform('smartxml.append', {text: ''}); + return true; // break + } + }); + return _.extend(newNodes, {emptyText: emptyText}); + }, + isAllowed: function() { + + } +}; + + +var breakContentAction = function(document, context) { + var textNode = context.cursor.currentNode; + if(textNode) { + var result, goto; + + result = textNode.transform('core.break-content', {offset: context.cursor.offset}); + + if(result.emptyText) { + goto = result.createdEmpty; + gotoOptions = {}; + } else { + goto = result.second; + gotoOptions = {caretTo: 'start'}; + } + + context.setCurrentElement(goto, gotoOptions); + } +} +breakContentAction.isAllowed = function(document, context) { + /* globals Node */ + var node = context.cursor.currentNode; + return node.nodeType === Node.TEXT_NODE; +} + +return { + keyHandlers: [ + {key: 'ENTER', target: 'main-document-area', handler: function(editor) { + var action = editor.getAction('core.break-document-content'); + if(action.isAllowed()) { + action.execute(); + } + }}, + {key: 'ENTER', target: 'main-document-area', actionHandler: 'core.break-document-content'} + ], + + documentActions: [ + { + name: 'core.break-document-content', + context: 'main-document-area', + label: 'break here' + icon: 'core:some-name', + action: breakContentAction + } + ], + + // zapisywanie dokumentu: + + documentTransformations: [ + {name: 'core.break-content', textNode: true, t: breakContentTransformation}, + + // transformacja z poziomu smartxml + {name: 'core.wrap-with', textNode: true, t: wrapWith} + + // list plugin: + {name: 'list.remove-list', elementNode: 'list', t: null} + // hipotetyczna akcja na itemie listy + {name: 'list.extract', elementNode: 'item', requiresParent: 'list', requiresInParents: '?'} + ] +}; + +}); \ No newline at end of file diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 1379153..712c0ad 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -2,8 +2,9 @@ define([ 'libs/jquery', 'libs/underscore', 'libs/backbone', - 'smartxml/events' -], function($, _, Backbone, events) { + 'smartxml/events', + 'smartxml/transformations' +], function($, _, Backbone, events, transformations) { 'use strict'; /* globals Node */ @@ -25,14 +26,6 @@ var INSERTION = function(implementation) { return toret; }; -// var TRANSFORMATION = function(name, implementation) { -// //implementation._isTransformation = true; - -// createDumbTransformation(name, implementation, ) - -// return implementation; -// }; - var DocumentNode = function(nativeNode, document) { if(!document) { throw new Error('undefined document for a node'); @@ -44,8 +37,10 @@ var DocumentNode = function(nativeNode, document) { $.extend(DocumentNode.prototype, { + transformations: new transformations.TransformationStorage(), + transform: function(name, args) { - var Transformation = contextTransformations[name], + var Transformation = this.transformations.get(name), transformation; if(Transformation) { transformation = new Transformation(this.document, this, args); @@ -380,6 +375,52 @@ $.extend(ElementNode.prototype, { } }); +// trans + +// todo - split+append + +ElementNode.prototype.transformations.register(transformations.createContextTransformation({ + name: 'smartxml.setAttr', + impl: function(args) { + this.setAttr(args.name, args.value); + }, + getChangeRoot: function() { + return this.context; + } +})); + +DocumentNode.prototype.transformations.register(transformations.createContextTransformation({ + name: 'smartxml.wrapWith', + getChangeRoot: function() { + return this.context.parent(); + }, + impl: function(args) { + return this.wrapWith(args); + } +})); + +DocumentNode.prototype.transformations.register(transformations.createContextTransformation({ + name: 'smartxml.wrapText', + getChangeRoot: function() { + return this.context; + }, + impl: function(args) { + return this.wrapText(args); + } +})); + +DocumentNode.prototype.transformations.register(transformations.createContextTransformation({ + name: 'smartxml.detach', + getChangeRoot: function() { + return this.context.parent(); + }, + impl: function(args) { + return this.detach(); + } +})); + +/// + var TextNode = function(nativeNode, document) { DocumentNode.call(this, nativeNode, document); }; @@ -467,6 +508,49 @@ $.extend(TextNode.prototype, { }); +TextNode.prototype.transformations.register(transformations.createContextTransformation({ + name: 'rng.breakContent', + // impl: function(args) { + // var node = this.context, + // newNodes, emptyNode, emptyText; + // newNodes = node.transform('smartxml.split', {offset: args.offset}); + // [newNodes.first, newNodes.second].some(function(newNode) { + // if(!(newNode.contents().length)) { + // newNode.transform('smartxml.append', {text: ''}); + // return true; // break + // } + // }); + // return _.extend(newNodes, {emptyText: emptyText}); + // }, + impl: function(args) { + var node = this, + newNodes, emptyNode, emptyText; + newNodes = node.split({offset: args.offset}); + [newNodes.first, newNodes.second].some(function(newNode) { + if(!(newNode.contents().length)) { + newNode.append({text: ''}); + return true; // break + } + }); + return _.extend(newNodes, {emptyText: emptyText}); + }, + getChangeRoot: function() { + return this.context.parent().parent(); + } +})); + + +ElementNode.prototype.transformations.register(transformations.createContextTransformation({ + name: 'smartxml.setText', + impl: function(args) { + this.setText(args.text); + }, + getChangeRoot: function() { + return this.context; + } +})); + + var parseXML = function(xml) { return $($.trim(xml))[0]; }; @@ -480,6 +564,7 @@ var Document = function(xml) { $.extend(Document.prototype, Backbone.Events, { ElementNodeFactory: ElementNode, TextNodeFactory: TextNode, + transformations: new transformations.TransformationStorage(), createDocumentNode: function(from) { if(!(from instanceof Node)) { @@ -651,17 +736,15 @@ $.extend(Document.prototype, Backbone.Events, { return insertion.ofNode; }, - transform: function(transformationName, args) { + transform: function(transformation, args) { console.log('transform'); - var Transformation, transformation, toret; - if(typeof transformationName === 'string') { - Transformation = transformations[transformationName]; + var Transformation, toret; + if(typeof transformation === 'string') { + Transformation = this.transformations.get(transformation); if(Transformation) { - transformation = new Transformation(args); + transformation = new Transformation(this, this, args); } - } else { - transformation = transformationName; - } + } if(transformation) { toret = transformation.run(); this.undoStack.push(transformation); @@ -669,7 +752,7 @@ $.extend(Document.prototype, Backbone.Events, { this.redoStack = []; return toret; } else { - throw new Error('Transformation ' + transformationName + ' doesn\'t exist!'); + throw new Error('Transformation ' + transformation + ' doesn\'t exist!'); } }, undo: function() { @@ -705,314 +788,20 @@ var defineDocumentProperties = function(doc, $document) { }, configurable: true}); }; +Document.prototype.transformations.register(transformations.createContextTransformation({ + name: 'smartxml.wrapNodes', + // init: function() { -// var registerTransformationsFromObject = function(object) { -// _.values(object).filter(function(val) { -// return typeof val === 'function' && val._isTransformation; -// }) -// .forEach(function(val) { -// registerTransformation(val._transformationName, val, object); -// }); -// }; -// registerTransformationsFromObject(ElementNode.prototype); -// registerTransformationsFromObject(TextNode.prototype); -// registerTransformationsFromObject(Document.prototype); - -// var Transformation = function() { -// }; -// $.extend(Transformation.prototype, { - -// }); - - -// var createDumbTransformation = function(impl, contextObject) { -// var DumbTransformation = function(args) { -// this.args = this.args; -// }; -// DumbTransformation.prototype = Object.create(Transformation.prototype); -// $.extend(DumbTransformation.prototype, { -// run: function() { -// impl.apply(contextObject, this.args); -// } -// }); - -// return DumbTransformation; - - -// }; - -var transformations = {}; -// var registerTransformation = function(name, impl, contextObject) { -// if(typeof impl === 'function') { -// transformations[name] = createDumbTransformation(impl, contextObject); -// } -// }; - -// registerTransformation('detachx', DocumentNode.prototype.detach, ) - - -// 1. detach via totalny fallback -var DetachNodeTransformation = function(args) { - this.node = args.node; - this.document = this.node.document; -}; -$.extend(DetachNodeTransformation.prototype, { - run: function() { - this.oldRoot = this.node.document.root.clone(); - this.path = this.node.getPath(); - this.node.detach(); // @TS - - }, - undo: function() { - this.document.root.replaceWith(this.oldRoot); // this.getDocument? - this.node = this.document.getNodeByPath(this.path); - } -}); -transformations['detach'] = DetachNodeTransformation; - -//2. detach via wskazanie changeroot - -var Detach2NodeTransformation = function(args) { - this.nodePath = args.node.getPath(); - this.document = args.node.document; -}; -$.extend(Detach2NodeTransformation.prototype, { - run: function() { - var node = this.document.getNodeByPath(this.nodePath), - root = node.parent() ? node.parent() : this.document.root; - - this.rootPath = root.getPath(); - this.oldRoot = (root).clone(); - node.detach(); - }, - undo: function() { - this.document.getNodeByPath(this.rootPath).replaceWith(this.oldRoot); - } -}); -//transformations['detach2'] = Detach2NodeTransformation; - -//2a. generyczna transformacja - -var createTransformation = function(desc) { - - var NodeTransformation = function(args) { - this.nodePath = args.node.getPath(); - this.document = args.node.document; - this.args = args; - }; - $.extend(NodeTransformation.prototype, { - run: function() { - var node = this.document.getNodeByPath(this.nodePath), - root; - - if(desc.getRoot) { - root = desc.getRoot(node); - } else { - root = this.document.root; - } - - this.rootPath = root.getPath(); - this.oldRoot = (root).clone(); - desc.impl.call(node, this.args); - }, - undo: function() { - this.document.getNodeByPath(this.rootPath).replaceWith(this.oldRoot); - } - }); - - return NodeTransformation; -} - -/// -var Transformation = function(args) { - this.args = args; -}; -$.extend(Transformation.prototype, { - run: function() { - throw new Error('not implemented'); - }, - undo: function() { - throw new Error('not implemented'); - }, -}); - -var createGenericTransformation = function(desc) { - var GenericTransformation = function(document, args) { - //document.getNodeByPath(contextPath).call(this, args); - this.args = args; - this.document = document; - }; - $.extend(GenericTransformation.prototype, { - run: function() { - var changeRoot = desc.getChangeRoot ? desc.getChangeRoot.call(this) : this.document.root; - this.snapshot = changeRoot.clone(); - this.changeRootPath = changeRoot.getPath(); - return desc.impl.call(this.context, this.args); // a argumenty do metody? - }, - undo: function() { - this.document.getNodeByPath(this.changeRootPath).replaceWith(this.snapshot); - }, - }); - - return GenericTransformation; -}; - -// var T = createGenericTransformation({impl: function() {}}); -// var t = T(doc, {a:1,b:2,c3:3}); - - -var createContextTransformation = function(desc) { - // mozna sie pozbyc przez przeniesienie object/context na koniec argumentow konstruktora generic transformation - var GenericTransformation = createGenericTransformation(desc); - - var ContextTransformation = function(document, object, args) { - var contextPath = object.getPath(); - - GenericTransformation.call(this, document, args); - - Object.defineProperty(this, 'context', { - get: function() { - return document.getNodeByPath(contextPath); - } - }); - } - ContextTransformation.prototype = Object.create(GenericTransformation.prototype); - return ContextTransformation; -} -// var T = createContextTransformation({impl: function() {}}); -// var t = T(doc, node, {a:1,b:2,c3:3}); -/// - -var contextTransformations = {}; -contextTransformations['setText'] = createContextTransformation({ - impl: function(args) { - this.setText(args.text); - }, - getChangeRoot: function() { - return this.context; - } -}); - -contextTransformations['setAttr'] = createContextTransformation({ - impl: function(args) { - this.setAttr(args.name, args.value); - }, - getChangeRoot: function() { - return this.context; - } -}); - -contextTransformations['split'] = createContextTransformation({ - impl: function(args) { - return this.split({offset: args.offset}); - }//, + // }, // getChangeRoot: function() { - // return this.context.parent().parent(); - // } -}); - -// var TRANSFORMATION2 = function(f, getChangeRoot, undo) { -// var context = this, - - -// var transformation = createContextTransformation({ -// impl: f, -// getChangeRoot: getChangeRoot, - -// }); - -// var toret = function() { -// var -// f.apply(context, createArgs ? createArgs(arguments) : arguments) -// }; -// return toret; -// } - -transformations['detach2'] = createTransformation({ - // impl: function() { - // //this.setAttr('class', 'cite'); // + // return this.context; // }, - impl: ElementNode.prototype.detach, - getRoot: function(node) { - return node.parent(); - } - -}); - -transformations['setText-old'] = createTransformation({ - impl: function(args) { - this.setText(args.text) - }, - getRoot: function(node) { - return node; - } - -}); - -transformations['setClass-old'] = createTransformation({ impl: function(args) { - this.setClass(args.klass); + this.wrapNodes(args); }, - getRoot: function(node) { - return node; - } -}) -//3. detach z pełnym własnym redo +})); -var Detach3NodeTransformation = function(args) { - this.node = args.node; - this.document = this.node.document; -}; -$.extend(Detach3NodeTransformation.prototype, { - run: function() { - //this.index = this.node.getIndex(); - //this.parent = this.node.parent(); - - this.path = this.node.getPath(); - if(this.node.isSurroundedByTextElements()) { - this.prevText = this.node.prev().getText(); - this.nextText = this.node.next().getText(); - this.merge = true; - } else { - this.prevText = this.nextText = null; - this.merge = false; - } - - this.node.detach(); - }, - undo: function() { - var parent = this.document.getNodeByPath(this.path.slice(0,-1)), - idx = _.last(this.path); - var inserted = parent.insertAtIndex(this.node, idx); - if(this.merge) { - if(inserted.next()) { - inserted.before({text: this.prevText}); - inserted.next().setText(this.nextText); - } else { - inserted.prev().setText(this.prevText); - inserted.after({text: this.nextText}); - } - } - } -}); -transformations['detach3'] = Detach3NodeTransformation; - - -var registerTransformationsFromObject = function(object) { - _.pairs(object).filter(function(pair) { - var property = pair[1]; - return typeof property === 'function' && property._isTransformation; - }) - .forEach(function(pair) { - var name = pair[0], - method = pair[1]; - object.registerTransformation(name, createContextTransformation(method)); - }); -}; -registerTransformationsFromObject(ElementNode.prototype); -registerTransformationsFromObject(TextNode.prototype); -registerTransformationsFromObject(Document.prototype); return { documentFromXML: function(xml) { diff --git a/src/smartxml/transformation_api_exp.js b/src/smartxml/transformation_api_exp.js new file mode 100644 index 0000000..7a03763 --- /dev/null +++ b/src/smartxml/transformation_api_exp.js @@ -0,0 +1,288 @@ +//use case: edytor robi split, jesli split był na koncu (czyli druga czesc jest pusta) +// chce tam dodac wezel tekstowy + +// flow: plugin_key_handler(enter, main-canvas-area) -> plugin_document_action('break-content') +// --w srodku--> refactoring tego co teraz w keyboard: + +//1. jedna transformacja z warunkiem (Zarejestrowana przez plugin) + + + +var breakContentTransformation = { + impl: function(args) { + var node = this.context; + emptyText = false, + newNodes, + emptyNode; + + newNodes = node.transform('core.split', {offset: args.offset}); + + if(args.offset === 0) + emptyNode = newNodes.first; + else if(args.offset === node.getText().length); //@ nie ma atEnd :( + emptyNode = newNodes.second; + + if(emptyNode) { + emptyText = emptyNode.transform('core.append', {text: ''}); + } + + return _.extend(newNodes, {emptyText: emptyText}); + } +}; + + +var breakContentAction = function(document, context) { + var textNode = context.currentTextNode; + if(textNode) { + var result, goto; + + result = textNode.transform('core.break-content', {offset: context.offset}); + + if(result.emptyText) { + goto = result.createdEmpty; + gotoOptions = {}; + } else { + goto = result.second; + gotoOptions = {caretTo: 'start'}; + } + + context.setCurrentElement(goto, gotoOptions); + } +} + +var toret = { + keyHandlers: [ + {key: 'ENTER', target: 'main-document-area', handler: function(editor) { + editor.getAction('core.break-document-content').execute(); + }}, + ], + + actions: [ + {name: 'core.break-document-content', context: 'main-document-area', action: breakContentAction} + ], + + // zapisywanie dokumentu: + + contextTransformations: [ + {name: 'core.break-content', textNode: true, t: breakContentTransformation}, + + // list plugin: + {name: 'list.remove-list', elementNode: 'list', t: null} + // hipotetyczna akcja na itemie listy + {name: 'list.extract', elementNode: 'item', requiresParent: 'list', requiresInParents: '?'} + ], + +} + + +/// STARE + +// 1. detach via totalny fallback +var DetachNodeTransformation = function(args) { + this.node = args.node; + this.document = this.node.document; +}; +$.extend(DetachNodeTransformation.prototype, { + run: function() { + this.oldRoot = this.node.document.root.clone(); + this.path = this.node.getPath(); + this.node.detach(); // @TS + + }, + undo: function() { + this.document.root.replaceWith(this.oldRoot); // this.getDocument? + this.node = this.document.getNodeByPath(this.path); + } +}); +transformations['detach'] = DetachNodeTransformation; + +//2. detach via wskazanie changeroot + +var Detach2NodeTransformation = function(args) { + this.nodePath = args.node.getPath(); + this.document = args.node.document; +}; +$.extend(Detach2NodeTransformation.prototype, { + run: function() { + var node = this.document.getNodeByPath(this.nodePath), + root = node.parent() ? node.parent() : this.document.root; + + this.rootPath = root.getPath(); + this.oldRoot = (root).clone(); + node.detach(); + }, + undo: function() { + this.document.getNodeByPath(this.rootPath).replaceWith(this.oldRoot); + } +}); +//transformations['detach2'] = Detach2NodeTransformation; + +//2a. generyczna transformacja + +var createTransformation = function(desc) { + + var NodeTransformation = function(args) { + this.nodePath = args.node.getPath(); + this.document = args.node.document; + this.args = args; + }; + $.extend(NodeTransformation.prototype, { + run: function() { + var node = this.document.getNodeByPath(this.nodePath), + root; + + if(desc.getRoot) { + root = desc.getRoot(node); + } else { + root = this.document.root; + } + + this.rootPath = root.getPath(); + this.oldRoot = (root).clone(); + desc.impl.call(node, this.args); + }, + undo: function() { + this.document.getNodeByPath(this.rootPath).replaceWith(this.oldRoot); + } + }); + + return NodeTransformation; +} + + + +var contextTransformations = {}; +contextTransformations['setText'] = createContextTransformation({ + impl: function(args) { + this.setText(args.text); + }, + getChangeRoot: function() { + return this.context; + } +}); + +contextTransformations['setAttr'] = createContextTransformation({ + impl: function(args) { + this.setAttr(args.name, args.value); + }, + getChangeRoot: function() { + return this.context; + } +}); + +contextTransformations['split'] = createContextTransformation({ + impl: function(args) { + return this.split({offset: args.offset}); + }//, + // getChangeRoot: function() { + // return this.context.parent().parent(); + // } +}); + + +contextTransformations['before'] = createContextTransformation({ + getChangeRoot: function() { + return this.context.parent(); + }, + impl: function(args) { + this.before(args.node) + }, + +}); + +contextTransformations['before'] = createContextTransformation({ + impl: function(args) { + this.before(args.node) + }, + undo: function() { + this.context.detach(); + } +}); + + + +transformations['detach2'] = createTransformation({ + // impl: function() { + // //this.setAttr('class', 'cite'); // + // }, + impl: ElementNode.prototype.detach, + getRoot: function(node) { + return node.parent(); + } + +}); + +transformations['setText-old'] = createTransformation({ + impl: function(args) { + this.setText(args.text) + }, + getRoot: function(node) { + return node; + } + +}); + +transformations['setClass-old'] = createTransformation({ + impl: function(args) { + this.setClass(args.klass); + }, + getRoot: function(node) { + return node; + } +}) + +//3. detach z pełnym własnym redo + +var Detach3NodeTransformation = function(args) { + this.node = args.node; + this.document = this.node.document; +}; +$.extend(Detach3NodeTransformation.prototype, { + run: function() { + //this.index = this.node.getIndex(); + //this.parent = this.node.parent(); + + this.path = this.node.getPath(); + if(this.node.isSurroundedByTextElements()) { + this.prevText = this.node.prev().getText(); + this.nextText = this.node.next().getText(); + this.merge = true; + } else { + this.prevText = this.nextText = null; + this.merge = false; + } + + this.node.detach(); + }, + undo: function() { + var parent = this.document.getNodeByPath(this.path.slice(0,-1)), + idx = _.last(this.path); + var inserted = parent.insertAtIndex(this.node, idx); + if(this.merge) { + if(inserted.next()) { + inserted.before({text: this.prevText}); + inserted.next().setText(this.nextText); + } else { + inserted.prev().setText(this.prevText); + inserted.after({text: this.nextText}); + } + } + } +}); +transformations['detach3'] = Detach3NodeTransformation; + + +var registerTransformationsFromObject = function(object) { + _.pairs(object).filter(function(pair) { + var property = pair[1]; + return typeof property === 'function' && property._isTransformation; + }) + .forEach(function(pair) { + var name = pair[0], + method = pair[1]; + object.registerTransformation(name, createContextTransformation(method)); + }); +}; +registerTransformationsFromObject(ElementNode.prototype); +registerTransformationsFromObject(TextNode.prototype); +registerTransformationsFromObject(Document.prototype); \ No newline at end of file diff --git a/src/smartxml/transformations.js b/src/smartxml/transformations.js new file mode 100644 index 0000000..53dc384 --- /dev/null +++ b/src/smartxml/transformations.js @@ -0,0 +1,142 @@ +define(function(require) { + +'use strict'; + +var _ = require('libs/underscore'), + toret = {}; + + +toret.createGenericTransformation = function(desc) { + + var GenericTransformation = function(document, args) { + this.args = args || {}; + + var transformation = this; + _.keys(this.args).forEach(function(key) { + if(transformation.args[key].nodeType) { //@@ change to instanceof check, fix circular dependency + var value = transformation.args[key], + path = value.getPath(); + Object.defineProperty(transformation.args, key, { + get: function() { + if(transformation.hasRun) { + console.log('returning via path'); + return transformation.document.getNodeByPath(path); + } else { + console.log('returning original arg'); + return value; + + } + } + }); + } + }); + this.document = document; + this.hasRun = false; + if(desc.init) { + desc.init.call(this); + } + }; + _.extend(GenericTransformation.prototype, { + name: desc.name, + run: function() { + var changeRoot; + if(!desc.undo) { + changeRoot = desc.getChangeRoot ? desc.getChangeRoot.call(this) : this.document.root; + this.snapshot = changeRoot.clone(); + this.changeRootPath = changeRoot.getPath(); + } + var toret = desc.impl.call(this.context, this.args); // a argumenty do metody? + this.hasRun = true; + return toret; + }, + undo: function() { + if(desc.undo) { + desc.undo.call(this.context); + } else { + this.document.getNodeByPath(this.changeRootPath).replaceWith(this.snapshot); + } + }, + }); + + return GenericTransformation; +}; +// var T = createGenericTransformation({impl: function() {}}); +// var t = T(doc, {a:1,b:2,c3:3}); + + +toret.createContextTransformation = function(desc) { + // mozna sie pozbyc przez przeniesienie object/context na koniec argumentow konstruktora generic transformation + var GenericTransformation = toret.createGenericTransformation(desc); + + var ContextTransformation = function(document, object, args) { + GenericTransformation.call(this, document, args); + + if(document === object) { + this.context = document; + } else { + var contextPath = object.getPath(); + Object.defineProperty(this, 'context', { + get: function() { + // todo: to jakos inaczej, bo np. this.context w undo transformacji before to juz nie ten sam obiekt + // moze transformacja powinna zwracac zmodyfikowana sciezke do obiektu po dzialaniu run? + + // tu tez trick z hasRun + return document.getNodeByPath(contextPath); + } + }); + } + } + ContextTransformation.prototype = Object.create(GenericTransformation.prototype); + return ContextTransformation; +} +// var T = createContextTransformation({impl: function() {}}); +// var t = T(doc, node, {a:1,b:2,c3:3}); +/// + + + +toret.TransformationStorage = function() {}; + +_.extend(toret.TransformationStorage.prototype, { + _transformations: {}, + + register: function(Transformation) { + var list = (this._transformations[Transformation.prototype.name] = this._transformations[Transformation.prototype.name] || []); + list.push(Transformation); + }, + + get: function(name) { + var transformations = this._transformations[name]; + if(!transformations) { + throw new Error('Transformation "' + name + '" not found!'); + } + // na razie zwraca pierwsza + return transformations[0]; + } +}); + + + +// var registerTransformationFromMethod = (object, methodName, desc) { +// if(!object[methodName]) { +// throw new Exeption('Cannot register transformation from unknown method ' + methodName + ' on ' + object); +// } +// desc.impl = object[name]; +// Transformation = createContextTransformation(desc); +// object.prototype.registerContextTransformation(name, createContextTransformation(method)); +// }; + + +// registerTransformationFromMethod(ElementNode, 'setAttr', { +// impl: function(args) { +// this.setAttr(args.name, args.value); +// }, +// getChangeRoot: function() { +// return this.context; +// } + +// }); + +return toret; + +}); \ No newline at end of file diff --git a/src/wlxml/wlxml.js b/src/wlxml/wlxml.js index 63d2307..e6ea26b 100644 --- a/src/wlxml/wlxml.js +++ b/src/wlxml/wlxml.js @@ -1,8 +1,9 @@ define([ 'libs/jquery', 'libs/underscore', - 'smartxml/smartxml' -], function($, _, smartxml) { + 'smartxml/smartxml', + 'smartxml/transformations' +], function($, _, smartxml, transformations) { 'use strict'; @@ -109,7 +110,15 @@ $.extend(WLXMLElementNode.prototype, smartxml.ElementNode.prototype, { } }); - +WLXMLElementNode.prototype.transformations.register(transformations.createContextTransformation({ + name: 'wlxml.setMetaAttribute', + impl: function(args) { + this.setMetaAttribute(args.name, args.value); + }, + getChangeRoot: function() { + return this.context; + } +})); -- 2.20.1 From 2ceaf44955f0f54fa0336978586bb8d9f4d7890b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Mon, 2 Dec 2013 14:19:34 +0100 Subject: [PATCH 06/16] wip: lists + uri extensions --- src/smartxml/smartxml.js | 14 +- src/wlxml/extensions/list/list.js | 167 ++++++++++ src/wlxml/extensions/list/list.test.js | 417 +++++++++++++++++++++++++ src/wlxml/extensions/uri.js | 10 + src/wlxml/wlxml.js | 41 ++- 5 files changed, 644 insertions(+), 5 deletions(-) create mode 100644 src/wlxml/extensions/list/list.js create mode 100644 src/wlxml/extensions/list/list.test.js create mode 100644 src/wlxml/extensions/uri.js diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 712c0ad..fc9715b 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -242,12 +242,18 @@ $.extend(ElementNode.prototype, { return this.nativeNode.tagName.toLowerCase(); }, - contents: function() { + contents: function(selector) { var toret = [], document = this.document; - this._$.contents().each(function() { - toret.push(document.createDocumentNode(this)); - }); + if(selector) { + this._$.children(selector).each(function() { + toret.push(document.createDocumentNode(this)); + }); + } else { + this._$.contents().each(function() { + toret.push(document.createDocumentNode(this)); + }); + } return toret; }, diff --git a/src/wlxml/extensions/list/list.js b/src/wlxml/extensions/list/list.js new file mode 100644 index 0000000..dd010a6 --- /dev/null +++ b/src/wlxml/extensions/list/list.js @@ -0,0 +1,167 @@ +define(function(require) { + +'use strict'; + + +var wlxml = require('wlxml/wlxml'), + extension = {documentTransformations: [], classMethods: {}}; + + +extension.classMethods['list'] = { + itemIndex: function(listItem) { + var toret = -1; + this.contents('.item').some(function(item, index) { + if(item.sameNode(listItem)) { + toret = index; + return true; // break + } + }); + return toret; + }, + getItem: function(index) { + return this.contents('.item')[index]; + } +} + +extension.documentTransformations.push({ + name: 'createList', + impl: function(params) { + var parent = params.node1.parent(), + parentContents = parent.contents(), + nodeIndexes = [params.node1.getIndex(), params.node2.getIndex()].sort(), + nodesToWrap = [], + listNode = params.node1.document.createDocumentNode({tagName: 'div', attrs: {'class': 'list.items'}}), + node, i; + + for(i = nodeIndexes[0]; i <= nodeIndexes[1]; i++) { + node = parentContents[i]; + if(node.nodeType === Node.TEXT_NODE) { + node = node.wrapWith({tagName: 'div', attrs: {'class': 'item'}}); //t + } else { + node.setClass('item'); //t + } + nodesToWrap.push(node); + } + + var toInsert; + if(parent.is('list') && parent.object.itemIndex(nodesToWrap[0]) > 0) { // object api + // var prevItem = parent.object.getItem(parent.object.itemIndex(nodesToWrap[0])-1); // object api + // prevItem.append(listNode); //t + toInsert = listNode.wrapWith({tagName: 'div', attrs: {'class': 'item'}}); + } else { + //nodesToWrap[0].before(listNode); //t + toInsert = listNode; + } + + params.node1.before(toInsert); + + nodesToWrap.forEach(function(node) { + listNode.append(node); //t + }); + }, + getChangeRoot: function() { + return this.args.node1.parent(); + }, + isAllowed: function() { + return this.args.node1.parent().sameNode(this.args.node2.parent()); + } +}); + +extension.documentTransformations.push({ + name: 'extractItems', + impl: function(params) { + params = _.extend({}, {merge: true}, params); + var list = params.item1.parent(), + indexes = [params.item1.getIndex(), params.item2.getIndex()].sort(), + precedingItems = [], + extractedItems = [], + succeedingItems = [], + items = list.contents(), // lub list.object.items() + listIsNested = list.parent().is('item'), + i; + + items.forEach(function(item, idx) { + if(idx < indexes[0]) { + precedingItems.push(item); + } + else if(idx >= indexes[0] && idx <= indexes[1]) { + extractedItems.push(item); + } + else { + succeedingItems.push(item); + } + }); + + var reference = listIsNested ? list.parent() : list; + if(succeedingItems.length === 0) { + var reference_orig = reference; + extractedItems.forEach(function(item) { + reference.after(item); //t + reference = item; + if(!listIsNested) { + item.setClass(null); //t + } + }); + if(precedingItems.length === 0) + reference_orig.detach(); //t + } else if(precedingItems.length === 0) { + extractedItems.forEach(function(item) { + reference.before(item); //t + if(!listIsNested) { + item.setClass(null); //t + } + }); + } else { + extractedItems.forEach(function(item) { + reference.after(item); //t + if(!listIsNested) + item.setClass(null); //t + reference = item; + }); + var secondList = params.item1.document.createDocumentNode({tagName: 'div', attrs: {'class':'list'}}), + toAdd = secondList; + + if(listIsNested) { + toAdd = secondList.wrapWith({tagName: 'div', attrs: {'class':'item'}}); + } + succeedingItems.forEach(function(item) { + secondList.append(item); + }); + + reference.after(toAdd); + } + if(!params.merge && listIsNested) { + debugger; + return this.transform('extractItems', {item1: extractedItems[0], item2: extractedItems[extractedItems.length-1]}); + } + return true; + }, + isAllowed: function() { + var parent = this.args.nodel1.parent(); + return parent.is('list') && parent.sameNode(this.args.node2.parent()); + } +}); + +wlxml.registerExtension(extension); + +// wlxml.registerClassTransformation('list', { +// name: 'insertItem', +// impl: function(item) { +// if(!item.is('item')) { +// throw new Error ('...'); +// } +// this.append(item); +// } +// }); + +// wlxml.registerClassMethod('list', { +// name: 'items', +// impl: function() { +// var node = this; +// return this.contents(); +// } +// }); + +//a atrybuty? registerClassAttrs? E... lepiej registerClassExtension({methods, attibutes}) + +}); \ No newline at end of file diff --git a/src/wlxml/extensions/list/list.test.js b/src/wlxml/extensions/list/list.test.js new file mode 100644 index 0000000..f15d75b --- /dev/null +++ b/src/wlxml/extensions/list/list.test.js @@ -0,0 +1,417 @@ +define(function(require) { + +'use strict'; + +var chai = require('libs/chai'), + wlxml = require('wlxml/wlxml'), + expect = chai.expect, + $ = require('libs/jquery'), + lists = require('wlxml/extensions/list/list'); + + +var getDocumentFromXML = function(xml, options) { + return wlxml.WLXMLDocumentFromXML(xml, options || {}); +}; + +var removeEmptyTextNodes = function(xml) { + xml = $($.trim(xml)); + xml.find(':not(iframe)') + .addBack() + .contents() + .filter(function() {return this.nodeType === Node.TEXT_NODE;}) + .each(function() { + if(!this.data.length) { + $(this).remove(); + } + }); + return $('').append(xml).html(); +}; + + +describe.only('Lists extension', function() { + + describe('creating lists', function() { + it('allows creation of a list from existing sibling DocumentElements', function() { + var doc = getDocumentFromXML('
Alice
has
a
cat
'), + section = doc.root, + div1 = section.contents()[1], + textA = section.contents()[2]; + + doc.transform('createList', {node1: div1, node2: textA}); + + expect(section.contents().length).to.equal(3, 'section has three child nodes'); + + var child1 = section.contents()[0], + list = section.contents()[1], + child3 = section.contents()[2]; + + expect(child1.getText()).to.equal('Alice'); + expect(list.is('list')).to.equal(true, 'second child is a list'); + expect(list.contents().length).to.equal(2, 'list contains two elements'); + list.contents().forEach(function(child) { + expect(child.getClass()).to.equal('item', 'list childs have wlxml class of item'); + }); + expect(child3.contents()[0].getText()).to.equal('cat'); + }); + + it('allows creating nested list from existing sibling list items', function() { + var doc = getDocumentFromXML('\ +
\ +
\ +
A
\ +
B
\ +
C
\ +
D
\ +
\ +
'); + + var outerList = doc.root.contents('.list')[0], + itemB = outerList.contents('.item')[1], + itemC = outerList.contents('.item')[2]; + + + doc.transform('createList', {node1: itemB, node2: itemC}); + + var outerListItems = outerList.contents('.item'), + innerList = outerListItems[1].contents()[0]; + + var innerListItems = innerList.contents('.item'); + + expect(outerListItems.length).to.equal(3, 'outer list has three items'); + expect(outerListItems[0].contents()[0].getText()).to.equal('A', 'first outer item ok'); + expect(outerListItems[1].getClass()).to.equal('item', 'inner list is wrapped by item element'); + + expect(innerList.is('list')).to.equal(true, 'inner list created'); + expect(innerListItems.length).to.equal(2, 'inner list has two items'); + expect(innerListItems[0].contents()[0].getText()).to.equal('B', 'first inner item ok'); + expect(innerListItems[1].contents()[0].getText()).to.equal('C', 'second inner item ok'); + + expect(outerListItems[2].contents()[0].getText()).to.equal('D', 'last outer item ok'); + + }); + }); + + describe('extracting list items', function() { + it('creates two lists with extracted items in the middle if extracting from the middle of the list', function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
1
\ +
2
\ +
3
\ +
\ +
')), + list = doc.root.contents()[0], + item1 = list.contents()[1], + item2 = list.contents()[2]; + + doc.transform('extractItems', {item1: item1, item2: item2}); + + var section = doc.root, + list1 = section.contents()[0], + oldItem1 = section.contents()[1], + oldItem2 = section.contents()[2], + list2 = section.contents()[3]; + + expect(section.contents().length).to.equal(4, 'section contains two old items and two lists'); + + expect(list1.is('list')).to.equal(true, 'first section child is a list'); + expect(list1.contents().length).to.equal(1, 'first list has one child'); + expect(list1.contents()[0].contents()[0].getText()).to.equal('0', 'first item of the first list is a first item of the original list'); + + expect(oldItem1.contents()[0].getText()).to.equal('1', 'first item got extracted'); + expect(oldItem1.getClass() === '').to.equal(true, 'first extracted element has no wlxml class'); + + expect(oldItem2.contents()[0].getText()).to.equal('2', 'second item got extracted'); + expect(oldItem2.getClass() === '').to.equal(true, 'second extracted element has no wlxml class'); + + expect(list2.is('list')).to.equal(true, 'last section child is a list'); + expect(list2.contents().length).to.equal(1, 'second list has one child'); + expect(list2.contents()[0].contents()[0].getText()).to.equal('3', 'first item of the second list is a last item of the original list'); + }); + + it('puts extracted items above the list if starting item is the first one', function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
1
\ +
2
\ +
\ +
')), + list = doc.root.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1], + item3 = list.contents()[2]; + + doc.transform('extractItems', {item1: item1, item2: item2}); + + var section = doc.root, + oldItem1 = section.contents()[0], + oldItem2 = section.contents()[1], + newList = section.contents()[2]; + + expect(section.contents().length).to.equal(3, 'section has three children'); + expect(oldItem1.contents()[0].getText()).to.equal('0', 'first item extracted'); + expect(oldItem2.contents()[0].getText()).to.equal('1', 'second item extracted'); + expect(newList.is('list')).to.equal(true, 'list lies below extracted item'); + expect(newList.contents().length).to.equal(1, 'list has now one child'); + }); + + it('puts extracted items below the list if ending item is the last one', function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
1
\ +
2
\ +
\ +
')), + list = doc.root.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1], + item3 = list.contents()[2]; + + doc.transform('extractItems', {item1: item2, item2: item3}); + + var section = doc.root, + oldItem1 = section.contents()[1], + oldItem2 = section.contents()[2], + newList = section.contents()[0]; + + expect(section.contents().length).to.equal(3, 'section has three children'); + expect(oldItem1.contents()[0].getText()).to.equal('1', 'first item extracted'); + expect(oldItem2.contents()[0].getText()).to.equal('2', 'second item extracted'); + expect(newList.is('list')).to.equal(true, 'list lies above extracted item'); + expect(newList.contents().length).to.equal(1, 'list has now one child'); + }); + + it('removes list if all its items are extracted', function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
some item
\ +
some item 2
\ +
\ +
')), + list = doc.root.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1]; + + doc.transform('extractItems', {item1: item1, item2: item2}); + + var section = doc.root, + oldItem1 = section.contents()[0], + oldItem2 = section.contents()[1]; + + expect(section.contents().length).to.equal(2, 'section contains two children'); + expect(oldItem1.contents()[0].getText()).to.equal('some item'); + expect(oldItem2.contents()[0].getText()).to.equal('some item 2'); + }); + + it('creates two lists with extracted items in the middle if extracting from the middle of the list - nested case' , function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
1.3
\ +
\ +
\ +
2
\ +
\ +
')), + list = doc.root.contents()[0], + nestedList = list.contents()[1].contents()[0], + nestedListItem = nestedList.contents()[1]; + + doc.transform('extractItems', {item1: nestedListItem, item2: nestedListItem}); + + var section = doc.root, + list = section.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1], // + item3 = list.contents()[2], + item4 = list.contents()[3], // + item5 = list.contents()[4], + nestedList1 = item2.contents()[0], + nestedList2 = item4.contents()[0]; + + expect(list.contents().length).to.equal(5, 'top list has five items'); + + expect(item1.contents()[0].getText()).to.equal('0', 'first item ok'); + + expect(item2.getClass()).to.equal('item', 'first nested list is still wrapped in item element'); + expect(nestedList1.contents().length).to.equal(1, 'first nested list is left with one child'); + expect(nestedList1.contents()[0].contents()[0].getText()).to.equal('1.1', 'first nested list item left alone'); + + expect(item3.contents()[0].getText()).to.equal('1.2', 'third item ok'); + + expect(item4.getClass()).to.equal('item', 'second nested list is still wrapped in item element'); + expect(nestedList2.contents().length).to.equal(1, 'second nested list is left with one child'); + expect(nestedList2.contents()[0].contents()[0].getText()).to.equal('1.3', 'second nested list item left alone'); + + expect(item5.contents()[0].getText()).to.equal('2', 'last item ok'); + }); + + it('puts extracted items below the list if ending item is the last one - nested case' , function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
1.3
\ +
\ +
\ +
2
\ +
\ +
')), + list = doc.root.contents()[0], + nestedList = list.contents()[1].contents()[0], + nestedListItem1 = nestedList.contents()[1], + nestedListItem2 = nestedList.contents()[2]; + + doc.transform('extractItems', {item1: nestedListItem1, item2: nestedListItem2}); + + var section = doc.root, + list = section.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1], + item3 = list.contents()[2], + item4 = list.contents()[3], + item5 = list.contents()[4]; + nestedList = item2.contents()[0]; + + expect(list.contents().length).to.equal(5, 'top list has five items'); + expect(item1.contents()[0].getText()).to.equal('0', 'first item ok'); + expect(item2.getClass()).to.equal('item', 'nested list is still wrapped in item element'); + expect(nestedList.contents().length).to.equal(1, 'nested list is left with one child'); + expect(nestedList.contents()[0].contents()[0].getText()).to.equal('1.1', 'nested list item left alone'); + expect(item3.contents()[0].getText()).to.equal('1.2', 'third item ok'); + expect(item4.contents()[0].getText()).to.equal('1.3', 'fourth item ok'); + expect(item5.contents()[0].getText()).to.equal('2', 'fifth item ok'); + }); + + it('puts extracted items above the list if starting item is the first one - nested case' , function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
1.3
\ +
\ +
\ +
2
\ +
\ +
')), + list = doc.root.contents()[0], + nestedList = list.contents()[1].contents()[0], + nestedListItem1 = nestedList.contents()[0], + nestedListItem2 = nestedList.contents()[1]; + + doc.transform('extractItems', {item1: nestedListItem1, item2: nestedListItem2}); + + var section = doc.root, + list = section.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1], + item3 = list.contents()[2], + item4 = list.contents()[3], + item5 = list.contents()[4]; + nestedList = item4.contents()[0]; + + expect(list.contents().length).to.equal(5, 'top list has five items'); + expect(item1.contents()[0].getText()).to.equal('0', 'first item ok'); + expect(item2.contents()[0].getText()).to.equal('1.1', 'second item ok'); + expect(item3.contents()[0].getText()).to.equal('1.2', 'third item ok'); + + expect(item4.getClass()).to.equal('item', 'nested list is still wrapped in item element'); + expect(nestedList.contents().length).to.equal(1, 'nested list is left with one child'); + expect(nestedList.contents()[0].contents()[0].getText()).to.equal('1.3', 'nested list item left alone'); + expect(item5.contents()[0].getText()).to.equal('2', 'fifth item ok'); + }); + + it('removes list if all its items are extracted - nested case', function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
\ +
\ +
2
\ +
\ +
')), + list = doc.root.contents()[0], + nestedList = list.contents()[1].contents()[0], + nestedListItem1 = nestedList.contents()[0], + nestedListItem2 = nestedList.contents()[1]; + + doc.transform('extractItems', {item1: nestedListItem1, item2: nestedListItem2}); + + var section = doc.root, + list = section.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1], + item3 = list.contents()[2], + item4 = list.contents()[3]; + + expect(list.contents().length).to.equal(4, 'top list has four items'); + expect(item1.contents()[0].getText()).to.equal('0', 'first item ok'); + expect(item2.contents()[0].getText()).to.equal('1.1', 'second item ok'); + expect(item3.contents()[0].getText()).to.equal('1.2', 'third item ok'); + expect(item4.contents()[0].getText()).to.equal('2', 'fourth item ok'); + }); + + it('extracts items out of outer most list when merge flag is set to false', function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
\ +
\ +
2
\ +
\ +
')), + section = doc.root, + list = section.contents()[0], + nestedList = list.contents()[1].contents()[0], + nestedListItem = nestedList.contents()[0]; + + var test = doc.transform('extractItems', {item1: nestedListItem, item2: nestedListItem, merge: false}); + + expect(test).to.equal(true, 'extraction status ok'); + + var sectionContents = section.contents(), + extractedItem = sectionContents[1]; + + expect(sectionContents.length).to.equal(3, 'section has three children'); + expect(sectionContents[0].is('list')).to.equal(true, 'first child is a list'); + + expect(extractedItem.getTagName()).to.equal('div', 'extracted item is a wlxml div'); + expect(extractedItem.getClass()).to.equal('', 'extracted item has no wlxml class'); + expect(extractedItem.contents()[0].getText()).to.equal('1.1', 'extracted item ok'); + expect(sectionContents[2].is('list')).to.equal(true, 'second child is a list'); + }); + }); + +}); + +}); \ No newline at end of file diff --git a/src/wlxml/extensions/uri.js b/src/wlxml/extensions/uri.js new file mode 100644 index 0000000..ec8f939 --- /dev/null +++ b/src/wlxml/extensions/uri.js @@ -0,0 +1,10 @@ +define(function() { + +'use strict'; + +return { + className: 'uri', + attributes: {uri: {type: 'string'}} +}; + +}); \ No newline at end of file diff --git a/src/wlxml/wlxml.js b/src/wlxml/wlxml.js index e6ea26b..d2dbd30 100644 --- a/src/wlxml/wlxml.js +++ b/src/wlxml/wlxml.js @@ -21,9 +21,19 @@ AttributesList.prototype.keys = function() { return _.keys(this); }; +var installObject = function(instance, klass) { + var methods = classMethods[klass]; + if(methods) { + instance.object = Object.create(methods); + _.keys(methods).forEach(function(key) { + instance.object[key] = _.bind(instance.object[key], instance); + }); + } +} var WLXMLElementNode = function(nativeNode, document) { smartxml.ElementNode.call(this, nativeNode, document); + installObject(this, this.getClass()); }; WLXMLElementNode.prototype = Object.create(smartxml.ElementNode.prototype); @@ -32,7 +42,14 @@ $.extend(WLXMLElementNode.prototype, smartxml.ElementNode.prototype, { return this.getAttr('class') || ''; }, setClass: function(klass) { - return this.setAttr('class', klass); + var methods, object; + if(klass !== this.klass) { + installObject(this, klass); + return this.setAttr('class', klass); + } + }, + is: function(klass) { + return this.getClass().substr(0, klass.length) === klass; }, getMetaAttributes: function() { var toret = new AttributesList(), @@ -228,6 +245,8 @@ var wlxmlClasses = { } }; +var classMethods = {}; + return { WLXMLDocumentFromXML: function(xml, options) { options = _.extend({wlxmlClasses: wlxmlClasses}, options); @@ -236,7 +255,27 @@ return { WLXMLElementNodeFromXML: function(xml) { return this.WLXMLDocumentFromXML(xml).root; + }, + + registerExtension: function(extension) { + extension.documentTransformations.forEach(function(method) { + WLXMLDocument.prototype.transformations.register(transformations.createContextTransformation(method)); + }); + + _.pairs(extension.classMethods).forEach(function(pair) { + var className = pair[0], + methods = pair[1]; + _.pairs(methods).forEach(function(pair) { + var methodName = pair[0], + method = pair[1]; + classMethods[className] = classMethods[className] || {}; + classMethods[className][methodName] = method; + }); + + }); + } + }; }); \ No newline at end of file -- 2.20.1 From f5faecafd614e050a276953335634cc581871218 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Sun, 15 Dec 2013 22:32:22 +0100 Subject: [PATCH 07/16] wip: lists + uri extensions --- src/wlxml/extensions/list/list.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/wlxml/extensions/list/list.js b/src/wlxml/extensions/list/list.js index dd010a6..6cf875f 100644 --- a/src/wlxml/extensions/list/list.js +++ b/src/wlxml/extensions/list/list.js @@ -144,24 +144,4 @@ extension.documentTransformations.push({ wlxml.registerExtension(extension); -// wlxml.registerClassTransformation('list', { -// name: 'insertItem', -// impl: function(item) { -// if(!item.is('item')) { -// throw new Error ('...'); -// } -// this.append(item); -// } -// }); - -// wlxml.registerClassMethod('list', { -// name: 'items', -// impl: function() { -// var node = this; -// return this.contents(); -// } -// }); - -//a atrybuty? registerClassAttrs? E... lepiej registerClassExtension({methods, attibutes}) - }); \ No newline at end of file -- 2.20.1 From 8652ad3f4a86e5019404ac90be9c7325ad077920 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Mon, 2 Dec 2013 14:22:00 +0100 Subject: [PATCH 08/16] wip: blocking nested transformations from being pushed on undo stack --- src/smartxml/smartxml.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index fc9715b..aa9a7c5 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -565,6 +565,7 @@ var Document = function(xml) { this.loadXML(xml); this.undoStack = []; this.redoStack = []; + this._transformationLevel = 0; }; $.extend(Document.prototype, Backbone.Events, { @@ -752,8 +753,12 @@ $.extend(Document.prototype, Backbone.Events, { } } if(transformation) { + this._transformationLevel++; toret = transformation.run(); - this.undoStack.push(transformation); + if(this._transformationLevel === 1) { + this.undoStack.push(transformation); + } + this._transformationLevel--; console.log('clearing redo stack'); this.redoStack = []; return toret; -- 2.20.1 From 00ec27f6852f1dd85f4d90bbc3f1dbb84a0d7f8c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Mon, 2 Dec 2013 14:22:40 +0100 Subject: [PATCH 09/16] some ideas --- src/editor/plugins/core.js | 2 +- src/smartxml/smartxml.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/editor/plugins/core.js b/src/editor/plugins/core.js index d8b01c8..c4991f8 100644 --- a/src/editor/plugins/core.js +++ b/src/editor/plugins/core.js @@ -23,7 +23,7 @@ var breakContentTransformation = { }; -var breakContentAction = function(document, context) { +var breakContentAction = function(document, context) { //@ editor.getDocument(); editor.getContext('...') var textNode = context.cursor.currentNode; if(textNode) { var result, goto; diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index aa9a7c5..6cc3e33 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -395,6 +395,17 @@ ElementNode.prototype.transformations.register(transformations.createContextTran } })); +ElementNode.prototype.transformations.register(transformations.createContextTransformation({ + name: 'smartxml.setAttr2', + impl: function(args) { + this.prevAttr = this.getAttr(args.name); + this.setAttr(args.name, args.value); + }, + undo: function(args) { + this.setAttr(args.name, this.prevAttr); + } +})); + DocumentNode.prototype.transformations.register(transformations.createContextTransformation({ name: 'smartxml.wrapWith', getChangeRoot: function() { @@ -542,6 +553,10 @@ TextNode.prototype.transformations.register(transformations.createContextTransfo }, getChangeRoot: function() { return this.context.parent().parent(); + }, + isAllowed: function(args) { + var parent = this.parent(); + return !!(parent && parent.parent()); } })); -- 2.20.1 From 0f79f3ef275dc3e8400b4b517373795a463818dd Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 3 Dec 2013 10:15:07 +0100 Subject: [PATCH 10/16] wip: fixing smartxml undo/redo smoke tests --- src/smartxml/smartxml.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index d2ac4a9..8d0ca4b 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -820,7 +820,7 @@ describe('smartxml', function() { var doc = getDocumentFromXML('
Alice
'), span = doc.root.contents()[0]; - doc.transform('detach2', {node: span}); + span.transform('smartxml.detach'); doc.undo(); @@ -842,7 +842,7 @@ describe('smartxml', function() { var doc = getDocumentFromXML('
Alice has a cat.
'), span = doc.root.contents()[1]; - doc.transform('detach2', {node: span}); + span.transform('smartxml.detach'); doc.undo(); @@ -857,8 +857,8 @@ describe('smartxml', function() { a = doc.root.contents()[0], b = doc.root.contents()[1]; - doc.transform('detach2', {node: a}); - doc.transform('detach2', {node: b}); + a.transform('smartxml.detach'); + b.transform('smartxml.detach'); doc.undo(); doc.undo(); expect(doc.root.contents().length).to.equal(2); -- 2.20.1 From 20b9b567165ee10532a441f809a2a718b5b5e570 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 3 Dec 2013 14:49:29 +0100 Subject: [PATCH 11/16] wip: integrating lists, first extensions api approach --- src/editor/modules/data/data.js | 3 +- src/editor/modules/documentCanvas/commands.js | 48 ++++++++++++------- src/editor/modules/documentCanvas/nodes.less | 6 +-- src/wlxml/extensions/list/list.js | 9 +++- src/wlxml/extensions/list/list.test.js | 2 +- src/wlxml/wlxml.js | 2 + 6 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/editor/modules/data/data.js b/src/editor/modules/data/data.js index 7c9077f..556d9b5 100644 --- a/src/editor/modules/data/data.js +++ b/src/editor/modules/data/data.js @@ -1,7 +1,8 @@ define([ 'libs/jquery', './saveDialog', - 'wlxml/wlxml' + 'wlxml/wlxml', + 'wlxml/extensions/list/list' ], function($, saveDialog, wlxml) { diff --git a/src/editor/modules/documentCanvas/commands.js b/src/editor/modules/documentCanvas/commands.js index 8793b9c..df21cff 100644 --- a/src/editor/modules/documentCanvas/commands.js +++ b/src/editor/modules/documentCanvas/commands.js @@ -50,12 +50,17 @@ commands.register('unwrap-node', function(canvas) { parent1 = selectionStart.element.parent() || undefined, parent2 = selectionEnd.element.parent() || undefined; - if(canvas.list.areItemsOfTheSameList({element1: parent1, element2: parent2})) { - return; - // TODO - // var selectionAnchor = cursor.getSelectionAnchor(); - // canvas.list.extractItems({element1: parent1, element2: parent2}); - // canvas.setCurrentElement(selectionAnchor.element, {caretTo: selectionAnchor.offset}); + var selectionAnchor = cursor.getSelectionAnchor(), + node1 = parent1.data('wlxmlNode'), + node2 = parent2.data('wlxmlNode'), + doc = node1.document; + if(doc.areItemsOfSameList({node1: node1, node2: node2})) { + + + doc.transform('extractItems', {item1: node1, item2: node2}); + + //canvas.list.extractItems({element1: parent1, element2: parent2}); + canvas.setCurrentElement(selectionAnchor.element, {caretTo: selectionAnchor.offset}); } else if(!cursor.isSelecting()) { var nodeToUnwrap = cursor.getPosition().element.data('wlxmlNode'), parentNode = nodeToUnwrap.unwrap(); @@ -65,17 +70,22 @@ commands.register('unwrap-node', function(canvas) { } }); -// commands.register('wrap-node', function(canvas) { -// var cursor = canvas.getCursor(), -// selectionStart = cursor.getSelectionStart(), -// selectionEnd = cursor.getSelectionEnd(), -// parent1 = selectionStart.element.parent() || undefined, -// parent2 = selectionEnd.element.parent() || undefined; +commands.register('wrap-node', function(canvas) { + var cursor = canvas.getCursor(), + selectionStart = cursor.getSelectionStart(), + selectionEnd = cursor.getSelectionEnd(), + parent1 = selectionStart.element.parent() || undefined, + parent2 = selectionEnd.element.parent() || undefined; -// if(canvas.list.areItemsOfTheSameList({element1: parent1, element2: parent2})) { -// canvas.list.create({element1: parent1, element2: parent2}); -// } -// }); + var node1 = parent1.data('wlxmlNode'), + node2 = parent2.data('wlxmlNode'), + doc = node1.document; + + if(canvas.list.areItemsOfTheSameList({element1: parent1, element2: parent2})) { + //canvas.list.create({element1: parent1, element2: parent2}); + doc.transform('createList', {node1: node1, node2: node2}); + } +}); commands.register('list', function(canvas, params) { var cursor = canvas.getCursor(), @@ -90,7 +100,11 @@ commands.register('list', function(canvas, params) { return; } - canvas.list.create({element1: parent1, element2: parent2}); + var node1 = parent1.data('wlxmlNode'), + node2 = parent2.data('wlxmlNode'), + doc = node1.document; + + doc.transform('createList', {node1: node1, node2: node2}); canvas.setCurrentElement(selectionFocus.element, {caretTo: selectionFocus.offset}); }); diff --git a/src/editor/modules/documentCanvas/nodes.less b/src/editor/modules/documentCanvas/nodes.less index d02e872..ab26442 100644 --- a/src/editor/modules/documentCanvas/nodes.less +++ b/src/editor/modules/documentCanvas/nodes.less @@ -73,7 +73,7 @@ display:none; } -[wlxml-class="list-items"] { +[wlxml-class="list"] { [wlxml-class="item"] { display: list-item; @@ -83,13 +83,13 @@ } [wlxml-class="item"] { - [wlxml-class="list-items"] { + [wlxml-class="list"] { display: block; } } -[wlxml-class="list-items-enum"] { +[wlxml-class="list-enum"] { counter-reset: myitem; diff --git a/src/wlxml/extensions/list/list.js b/src/wlxml/extensions/list/list.js index 6cf875f..4291957 100644 --- a/src/wlxml/extensions/list/list.js +++ b/src/wlxml/extensions/list/list.js @@ -23,6 +23,12 @@ extension.classMethods['list'] = { } } +extension.documentMethods = { + areItemsOfSameList: function(params) { + return params.node1.parent().sameNode(params.node2.parent()) && params.node2.parent().is('list'); + } +} + extension.documentTransformations.push({ name: 'createList', impl: function(params) { @@ -30,7 +36,7 @@ extension.documentTransformations.push({ parentContents = parent.contents(), nodeIndexes = [params.node1.getIndex(), params.node2.getIndex()].sort(), nodesToWrap = [], - listNode = params.node1.document.createDocumentNode({tagName: 'div', attrs: {'class': 'list.items'}}), + listNode = params.node1.document.createDocumentNode({tagName: 'div', attrs: {'class': 'list'}}), node, i; for(i = nodeIndexes[0]; i <= nodeIndexes[1]; i++) { @@ -131,7 +137,6 @@ extension.documentTransformations.push({ reference.after(toAdd); } if(!params.merge && listIsNested) { - debugger; return this.transform('extractItems', {item1: extractedItems[0], item2: extractedItems[extractedItems.length-1]}); } return true; diff --git a/src/wlxml/extensions/list/list.test.js b/src/wlxml/extensions/list/list.test.js index f15d75b..1efa795 100644 --- a/src/wlxml/extensions/list/list.test.js +++ b/src/wlxml/extensions/list/list.test.js @@ -28,7 +28,7 @@ var removeEmptyTextNodes = function(xml) { }; -describe.only('Lists extension', function() { +describe('Lists extension', function() { describe('creating lists', function() { it('allows creation of a list from existing sibling DocumentElements', function() { diff --git a/src/wlxml/wlxml.js b/src/wlxml/wlxml.js index d2dbd30..dbc0cf0 100644 --- a/src/wlxml/wlxml.js +++ b/src/wlxml/wlxml.js @@ -274,6 +274,8 @@ return { }); + _.extend(WLXMLDocument.prototype, extension.documentMethods); + } }; -- 2.20.1 From b729636a8bfe248aa9890a6f3bef56cbbab885fd Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 3 Dec 2013 16:31:28 +0100 Subject: [PATCH 12/16] extensions api wip - 1 canvas test fails because of setting setText trans via old api --- src/editor/modules/data/data.js | 4 +- src/smartxml/smartxml.js | 5 +- src/smartxml/transformations.js | 5 +- src/wlxml/extensions/list/list.js | 23 +++-- src/wlxml/extensions/list/list.test.js | 6 +- src/wlxml/extensions/uri.js | 11 ++- src/wlxml/wlxml.js | 123 +++++++++++++++++++++++-- src/wlxml/wlxml.test.js | 93 +++++++++++++++++++ 8 files changed, 242 insertions(+), 28 deletions(-) diff --git a/src/editor/modules/data/data.js b/src/editor/modules/data/data.js index 556d9b5..f3494a0 100644 --- a/src/editor/modules/data/data.js +++ b/src/editor/modules/data/data.js @@ -4,7 +4,7 @@ define([ 'wlxml/wlxml', 'wlxml/extensions/list/list' -], function($, saveDialog, wlxml) { +], function($, saveDialog, wlxml, listExtension) { 'use strict'; @@ -16,6 +16,8 @@ return function(sandbox) { var history = sandbox.getBootstrappedData().history; var wlxmlDocument = wlxml.WLXMLDocumentFromXML(sandbox.getBootstrappedData().document); + + wlxmlDocument.registerExtension(listExtension); function readCookie(name) { diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 6cc3e33..bb47c06 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -581,6 +581,8 @@ var Document = function(xml) { this.undoStack = []; this.redoStack = []; this._transformationLevel = 0; + this.transformations = new transformations.TransformationStorage(); + }; $.extend(Document.prototype, Backbone.Events, { @@ -840,7 +842,8 @@ return { Document: Document, DocumentNode: DocumentNode, - ElementNode: ElementNode + ElementNode: ElementNode, + TextNode: TextNode }; }); \ No newline at end of file diff --git a/src/smartxml/transformations.js b/src/smartxml/transformations.js index 53dc384..8cf0819 100644 --- a/src/smartxml/transformations.js +++ b/src/smartxml/transformations.js @@ -95,10 +95,11 @@ toret.createContextTransformation = function(desc) { -toret.TransformationStorage = function() {}; +toret.TransformationStorage = function() { + this._transformations = {}; +}; _.extend(toret.TransformationStorage.prototype, { - _transformations: {}, register: function(Transformation) { var list = (this._transformations[Transformation.prototype.name] = this._transformations[Transformation.prototype.name] || []); diff --git a/src/wlxml/extensions/list/list.js b/src/wlxml/extensions/list/list.js index 4291957..75b5336 100644 --- a/src/wlxml/extensions/list/list.js +++ b/src/wlxml/extensions/list/list.js @@ -1,13 +1,11 @@ -define(function(require) { +define(function() { 'use strict'; +var extension = {document: {transformations: {}}, wlxmlClass: {list: {methods: {}}}}; -var wlxml = require('wlxml/wlxml'), - extension = {documentTransformations: [], classMethods: {}}; - -extension.classMethods['list'] = { +extension.wlxmlClass.list.methods = { itemIndex: function(listItem) { var toret = -1; this.contents('.item').some(function(item, index) { @@ -29,8 +27,9 @@ extension.documentMethods = { } } -extension.documentTransformations.push({ - name: 'createList', + + +extension.document.transformations.createList = { impl: function(params) { var parent = params.node1.parent(), parentContents = parent.contents(), @@ -71,10 +70,9 @@ extension.documentTransformations.push({ isAllowed: function() { return this.args.node1.parent().sameNode(this.args.node2.parent()); } -}); +}; -extension.documentTransformations.push({ - name: 'extractItems', +extension.document.transformations.extractItems = { impl: function(params) { params = _.extend({}, {merge: true}, params); var list = params.item1.parent(), @@ -145,8 +143,9 @@ extension.documentTransformations.push({ var parent = this.args.nodel1.parent(); return parent.is('list') && parent.sameNode(this.args.node2.parent()); } -}); +}; + +return extension; -wlxml.registerExtension(extension); }); \ No newline at end of file diff --git a/src/wlxml/extensions/list/list.test.js b/src/wlxml/extensions/list/list.test.js index 1efa795..10a6e91 100644 --- a/src/wlxml/extensions/list/list.test.js +++ b/src/wlxml/extensions/list/list.test.js @@ -6,11 +6,13 @@ var chai = require('libs/chai'), wlxml = require('wlxml/wlxml'), expect = chai.expect, $ = require('libs/jquery'), - lists = require('wlxml/extensions/list/list'); + listsExtension = require('wlxml/extensions/list/list'); var getDocumentFromXML = function(xml, options) { - return wlxml.WLXMLDocumentFromXML(xml, options || {}); + var doc = wlxml.WLXMLDocumentFromXML(xml, options || {}); + doc.registerExtension(listsExtension); + return doc; }; var removeEmptyTextNodes = function(xml) { diff --git a/src/wlxml/extensions/uri.js b/src/wlxml/extensions/uri.js index ec8f939..88e3f66 100644 --- a/src/wlxml/extensions/uri.js +++ b/src/wlxml/extensions/uri.js @@ -2,9 +2,12 @@ define(function() { 'use strict'; -return { - className: 'uri', - attributes: {uri: {type: 'string'}} -}; +var extenstion = {wlxmlClass: {uri: attributes: { + { + uri: {type: 'string'} + } +}}} + +return extension; }); \ No newline at end of file diff --git a/src/wlxml/wlxml.js b/src/wlxml/wlxml.js index dbc0cf0..ee85a03 100644 --- a/src/wlxml/wlxml.js +++ b/src/wlxml/wlxml.js @@ -22,9 +22,19 @@ AttributesList.prototype.keys = function() { }; var installObject = function(instance, klass) { - var methods = classMethods[klass]; + var methods = instance.document.classMethods[klass]; if(methods) { - instance.object = Object.create(methods); + instance.object = Object.create(_.extend({ + transform: function(name, args) { + // TODO: refactor with DocumentElement.transform + var Transformation = instance.document.classTransformations[klass].get(name), + transformation; + if(Transformation) { + transformation = new Transformation(instance.document, instance, args); + } + return instance.document.transform(transformation); + } + }, methods)); _.keys(methods).forEach(function(key) { instance.object[key] = _.bind(instance.object[key], instance); }); @@ -139,17 +149,43 @@ WLXMLElementNode.prototype.transformations.register(transformations.createContex +var WLXMLDocumentNode = function() { + smartxml.DocumentNode.apply(this, arguments); +} +WLXMLDocumentNode.prototype = Object.create(smartxml.DocumentNode.prototype); + var WLXMLDocument = function(xml, options) { smartxml.Document.call(this, xml); this.options = options; + + // this.DocumentNodeFactory = function() { + // WLXMLDocumentNode.apply(this, arguments); + // }; + + // this.DocumentNodeFactory.prototype = Object.create(WLXMLDocumentNode.prototype); + + this.ElementNodeFactory = function() { + WLXMLElementNode.apply(this, arguments); + } + this.ElementNodeFactory.prototype = Object.create(WLXMLElementNode.prototype); + this.ElementNodeFactory.prototype.transformations = new transformations.TransformationStorage(); + + this.TextNodeFactory = function() { + smartxml.TextNode.apply(this, arguments); + } + this.TextNodeFactory.prototype = Object.create(smartxml.TextNode.prototype); + this.TextNodeFactory.prototype.transformations = new transformations.TransformationStorage(); + + this.classMethods = {}; + this.classTransformations = {}; }; var formatter_prefix = '_wlxml_formatter_'; + WLXMLDocument.prototype = Object.create(smartxml.Document.prototype); $.extend(WLXMLDocument.prototype, { ElementNodeFactory: WLXMLElementNode, - loadXML: function(xml) { smartxml.Document.prototype.loadXML.call(this, xml, {silent: true}); $(this.dom).find(':not(iframe)').addBack().contents() @@ -235,6 +271,78 @@ $.extend(WLXMLDocument.prototype, { el.replaceWith(document.createTextNode(text.transformed)); }); this.trigger('contentSet'); + }, + + registerExtension: function(extension) { + //debugger; + var doc = this, + existingPropertyName = _.values(this); + + [ + {source: extension.document, target: doc}, + {source: extension.documentNode, target: [doc.ElementNodeFactory.prototype, doc.TextNodeFactory.prototype]}, + + ].forEach(function(x) { + if(x.source && x.source.methods) { + existingPropertyName = _.values(x.target) + _.pairs(x.source.methods).forEach(function(pair) { + var methodName = pair[0], + method = pair[1], + targets = _.isArray(x.target) ? x.target : [x.target]; + if(_.contains(existingPropertyName, methodName)) { + throw new Error('Cannot extend XXX with method name {methodName}. Name already exists.'.replace('{methodName}', methodName)); + } + targets.forEach(function(target) { + target[methodName] = method; + }); + + }); + } + }); + + + var getTrans = function(desc, methodName) { + if(typeof desc === 'function') { + desc = {impl: desc}; + } + if(!desc.impl) { + throw new Error('Got transformation description without implementation.') + } + desc.name = desc.name || methodName; + return desc; + }; + + if(extension.document && extension.document.transformations) { + _.pairs(extension.document.transformations).forEach(function(pair) { + var transformation = getTrans(pair[1], pair[0]); + doc.transformations.register(transformations.createContextTransformation(transformation)); + }); + } + + if(extension.documentNode && extension.documentNode.transformations) { + _.pairs(extension.documentNode.transformations).forEach(function(pair) { + var transformation = getTrans(pair[1], pair[0]); + + doc.ElementNodeFactory.prototype.transformations.register(transformations.createContextTransformation(transformation)); + doc.TextNodeFactory.prototype.transformations.register(transformations.createContextTransformation(transformation)); + }); + } + + _.pairs(extension.wlxmlClass).forEach(function(pair) { + var className = pair[0], + classExtension = pair[1], + thisClassMethods = (doc.classMethods[className] = doc.classMethods[className] || {}), + thisClassTransformations = (doc.classTransformations[className] = doc.classTransformations[className] || new transformations.TransformationStorage()); + + _.extend(thisClassMethods, classExtension.methods || {}); //@ warning/throw on override? + + + _.pairs(classExtension.transformations || {}).forEach(function(pair) { + var transformation = getTrans(pair[1], pair[0]); + thisClassTransformations.register(transformations.createContextTransformation(transformation)); + }); + }); + } }); @@ -258,9 +366,12 @@ return { }, registerExtension: function(extension) { - extension.documentTransformations.forEach(function(method) { - WLXMLDocument.prototype.transformations.register(transformations.createContextTransformation(method)); - }); + // @@ depracated + if(extension.documentTransformations) { + extension.documentTransformations.forEach(function(method) { + WLXMLDocument.prototype.transformations.register(transformations.createContextTransformation(method)); + }); + } _.pairs(extension.classMethods).forEach(function(pair) { var className = pair[0], diff --git a/src/wlxml/wlxml.test.js b/src/wlxml/wlxml.test.js index c802ae3..e2c11fa 100644 --- a/src/wlxml/wlxml.test.js +++ b/src/wlxml/wlxml.test.js @@ -251,6 +251,99 @@ describe('WLXMLDocument', function() { }); + describe('Extension', function() { + var doc, extension, elementNode, textNode, testClassNode; + + beforeEach(function() { + doc = getDocumentFromXML('
Alice
'); + elementNode = doc.root; + textNode = doc.root.contents()[0]; + extension = {}; + + console.log('A'); + expect(function() { + elementNode.transform('testTransformation'); + }).to.throw(Error); + console.log('B'); + expect(function() { + textNode.transform('testTransformation'); + }).to.throw(Error); + console.log('C'); + expect(function() { + doc.transform('testTransformation'); + }).to.throw(Error); + expect(doc.testMethod).to.be.undefined; + expect(elementNode.testMethod).to.be.undefined; + expect(textNode.testMethod).to.be.undefined; + }); + + it('allows adding method to a document', function() { + extension = {document: {methods: { + testMethod: function() { return this; } + }}}; + + doc.registerExtension(extension); + expect(doc.testMethod()).to.equal(doc, 'context is set to a document instance'); + }); + + it('allows adding transformation to a document', function() { + extension = {document: {transformations: { + testTransformation: function() { return this; }, + testTransformation2: {impl: function() { return this;}} + }}}; + + doc.registerExtension(extension); + expect(doc.transform('testTransformation')).to.equal(doc, 'context is set to a document instance'); + expect(doc.transform('testTransformation2')).to.equal(doc, 'context is set to a document instance'); + }); + + it('allows adding method to a DocumentNode instance', function() { + extension = {documentNode: {methods: { + testMethod: function() { return this; } + }}}; + + doc.registerExtension(extension); + expect(elementNode.testMethod().sameNode(elementNode)).to.equal(true, 'context is set to a node instance'); + expect(textNode.testMethod().sameNode(textNode)).to.equal(true, 'context is set to a node instance'); + }); + + it('allows adding transformation to a DocumentNode', function() { + extension = {documentNode: {transformations: { + testTransformation: function() { return this; }, + testTransformation2: {impl: function() { return this;}} + }}}; + + doc.registerExtension(extension); + + expect(elementNode.transform('testTransformation').sameNode(elementNode)).to.equal(true, '1'); + expect(elementNode.transform('testTransformation2').sameNode(elementNode)).to.equal(true, '2'); + expect(textNode.transform('testTransformation').sameNode(textNode)).to.equal(true, '3'); + expect(textNode.transform('testTransformation2').sameNode(textNode)).to.equal(true, '4'); + }); + + it('allows adding method to an ElementNode of specific class', function() { + extension = {wlxmlClass: {test_class: {methods: { + testMethod: function() { return this; } + }}}}; + doc.registerExtension(extension); + testClassNode = doc.root.contents()[1]; + expect(testClassNode.object.testMethod().sameNode(testClassNode)).to.equal(true, '1'); + }); + + it('allows adding transformation to an ElementNode of specific class', function() { + extension = {wlxmlClass: {test_class: {transformations: { + testTransformation: function() { return this; }, + testTransformation2: {impl: function() { return this; }} + }}}}; + doc.registerExtension(extension); + testClassNode = doc.root.contents()[1]; + expect(testClassNode.object.transform('testTransformation').sameNode(testClassNode)).to.equal(true, '1'); + expect(testClassNode.object.transform('testTransformation2').sameNode(testClassNode)).to.equal(true, '1'); + }); + + + }); + }); }); \ No newline at end of file -- 2.20.1 From 2fbd2cb6926b1066b38adf0698a3679d089ad80c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 3 Dec 2013 16:56:59 +0100 Subject: [PATCH 13/16] wip: removing console calls --- src/editor/modules/documentCanvas/canvas/canvas.js | 2 +- src/editor/modules/documentCanvas/canvas/wlxmlListener.js | 2 +- src/smartxml/smartxml.js | 6 +++--- src/smartxml/transformations.js | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/editor/modules/documentCanvas/canvas/canvas.js b/src/editor/modules/documentCanvas/canvas/canvas.js index ae75d61..d13e2ea 100644 --- a/src/editor/modules/documentCanvas/canvas/canvas.js +++ b/src/editor/modules/documentCanvas/canvas/canvas.js @@ -13,7 +13,7 @@ define([ var TextHandler = function(canvas) {this.canvas = canvas; this.buffer = null}; $.extend(TextHandler.prototype, { handle: function(node, text) { - console.log('canvas text handler: ' + text); + //console.log('canvas text handler: ' + text); this.setText(text, node); return; if(!this.node) { diff --git a/src/editor/modules/documentCanvas/canvas/wlxmlListener.js b/src/editor/modules/documentCanvas/canvas/wlxmlListener.js index ffabfec..a98397f 100644 --- a/src/editor/modules/documentCanvas/canvas/wlxmlListener.js +++ b/src/editor/modules/documentCanvas/canvas/wlxmlListener.js @@ -68,7 +68,7 @@ var handlers = { canvasNode.detach(); }, nodeTextChange: function(event) { - console.log('wlxmlListener: ' + event.meta.node.getText()); + //console.log('wlxmlListener: ' + event.meta.node.getText()); var canvasElement = utils.findCanvasElement(event.meta.node), toSet = event.meta.node.getText(); if(toSet === '') { diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index bb47c06..4a0cd7a 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -451,7 +451,7 @@ $.extend(TextNode.prototype, { }, setText: function(text) { - console.log('smartxml: ' + text); + //console.log('smartxml: ' + text); this.nativeNode.data = text; this.triggerTextChangeEvent(); }, @@ -761,7 +761,7 @@ $.extend(Document.prototype, Backbone.Events, { }, transform: function(transformation, args) { - console.log('transform'); + //console.log('transform'); var Transformation, toret; if(typeof transformation === 'string') { Transformation = this.transformations.get(transformation); @@ -776,7 +776,7 @@ $.extend(Document.prototype, Backbone.Events, { this.undoStack.push(transformation); } this._transformationLevel--; - console.log('clearing redo stack'); + //console.log('clearing redo stack'); this.redoStack = []; return toret; } else { diff --git a/src/smartxml/transformations.js b/src/smartxml/transformations.js index 8cf0819..6d72049 100644 --- a/src/smartxml/transformations.js +++ b/src/smartxml/transformations.js @@ -19,10 +19,10 @@ toret.createGenericTransformation = function(desc) { Object.defineProperty(transformation.args, key, { get: function() { if(transformation.hasRun) { - console.log('returning via path'); + //console.log('returning via path'); return transformation.document.getNodeByPath(path); } else { - console.log('returning original arg'); + //console.log('returning original arg'); return value; } -- 2.20.1 From 2a317c49f0b5110cc3717878a4f988aba8c63c5e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 3 Dec 2013 23:41:47 +0100 Subject: [PATCH 14/16] smartxml: fix - do not send nodeDetached event for out of document node This fixes the case where new node, which wasn't inserted into document tree yet was moved (via append/prepend/before/after) into another out of document node which was triggering nodeDetached event. --- src/smartxml/smartxml.js | 7 ++++--- src/smartxml/smartxml.test.js | 13 +++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 4a0cd7a..8110a97 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -15,12 +15,13 @@ var TEXT_NODE = Node.TEXT_NODE; var INSERTION = function(implementation) { var toret = function(node) { var insertion = this.getNodeInsertion(node), + nodeWasContained = this.document.containsNode(insertion.ofNode), nodeParent; if(!(this.document.containsNode(this))) { nodeParent = insertion.ofNode.parent(); } implementation.call(this, insertion.ofNode.nativeNode); - this.triggerChangeEvent(insertion.insertsNew ? 'nodeAdded' : 'nodeMoved', {node: insertion.ofNode}, nodeParent); + this.triggerChangeEvent(insertion.insertsNew ? 'nodeAdded' : 'nodeMoved', {node: insertion.ofNode}, nodeParent, nodeWasContained); return insertion.ofNode; }; return toret; @@ -180,13 +181,13 @@ $.extend(DocumentNode.prototype, { } }, - triggerChangeEvent: function(type, metaData, origParent) { + triggerChangeEvent: function(type, metaData, origParent, nodeWasContained) { var node = (metaData && metaData.node) ? metaData.node : this, event = new events.ChangeEvent(type, $.extend({node: node}, metaData || {})); if(type === 'nodeDetached' || this.document.containsNode(event.meta.node)) { this.document.trigger('change', event); } - if((type === 'nodeAdded' || type === 'nodeMoved') && !(this.document.containsNode(this))) { + if((type === 'nodeAdded' || type === 'nodeMoved') && !this.document.containsNode(this) && nodeWasContained) { event = new events.ChangeEvent('nodeDetached', {node: node, parent: origParent}); this.document.trigger('change', event); } diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 8d0ca4b..c7cb6ad 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -734,6 +734,19 @@ describe('smartxml', function() { expect(event.type).to.equal('nodeDetached'); expect(event.meta.node.sameNode(a)); }); + + it('doesn\'t emit nodeDetached event for already out of document moved to out of document node: ' + insertionMethod, function() { + var doc = getDocumentFromXML('
'), + a = doc.root.contents()[0], + spy = sinon.spy(); + + doc.on('change', spy); + + var newNode = doc.createDocumentNode({tagName: 'b'}); + var newNodeInner = newNode.append({tagName:'c'}); + + expect(spy.callCount).to.equal(0); + }); }); -- 2.20.1 From 26108cb6ddfa48f5b1530ef035ba38c3c117d9ad Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 4 Dec 2013 11:35:18 +0100 Subject: [PATCH 15/16] Refactoring in WLXMLDocument.registerExtension --- src/wlxml/wlxml.js | 73 ++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/src/wlxml/wlxml.js b/src/wlxml/wlxml.js index ee85a03..01ab869 100644 --- a/src/wlxml/wlxml.js +++ b/src/wlxml/wlxml.js @@ -276,30 +276,7 @@ $.extend(WLXMLDocument.prototype, { registerExtension: function(extension) { //debugger; var doc = this, - existingPropertyName = _.values(this); - - [ - {source: extension.document, target: doc}, - {source: extension.documentNode, target: [doc.ElementNodeFactory.prototype, doc.TextNodeFactory.prototype]}, - - ].forEach(function(x) { - if(x.source && x.source.methods) { - existingPropertyName = _.values(x.target) - _.pairs(x.source.methods).forEach(function(pair) { - var methodName = pair[0], - method = pair[1], - targets = _.isArray(x.target) ? x.target : [x.target]; - if(_.contains(existingPropertyName, methodName)) { - throw new Error('Cannot extend XXX with method name {methodName}. Name already exists.'.replace('{methodName}', methodName)); - } - targets.forEach(function(target) { - target[methodName] = method; - }); - - }); - } - }); - + existingPropertyNames = _.values(this); var getTrans = function(desc, methodName) { if(typeof desc === 'function') { @@ -312,21 +289,41 @@ $.extend(WLXMLDocument.prototype, { return desc; }; - if(extension.document && extension.document.transformations) { - _.pairs(extension.document.transformations).forEach(function(pair) { - var transformation = getTrans(pair[1], pair[0]); - doc.transformations.register(transformations.createContextTransformation(transformation)); - }); - } + [ + {source: extension.document, target: doc}, + {source: extension.documentNode, target: [doc.ElementNodeFactory.prototype, doc.TextNodeFactory.prototype]}, - if(extension.documentNode && extension.documentNode.transformations) { - _.pairs(extension.documentNode.transformations).forEach(function(pair) { - var transformation = getTrans(pair[1], pair[0]); - - doc.ElementNodeFactory.prototype.transformations.register(transformations.createContextTransformation(transformation)); - doc.TextNodeFactory.prototype.transformations.register(transformations.createContextTransformation(transformation)); - }); - } + ].forEach(function(mapping) { + if(mapping.source) { + if(mapping.source.methods) { + existingPropertyNames = _.values(mapping.target) + _.pairs(mapping.source.methods).forEach(function(pair) { + var methodName = pair[0], + method = pair[1], + targets = _.isArray(mapping.target) ? mapping.target : [mapping.target]; + if(_.contains(existingPropertyNames, methodName)) { + throw new Error('Cannot extend {target} with method name {methodName}. Name already exists.' + .replace('{target}', mapping.target) + .replace('{methodName}', methodName) + ); + } + targets.forEach(function(target) { + target[methodName] = method; + }); + }); + } + + if(mapping.source.transformations) { + _.pairs(mapping.source.transformations).forEach(function(pair) { + var transformation = getTrans(pair[1], pair[0]), + targets = _.isArray(mapping.target) ? mapping.target : [mapping.target]; + targets.forEach(function(target) { + target.transformations.register(transformations.createContextTransformation(transformation)); + }); + }); + } + } + }); _.pairs(extension.wlxmlClass).forEach(function(pair) { var className = pair[0], -- 2.20.1 From ea602146cf83203fe89e24dcba3f0b055577e88c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 4 Dec 2013 12:51:31 +0100 Subject: [PATCH 16/16] Refactoring extension registration - abstracting away registration on objects --- src/wlxml/wlxml.js | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/wlxml/wlxml.js b/src/wlxml/wlxml.js index 01ab869..98d6a10 100644 --- a/src/wlxml/wlxml.js +++ b/src/wlxml/wlxml.js @@ -22,7 +22,7 @@ AttributesList.prototype.keys = function() { }; var installObject = function(instance, klass) { - var methods = instance.document.classMethods[klass]; + var methods = instance.document.classMethods[klass] || instance.document.classTransformations; if(methods) { instance.object = Object.create(_.extend({ transform: function(name, args) { @@ -169,12 +169,18 @@ var WLXMLDocument = function(xml, options) { } this.ElementNodeFactory.prototype = Object.create(WLXMLElementNode.prototype); this.ElementNodeFactory.prototype.transformations = new transformations.TransformationStorage(); + this.ElementNodeFactory.prototype.registerTransformation = function(Transformation) { + return this.transformations.register(Transformation); + }; this.TextNodeFactory = function() { smartxml.TextNode.apply(this, arguments); } this.TextNodeFactory.prototype = Object.create(smartxml.TextNode.prototype); this.TextNodeFactory.prototype.transformations = new transformations.TransformationStorage(); + this.TextNodeFactory.prototype.registerTransformation = function(Transformation) { + return this.transformations.register(Transformation); + }; this.classMethods = {}; this.classTransformations = {}; @@ -273,6 +279,20 @@ $.extend(WLXMLDocument.prototype, { this.trigger('contentSet'); }, + registerTransformation: function(Transformation) { + return this.transformations.register(Transformation); + }, + + registerClassTransformation: function(Transformation, className) { + var thisClassTransformations = (this.classTransformations[className] = this.classTransformations[className] || new transformations.TransformationStorage()); + return thisClassTransformations.register(Transformation); + }, + + registerClassMethod: function(methodName, method, className) { + var thisClassMethods = (this.classMethods[className] = this.classMethods[className] || {}); + thisClassMethods[methodName] = method; + }, + registerExtension: function(extension) { //debugger; var doc = this, @@ -318,7 +338,7 @@ $.extend(WLXMLDocument.prototype, { var transformation = getTrans(pair[1], pair[0]), targets = _.isArray(mapping.target) ? mapping.target : [mapping.target]; targets.forEach(function(target) { - target.transformations.register(transformations.createContextTransformation(transformation)); + target.registerTransformation(transformations.createContextTransformation(transformation)); }); }); } @@ -327,16 +347,17 @@ $.extend(WLXMLDocument.prototype, { _.pairs(extension.wlxmlClass).forEach(function(pair) { var className = pair[0], - classExtension = pair[1], - thisClassMethods = (doc.classMethods[className] = doc.classMethods[className] || {}), - thisClassTransformations = (doc.classTransformations[className] = doc.classTransformations[className] || new transformations.TransformationStorage()); - - _.extend(thisClassMethods, classExtension.methods || {}); //@ warning/throw on override? - + classExtension = pair[1]; + + _.pairs(classExtension.methods || {}).forEach(function(pair) { + var name = pair[0], + method = pair[1]; + doc.registerClassMethod(name, method, className); + }); _.pairs(classExtension.transformations || {}).forEach(function(pair) { var transformation = getTrans(pair[1], pair[0]); - thisClassTransformations.register(transformations.createContextTransformation(transformation)); + doc.registerClassTransformation(transformations.createContextTransformation(transformation), className); }); }); -- 2.20.1