editor: Handling backspace/delete on the text boundries
authorAleksander Łukasz <aleksander.lukasz@nowoczesnapolska.org.pl>
Thu, 23 Jan 2014 14:19:40 +0000 (15:19 +0100)
committerAleksander Łukasz <aleksander.lukasz@nowoczesnapolska.org.pl>
Thu, 23 Jan 2014 14:19:40 +0000 (15:19 +0100)
src/editor/modules/documentCanvas/canvas/keyboard.js
src/editor/plugins/core/core.js
src/editor/plugins/core/core.test.js

index cd12a02..65f8f6b 100644 (file)
@@ -181,7 +181,8 @@ handlers.push({keys: [KEYS.BACKSPACE, KEYS.DELETE],
         var cursor = canvas.getCursor(),
             position = canvas.getCursor().getPosition(),
             element = position.element,
-            node = element.data('wlxmlNode');
+            node = element.data('wlxmlNode'),
+            goto;
 
         if(cursor.isSelecting() && !cursor.isSelectingWithinElement()) {
             event.preventDefault();
@@ -199,25 +200,23 @@ handlers.push({keys: [KEYS.BACKSPACE, KEYS.DELETE],
 
         canvas.wlxmlDocument.startTransaction();
         
+        var direction = 'above',
+            caretTo = 'end';
+            
+        if(event.which === KEYS.DELETE) {
+            direction = 'below';
+            caretTo = 'start';
+        }
+
         if(willDeleteWholeText()) {
             event.preventDefault();
             node.setText('');
         }
         else if(element.isEmpty()) {
-
-            var direction = 'above',
-                caretTo = 'end';
-                
-            if(event.which === KEYS.DELETE) {
-                direction = 'below';
-                caretTo = 'start';
-            }
-
             event.preventDefault();
 
             var parent = element.parent(),
-                grandParent = parent ? parent.parent() : null,
-                goto;
+                grandParent = parent ? parent.parent() : null;
             if(parent.children().length === 1 && parent.children()[0].sameNode(element)) {
                 if(grandParent && grandParent.children().length === 1) {
                     goto = grandParent.data('wlxmlNode').append({text: ''});
@@ -233,7 +232,13 @@ handlers.push({keys: [KEYS.BACKSPACE, KEYS.DELETE],
             canvas.publisher('contentChanged');
         }
         else if(cursorAtOperationEdge) {
-            // todo
+            if(direction === 'below') {
+                element = element.getNearestTextElement(direction);
+            }
+            if(element) {
+                goto = element.data('wlxmlNode').mergeContentUp();
+                canvas.setCurrentElement(goto.node, {caretTo: goto.offset});
+            }
             event.preventDefault();
         }
         canvas.wlxmlDocument.endTransaction();
index 718a985..d11bf73 100644 (file)
@@ -23,6 +23,18 @@ plugin.documentExtension.textNode.transformations = {
         getChangeRoot: function() {
             return this.context.parent().parent();
         }
+    },
+    mergeContentUp: function() {
+        var myPrev = this.prev(),
+            ret;
+
+        if(myPrev) {
+            ret = myPrev.append(this);
+            return {node: ret, offset: ret.sameNode(this) ? null : ret.getText().length - this.getText().length};
+        } else {
+            var range = this.parent().unwrapContent();
+            return {node: range.element1, offset: 0};
+        }
     }
 };
 
index 940e22e..0ed38eb 100644 (file)
@@ -14,6 +14,40 @@ var getDocumentFromXML = function(xml, options) {
     return doc;
 };
 
+var getTextNodes = function(text, doc) {
+    /* globals Node */
+    var toret = [];
+    var search = function(node) {
+        node.contents().forEach(function(node) {
+            if(node.nodeType === Node.TEXT_NODE) {
+                if(node.getText() === text) {
+                    toret.push(node);
+                }
+            } else {
+                search(node);
+            }
+        });
+    };
+    search(doc.root);
+    return toret;
+};
+
+var getTextNode = function(text, doc) {
+    var nodes = getTextNodes(text, doc),
+        error;
+    if(nodes.length === 0) {
+        error = 'Text not found';
+    } else if(nodes.length > 1) {
+        error = 'Text not unique';
+    } else if(nodes[0].getText() !== text) {
+        error = 'I was trying to cheat your test :(';
+    }
+    if(error) {
+        throw new Error(error);
+    }
+    return nodes[0];
+};
+
 
 describe('Document extensions', function() {
     describe('break content', function() {
@@ -53,6 +87,67 @@ describe('Document extensions', function() {
             expect(result.emptyText.getText()).to.equal('');
         });
     });
+
+    describe('merging with adjacent content', function() {
+
+            describe('when text preceded by element', function() {
+                describe('when text followed by element', function() {
+                    it('appends text to the preceding element, following elements stays in place', function() {
+                        var doc = getDocumentFromXML('<section><a>A</a>text<b>B</b></section>'),
+                            text = getTextNode('text', doc);
+                        
+                        text.mergeContentUp();
+                        var contents = doc.root.contents();
+                        
+                        expect(contents.length).to.equal(2);
+                        expect(contents[0].getTagName()).to.equal('a');
+                        expect(contents[0].contents()[0].getText()).to.equal('Atext');
+                        expect(contents[1].getTagName()).to.equal('b');
+                    });
+                });
+                describe('when text is a last child', function() {
+                    it('appends text to the preceding element', function() {
+                        var doc = getDocumentFromXML('<section><a>A</a>text</section>'),
+                            text = getTextNode('text', doc);
+                        
+                        text.mergeContentUp();
+                        var contents = doc.root.contents();
+                        
+                        expect(contents.length).to.equal(1);
+                        expect(contents[0].getTagName()).to.equal('a');
+                        expect(contents[0].contents()[0].getText()).to.equal('Atext');
+                    });
+                });
+            });
+
+            describe('when text is a first child', function() {
+                describe('when text followed by element', function() {
+                    it('appends text and its siblings to the parent preceding element', function() {
+                        var doc = getDocumentFromXML('<section><b>B</b><div>text<a>A</a></div></section>'),
+                            text = getTextNode('text', doc);
+                        
+                        text.mergeContentUp();
+                        var contents = doc.root.contents();
+                        
+                        expect(contents.length).to.equal(3);
+                        expect(contents[0].getTagName()).to.equal('b');
+                        expect(contents[1].getText()).to.equal('text');
+                        expect(contents[2].getTagName()).to.equal('a');
+                    });
+                    it('appends text and its siblings after the parent preceding text', function() {
+                        var doc = getDocumentFromXML('<section>B<div>text<a>A</a></div></section>'),
+                            text = getTextNode('text', doc);
+                        
+                        text.mergeContentUp();
+                        var contents = doc.root.contents();
+                        
+                        expect(contents.length).to.equal(2);
+                        expect(contents[0].getText()).to.equal('Btext');
+                        expect(contents[1].getTagName()).to.equal('a');
+                    });
+                });
+            });
+    });
 });