From ff3715ae2dc79eda1edd45d462e5cc0c3e9803e4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Thu, 31 Jul 2014 12:48:14 +0200 Subject: [PATCH 1/1] editor: wip full backspace/insert support --- .../modules/documentCanvas/canvas/keyboard.js | 55 +++++++---- src/editor/plugins/core/core.js | 26 +++-- src/editor/plugins/core/core.test.js | 94 ++++++++++++++----- 3 files changed, 129 insertions(+), 46 deletions(-) diff --git a/src/editor/modules/documentCanvas/canvas/keyboard.js b/src/editor/modules/documentCanvas/canvas/keyboard.js index b2ec6ba..b39a900 100644 --- a/src/editor/modules/documentCanvas/canvas/keyboard.js +++ b/src/editor/modules/documentCanvas/canvas/keyboard.js @@ -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}); - } + } } ]; diff --git a/src/editor/plugins/core/core.js b/src/editor/plugins/core/core.js index cde08d6..0ac9cff 100644 --- a/src/editor/plugins/core/core.js +++ b/src/editor/plugins/core/core.js @@ -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'); diff --git a/src/editor/plugins/core/core.test.js b/src/editor/plugins/core/core.test.js index 99f49f4..d315ca5 100644 --- a/src/editor/plugins/core/core.test.js +++ b/src/editor/plugins/core/core.test.js @@ -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('
Alice
'), @@ -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('
Alice
cat
'), @@ -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('
A
'), - // 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('
A
B
'), 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('
A
B
'), @@ -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('
A
B
'), @@ -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('
A
B
'), @@ -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('
item
paragraph
'), @@ -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('
item1
item2
'), @@ -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('
item1
item2
'), @@ -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('
item
'), @@ -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); }); }); -- 2.20.1