From: Aleksander Łukasz Date: Fri, 29 Nov 2013 15:26:49 +0000 (+0100) Subject: wip: breakContent & others transformations - still need to confirm the api X-Git-Url: https://git.mdrn.pl/fnpeditor.git/commitdiff_plain/8464a511c74dff643095ce419659df60b0580b7a wip: breakContent & others transformations - still need to confirm the api --- 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; + } +}));