editor: wip full backspace/insert support
authorAleksander Łukasz <aleksander.lukasz@nowoczesnapolska.org.pl>
Thu, 31 Jul 2014 10:48:14 +0000 (12:48 +0200)
committerAleksander Łukasz <aleksander.lukasz@nowoczesnapolska.org.pl>
Thu, 14 Aug 2014 14:01:45 +0000 (16:01 +0200)
src/editor/modules/documentCanvas/canvas/keyboard.js
src/editor/plugins/core/core.js
src/editor/plugins/core/core.test.js

index b2ec6ba..b39a900 100644 (file)
@@ -409,9 +409,7 @@ var keyEventHandlers = [
                 direction = 'below';
                 caretTo = 'start';
                 cursorAtOperationEdge = s.isAtEnd();
-                if(cursorAtOperationEdge) {
-                    element = cursorAtOperationEdge && s.canvas.getNearestTextElement(direction, s.element);
-                }
+                element = cursorAtOperationEdge && s.canvas.getNearestTextElement(direction, s.element);
             }
 
             if(!cursorAtOperationEdge || !element) {
@@ -433,7 +431,18 @@ var keyEventHandlers = [
                 }
             });
         }
-    }, 
+    },
+
+    {
+        applies: function(e,s) {
+            return s.type === 'caret' && s.element.getText().length === 1 && (e.key === KEYS.BACKSPACE || e.key === KEYS.DELETE);
+        },
+        run: function(e,s) {
+            e.preventDefault();
+            e.element.wlxmlNode.setText('');
+            s.canvas.setCurrentElement(s.element, {caretTo: 0});
+        }
+    },
 
     {
         applies: function(e, s) {
@@ -444,14 +453,17 @@ var keyEventHandlers = [
                 caretTo = 'end',
                 goto;
 
-                
             if(e.key === KEYS.DELETE) {
                 direction = 'below';
                 caretTo = 'start';
             }
 
             e.preventDefault();
-            if(direction === 'above') {
+
+            if(s.startsAtBeginning && s.endsAtEnd && s.startElement.sameNode(s.endElement)) {
+                goto = s.startElement;
+                caretTo = s.startOffset;
+            } else if(direction === 'above') {
                 if(s.startsAtBeginning()) {
                     goto = s.canvas.getNearestTextElement('above', s.startElement);
                     caretTo = 'end';
@@ -469,19 +481,28 @@ var keyEventHandlers = [
                 }
             }
 
-            s.canvas.wlxmlDocument.deleteText({
-                from: {
-                    node: s.startElement.wlxmlNode,
-                    offset: s.startOffset
-                },
-                to: {
-                    node: s.endElement.wlxmlNode,
-                    offset: s.endOffset
+            var doc = s.canvas.wlxmlDocument;
+            doc.transaction(function() {
+                
+                doc.deleteText({
+                    from: {
+                        node: s.startElement.wlxmlNode,
+                        offset: s.startOffset
+                    },
+                    to: {
+                        node: s.endElement.wlxmlNode,
+                        offset: s.endOffset
+                    }
+                });
+
+            }, {
+                success: function() {
+                    if(goto) {
+                        s.canvas.setCurrentElement(goto, {caretTo: caretTo});
+                    }
                 }
             });
-            if(goto) {
-                s.canvas.setCurrentElement(goto, {caretTo: caretTo});
-            }
+
         }
     }
 ];
index cde08d6..0ac9cff 100644 (file)
@@ -88,10 +88,23 @@ plugin.documentExtension.elementNode.transformations = {
             prev = toMerge.prev();
 
         var merge = function(from, to) {
-            from.contents().forEach(function(node) {
-                to.append(node);
+            var toret;
+            from.contents().forEach(function(node, idx) {
+                var len, ret;
+                if(idx === 0 && node.nodeType === Node.TEXT_NODE) {
+                    len = node.getText().length;
+                }
+                ret = to.append(node);
+                
+                if(idx === 0) {
+                    toret = {
+                        node: ret,
+                        offset: ret.getText().length - len
+                    };
+                }
             });
             from.detach();
+            return toret;
         };
 
         var strategies = [
@@ -101,12 +114,11 @@ plugin.documentExtension.elementNode.transformations = {
                 },
                 run: function() {
                     if(prev && prev.is('p') || prev.is({tagName: 'header'})) {
-                        merge(toMerge, prev);
+                        return merge(toMerge, prev);
                     }
                     if(prev && prev.is('list')) {
                         var items = prev.contents().filter(function(n) { return n.is('item');});
-                        merge(toMerge, items[items.length-1]);
-                        //return {node: toMerge, offset:0};
+                        return merge(toMerge, items[items.length-1]);
                     }
                 }
             },
@@ -116,7 +128,7 @@ plugin.documentExtension.elementNode.transformations = {
                 },
                 run: function() {
                     if(prev && prev.is('p') || prev.is({tagName: 'header'})) {
-                        merge(toMerge, prev);
+                        return merge(toMerge, prev);
                     }
                 }
             },
@@ -127,7 +139,7 @@ plugin.documentExtension.elementNode.transformations = {
                 run: function() {
                     var list;
                     if(prev && prev.is('item')) {
-                        merge(toMerge, prev);
+                        return merge(toMerge, prev);
                     } else if(!prev && (list = toMerge.parent()) && list.is('list')) {
                         list.before(toMerge);
                         toMerge.setClass('p');
index 99f49f4..d315ca5 100644 (file)
@@ -1,9 +1,11 @@
 define(function(require) {
     
 'use strict';
-/* globals describe, it */
 
-var _ = require('libs/underscore'),
+/* globals describe, it, afterEach */
+
+var $ = require('libs/jquery'),
+    _ = require('libs/underscore'),
     chai = require('libs/chai'),
     sinon = require('libs/sinon'),
     wlxml = require('wlxml/wlxml'),
@@ -23,7 +25,15 @@ var getDocumentFromXML = function(xml, options) {
 
 
 var getCanvasFromXML = function(xml, elements) {
-    return canvas.fromXMLDocument(getDocumentFromXML(xml), elements);
+    var c = canvas.fromXMLDocument(getDocumentFromXML(xml), elements),
+        view = c.view();
+    view.attr('canvas-test', true);
+    /* globals document */
+    $(document.body).append(view);
+    return c;
+};
+var removeCanvas = function() {
+    $('[canvas-test]').remove();
 };
 
 var getTextNodes = function(text, doc) {
@@ -153,7 +163,7 @@ describe('Document extensions', function() {
     });
 });
 
-describe.only('Keyboard interactions', function() {
+describe('Keyboard interactions', function() {
 
     var Keyboard = function(canvas) {
         this.canvas = canvas;
@@ -199,6 +209,8 @@ describe.only('Keyboard interactions', function() {
     });
 
     describe('deleting text with selection', function() {
+        afterEach(removeCanvas);
+
         [K.BACKSPACE, K.DELETE].forEach(function(key) {
             it('deletes text withing a single text element ' + key, function() {
                 var c = getCanvasFromXML('<section><div>Alice</div></section>'),
@@ -206,6 +218,12 @@ describe.only('Keyboard interactions', function() {
 
                 k.withSelection('A|lice', 'Alic|e').press(key);
                 expect(c.wlxmlDocument.root.contents()[0].contents()[0].getText()).to.equal('Ae');
+
+
+                var selection = c.getSelection();
+                expect(selection.type).to.equal('caret');
+                expect(selection.element.getText()).to.equal('Ae');
+                expect(selection.offset).to.equal(1);
             });
             it('deletes text across two paragraphs ' + key, function() {
                 var c = getCanvasFromXML('<section><div class="p">Alice</div><div class="p">cat</div></section>'),
@@ -217,6 +235,10 @@ describe.only('Keyboard interactions', function() {
                 expect(rootContents.length).to.equal(2);
                 expect(rootContents[0].contents()[0].getText()).to.equal('A');
                 expect(rootContents[1].contents()[0].getText()).to.equal('at');
+
+                var selection = c.getSelection();
+                expect(selection.type).to.equal('caret');
+                expect(selection.element.wlxmlNode.getText()).to.equal(key === K.BACKSPACE ? 'A' : 'at');
             });
 
             it('keeps an empty paragraph after deleting its whole text ' + key, function() {
@@ -228,31 +250,19 @@ describe.only('Keyboard interactions', function() {
 
                 expect(rootContents.length).to.equal(1);
                 expect(rootContents[0].contents()[0].getText()).to.equal('');
+                
+                var selection = c.getSelection();
+                expect(selection.type).to.equal('caret');
+                expect(selection.element.wlxmlNode.parent().sameNode(c.wlxmlDocument.root.contents()[0]));
             });
         });
 
     });
 
-    // describe('deleting with a caret', function() {
-    //     it('keeps an empty paragraph after deleteing last letter with backspace', function() {
-    //         var c = getCanvasFromXML('<section><div class="p">A</div></section>'),
-    //             k = new Keyboard(c);
-
-    //         k.withCaret('A|').press(K.BACKSPACE);
-    //         var rootContents = c.wlxmlDocument.root.contents();
-
-    //         expect(rootContents.length).to.equal(1);
-    //         expect(rootContents[0].contents()[0].getText()).to.equal('');    
-    //     });
-    //     // it('removes a paragraph on yet another delete' + key, function() {
-
-    //     // });
-    // });
-    
-
-            // + empty when bck/ins + l===1
 
     describe('backspace at the beginning', function() {
+        afterEach(removeCanvas);
+
         it('merges two adjacent paragraphs', function() {
             var c = getCanvasFromXML('<section><div class="p">A</div><div class="p">B</div></section>'),
                 k = new Keyboard(c);
@@ -263,6 +273,11 @@ describe.only('Keyboard interactions', function() {
             expect(rootContents.length).to.equal(1);
             expect(rootContents[0].getClass()).to.equal('p');
             expect(rootContents[0].contents()[0].getText()).to.equal('AB');
+
+            var selection = c.getSelection();
+            expect(selection.type).to.equal('caret');
+            expect(selection.element.sameNode(getTextElement('AB', c))).to.equal(true);
+            expect(selection.offset).to.equal(1);
         });
         it('merges a paragraph with a header', function() {
             var c = getCanvasFromXML('<section><header>A</header><div class="p">B</div></section>'),
@@ -274,6 +289,11 @@ describe.only('Keyboard interactions', function() {
             expect(rootContents.length).to.equal(1);
             expect(rootContents[0].getTagName()).to.equal('header');
             expect(rootContents[0].contents()[0].getText()).to.equal('AB');
+
+            var selection = c.getSelection();
+            expect(selection.type).to.equal('caret');
+            expect(selection.element.sameNode(getTextElement('AB', c))).to.equal(true);
+            expect(selection.offset).to.equal(1);
         });
         it('merges two adjacent headers', function() {
             var c = getCanvasFromXML('<section><header>A</header><header>B</header></section>'),
@@ -284,6 +304,11 @@ describe.only('Keyboard interactions', function() {
             expect(rootContents.length).to.equal(1);
             expect(rootContents[0].getTagName()).to.equal('header');
             expect(rootContents[0].contents()[0].getText()).to.equal('AB');
+
+            var selection = c.getSelection();
+            expect(selection.type).to.equal('caret');
+            expect(selection.element.sameNode(getTextElement('AB', c))).to.equal(true);
+            expect(selection.offset).to.equal(1);
         });
         it('merges a header with a paragraph', function() {
             var c = getCanvasFromXML('<section><div class="p">A</div><header>B</header></section>'),
@@ -295,6 +320,11 @@ describe.only('Keyboard interactions', function() {
             expect(rootContents.length).to.equal(1);
             expect(rootContents[0].is('p')).to.equal(true);
             expect(rootContents[0].contents()[0].getText()).to.equal('AB');
+
+            var selection = c.getSelection();
+            expect(selection.type).to.equal('caret');
+            expect(selection.element.sameNode(getTextElement('AB', c))).to.equal(true);
+            expect(selection.offset).to.equal(1);
         });
         it('merges a paragraph into a last list item', function() {
             var c = getCanvasFromXML('<section><div class="list"><div class="item">item</div></div><div class="p">paragraph</div></section>'),
@@ -310,6 +340,11 @@ describe.only('Keyboard interactions', function() {
             var items = list.contents();
             expect(items.length).to.equal(1);
             expect(items[0].contents()[0].getText()).to.equal('itemparagraph');
+
+            var selection = c.getSelection();
+            expect(selection.type).to.equal('caret');
+            expect(selection.element.sameNode(getTextElement('itemparagraph', c))).to.equal(true);
+            expect(selection.offset).to.equal(4);
         });
         it('merges a list item with a list item', function() {
             var c = getCanvasFromXML('<section><div class="list"><div class="item">item1</div><div class="item">item2</div></div></section>'),
@@ -327,6 +362,11 @@ describe.only('Keyboard interactions', function() {
 
             expect(items.length).to.equal(1);
             expect(items[0].contents()[0].getText()).to.equal('item1item2');
+
+            var selection = c.getSelection();
+            expect(selection.type).to.equal('caret');
+            expect(selection.element.sameNode(getTextElement('item1item2', c))).to.equal(true);
+            expect(selection.offset).to.equal(5);
         });
         it('creates a new paragraph preceding the list from a first list item', function() {
             var c = getCanvasFromXML('<section><div class="list"><div class="item">item1</div><div class="item">item2</div></div></section>'),
@@ -342,6 +382,11 @@ describe.only('Keyboard interactions', function() {
             expect(rootContents[0].contents()[0].getText()).to.equal('item1');
 
             expect(rootContents[1].sameNode(list)).to.equal(true);
+
+            var selection = c.getSelection();
+            expect(selection.type).to.equal('caret');
+            expect(selection.element.sameNode(getTextElement('item1', c))).to.equal(true);
+            expect(selection.offset).to.equal(0);
         });
         it('removes list after moving up its only item', function() {
             var c = getCanvasFromXML('<section><div class="list"><div class="item">item</div></div></section>'),
@@ -353,6 +398,11 @@ describe.only('Keyboard interactions', function() {
             
             expect(rootContents[0].getClass()).to.equal('p');
             expect(rootContents[0].contents()[0].getText()).to.equal('item');
+
+            var selection = c.getSelection();
+            expect(selection.type).to.equal('caret');
+            expect(selection.element.sameNode(getTextElement('item', c))).to.equal(true);
+            expect(selection.offset).to.equal(0);
         });
     });