smartxml: implementing merging of inserted text nodes if necessary
[fnpeditor.git] / src / smartxml / core.js
index dd472ce..ae6d20e 100644 (file)
@@ -1,22 +1,27 @@
-define([
-
-], function() {
+define(function(require) {
     
 'use strict';
 /* globals Node */
-var TEXT_NODE = Node.TEXT_NODE;
+
+var _ = require('libs/underscore'),
+    TEXT_NODE = Node.TEXT_NODE;
+
 
 var INSERTION = function(implementation) {
-    var toret = function(node) {
+    var toret = function(node, options) {
         var insertion = this.getNodeInsertion(node),
             nodeWasContained = this.document.containsNode(insertion.ofNode),
-            nodeParent;
+            nodeParent,
+            returned;
+        options = options || {};
         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, nodeWasContained);
-        return insertion.ofNode;
+        returned = implementation.call(this, insertion.ofNode);
+        if(!options.silent && returned.sameNode(insertion.ofNode)) {
+            this.triggerChangeEvent(insertion.insertsNew ? 'nodeAdded' : 'nodeMoved', {node: insertion.ofNode}, nodeParent, nodeWasContained);
+        }
+        return returned;
     };
     return toret;
 };
@@ -39,12 +44,24 @@ var documentNodeTransformations = {
         return toret;
     },
 
-    after: INSERTION(function(nativeNode) {
-        return this._$.after(nativeNode);
+    after: INSERTION(function(node) {
+        var next = this.next();
+        if(next && next.nodeType === Node.TEXT_NODE && node.nodeType === Node.TEXT_NODE) {
+            next.setText(node.getText() + next.getText());
+            return next;
+        }
+        this._$.after(node.nativeNode);
+        return node;
     }),
 
-    before: INSERTION(function(nativeNode) {
-        return this._$.before(nativeNode);
+    before: INSERTION(function(node) {
+        var prev = this.prev();
+        if(prev && prev.nodeType === Node.TEXT_NODE && node.nodeType === Node.TEXT_NODE) {
+            prev.setText(prev.getText() + node.getText());
+            return prev;
+        }
+        this._$.before(node.nativeNode);
+        return node;
     }),
 
     wrapWith: function(node) {
@@ -98,6 +115,8 @@ var elementNodeTransformations = {
         if(this.sameNode(this.document.root)) {
             this.document._defineDocumentProperties(node._$);
         }
+
+        /* TODO: This invalidates old references to this node. Caching instances on nodes would fix this. */
         this._$.replaceWith(node._$);
         this._setNativeNode(node._$[0]);
         this._$.append(myContents);
@@ -113,12 +132,26 @@ var elementNodeTransformations = {
         }
     },
 
-    append: INSERTION(function(nativeNode) {
-        this._$.append(nativeNode);
+    append: INSERTION(function(node) {
+        var last = _.last(this.contents());
+        if(last && last.nodeType === Node.TEXT_NODE && node.nodeType === Node.TEXT_NODE) {
+            last.setText(last.getText() + node.getText());
+            return last;
+        } else {
+            this._$.append(node.nativeNode);
+            return node;
+        }
     }),
 
-    prepend: INSERTION(function(nativeNode) {
-        this._$.prepend(nativeNode);
+    prepend: INSERTION(function(node) {
+        var first = this.contents()[0];
+        if(first && first.nodeType === Node.TEXT_NODE && node.nodeType === Node.TEXT_NODE) {
+            first.setText(node.getText() + first.getText());
+            return first;
+        } else {
+            this._$.prepend(node.nativeNode);
+            return node;
+        }
     }),
 
     insertAtIndex: function(nativeNode, index) {
@@ -185,12 +218,35 @@ var elementNodeTransformations = {
 };
 
 var textNodeTransformations = {
-    setText: function(text) {
-        //console.log('smartxml: ' + text);
-        this.nativeNode.data = text;
-        this.triggerTextChangeEvent();
+    setText: {
+        impl: function(t, text) {
+            t.oldText = this.getText();
+            this.nativeNode.data = text;
+            this.triggerTextChangeEvent();
+        },
+        undo: function(t) {
+            this.setText(t.oldText);
+        }
     },
 
+    before: INSERTION(function(node) {
+        if(node.nodeType === Node.TEXT_NODE) {
+            this.prependText(node.getText());
+            return this;
+        } else {
+            return this.__super__.before(node, {silent:true});
+        }
+    }),
+
+    after: INSERTION(function(node) {
+        if(node.nodeType === Node.TEXT_NODE) {
+            this.appendText(node.getText());
+            return this;
+        } else {
+            return this.__super__.after(node, {silent:true});
+        }
+    }),
+
     appendText: function(text) {
         this.nativeNode.data = this.nativeNode.data + text;
         this.triggerTextChangeEvent();
@@ -251,6 +307,27 @@ var textNodeTransformations = {
         });
 
         return {first: parentElement, second: newElement};
+    },
+
+    divideWithElementNode: function(node, params) {
+        var insertion = this.getNodeInsertion(node),
+            myText = this.getText();
+
+        if(params.offset === myText.length) {
+            return this.after(node);
+        }
+        if(params.offset === 0) {
+            return this.before(node);
+        }
+
+        var lhsText = myText.substr(0, params.offset),
+            rhsText = myText.substr(params.offset),
+            rhsTextNode = this.document.createDocumentNode({text: rhsText});
+
+        this.setText(lhsText);
+        this.after(insertion.ofNode);
+        insertion.ofNode.after(rhsTextNode);
+        return insertion.ofNode;
     }
 };
 
@@ -347,7 +424,7 @@ var documentTransformations = {
         insertion.ofNode.triggerChangeEvent('nodeAdded');
         return insertion.ofNode;
     }
-}
+};
 
 return {
     document: {