dzialacy remove+text
[fnpeditor.git] / src / smartxml / smartxml.js
index 9f060e8..7b59b18 100644 (file)
@@ -13,14 +13,26 @@ var TEXT_NODE = Node.TEXT_NODE;
 
 var INSERTION = function(implementation) {
     var toret = function(node) {
-        var insertion = this.getNodeInsertion(node);
+        var insertion = this.getNodeInsertion(node),
+            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});
+        this.triggerChangeEvent(insertion.insertsNew ? 'nodeAdded' : 'nodeMoved', {node: insertion.ofNode}, nodeParent);
         return insertion.ofNode;
     };
     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');
@@ -37,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) {
@@ -155,11 +175,16 @@ $.extend(DocumentNode.prototype, {
         }
     },
 
-    triggerChangeEvent: function(type, metaData) {
-        var event = new events.ChangeEvent(type, $.extend({node: this}, metaData || {}));
+    triggerChangeEvent: function(type, metaData, origParent) {
+        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))) {
+             event = new events.ChangeEvent('nodeDetached', {node: node, parent: origParent});
+             this.document.trigger('change', event);
+        }
     },
     
     getNodeInsertion: function(node) {
@@ -272,6 +297,15 @@ $.extend(ElementNode.prototype, {
         this._$.prepend(nativeNode);
     }),
 
+    insertAtIndex: function(nativeNode, index) {
+        var contents = this.contents();
+        if(index < contents.length) {
+            return contents[index].before(nativeNode);
+        } else if(index === contents.length) {
+            return this.append(nativeNode);
+        }
+    },
+
     unwrapContent: function() {
         var parent = this.parent();
         if(!parent) {
@@ -349,6 +383,7 @@ $.extend(TextNode.prototype, {
     },
 
     setText: function(text) {
+        console.log('smartxml: ' + text);
         this.nativeNode.data = text;
         this.triggerTextChangeEvent();
     },
@@ -428,6 +463,8 @@ var parseXML = function(xml) {
 
 var Document = function(xml) {
     this.loadXML(xml);
+    this.undoStack = [];
+    this.redoStack = [];
 };
 
 $.extend(Document.prototype, Backbone.Events, {
@@ -602,6 +639,43 @@ $.extend(Document.prototype, Backbone.Events, {
         defineDocumentProperties(this, insertion.ofNode._$);
         insertion.ofNode.triggerChangeEvent('nodeAdded');
         return insertion.ofNode;
+    },
+
+    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!');
+        }
+    },
+    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;
     }
 });
 
@@ -614,6 +688,185 @@ 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.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;
+}
+
+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
+
+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;
+
 return {
     documentFromXML: function(xml) {
         return new Document(xml);