From 64023eecd0051c57391e28a989aec4660d891a3b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Mon, 5 Aug 2013 16:47:28 +0200 Subject: [PATCH 01/16] Prevent widget's click events to propagate --- modules/documentCanvas/canvas/widgets.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/documentCanvas/canvas/widgets.js b/modules/documentCanvas/canvas/widgets.js index cedf16d..570bd0b 100644 --- a/modules/documentCanvas/canvas/widgets.js +++ b/modules/documentCanvas/canvas/widgets.js @@ -17,7 +17,8 @@ return { .css('display', 'inline') .show(); - mydom.click(function() { + mydom.click(function(e) { + e.stopPropagation(); clickHandler(); }); @@ -27,7 +28,10 @@ return { hideButton: function(clickHandler) { var mydom = $('x') .addClass('canvas-widget canvas-widget-hide-button'); - mydom.click(clickHandler); + mydom.click(function(e) { + e.stopPropagation(); + clickHandler(); + }); return mydom; } -- 2.20.1 From ecf3b34e969acfeeb5d09b7287b233ce4bffed83 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 6 Aug 2013 09:21:25 +0200 Subject: [PATCH 02/16] EventBus for canvas elements + setting current element after footnote close use case --- modules/documentCanvas/canvas/canvas.js | 17 +++++++++++++---- .../documentCanvas/canvas/documentElement.js | 13 ++++++++----- modules/documentCanvas/canvas/wlxmlManagers.js | 16 ++++++++++++++-- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/modules/documentCanvas/canvas/canvas.js b/modules/documentCanvas/canvas/canvas.js index dd15d06..9ce8a72 100644 --- a/modules/documentCanvas/canvas/canvas.js +++ b/modules/documentCanvas/canvas/canvas.js @@ -1,13 +1,15 @@ define([ 'libs/jquery-1.9.1.min', 'libs/underscore-min', +'libs/backbone-min', 'modules/documentCanvas/canvas/documentElement', 'modules/documentCanvas/canvas/utils' -], function($, _, documentElement, utils) { +], function($, _, Backbone, documentElement, utils) { 'use strict'; var Canvas = function(wlxml, publisher) { + this.eventBus = _.extend({}, Backbone.Events); this.loadWlxml(wlxml); this.publisher = publisher ? publisher : function() {}; }; @@ -19,7 +21,8 @@ $.extend(Canvas.prototype, { if(d) { this.wrapper = $('
').addClass('canvas-wrapper').attr('contenteditable', true); this.wrapper.append(d); - + var canvas = this; + this.wrapper.find('*').replaceWith(function() { var currentTag = $(this); if(currentTag.attr('wlxml-tag')) @@ -40,7 +43,7 @@ $.extend(Canvas.prototype, { meta: meta, others: others, rawChildren: currentTag.contents() - }); + }, canvas); ['orig-before', 'orig-after', 'orig-begin', 'orig-end'].forEach(function(attr) { element.data(attr, ''); @@ -137,7 +140,6 @@ $.extend(Canvas.prototype, { this.d = this.wrapper.children(0); - var canvas = this; this.wrapper.on('keydown', function(e) { if(e.which === 13) { e.preventDefault(); @@ -255,6 +257,13 @@ $.extend(Canvas.prototype, { el.toggleLabel(false); }); + this.eventBus.on('elementToggled', function(toggle, element) { + if(!toggle) { + element = canvas.getDocumentElement(utils.nearestInDocumentOrder('[document-text-element]:visible', 'above', element.dom()[0])); + canvas.setCurrentElement(element); + } + }) + } else { this.d = null; } diff --git a/modules/documentCanvas/canvas/documentElement.js b/modules/documentCanvas/canvas/documentElement.js index e1d3203..749a2ef 100644 --- a/modules/documentCanvas/canvas/documentElement.js +++ b/modules/documentCanvas/canvas/documentElement.js @@ -46,6 +46,9 @@ $.extend(DocumentElement.prototype, { _setupDOMHandler: function(htmlElement) { this.$element = $(htmlElement); }, + bound: function() { + return this.canvas !== undefined; + }, dom: function() { return this.$element; }, @@ -115,7 +118,7 @@ var DocumentNodeElement = function(htmlElement, canvas) { }; $.extend(DocumentNodeElement, { - createDOM: function(params) { + createDOM: function(params, canvas) { var dom = $('
') .attr('document-node-element', ''), widgetsContainer = $('
') @@ -128,7 +131,7 @@ $.extend(DocumentNodeElement, { // Make sure widgets aren't navigable with arrow keys widgetsContainer.find('*').add(widgetsContainer).attr('tabindex', -1); - var element = this.fromHTMLElement(dom[0]); + var element = this.fromHTMLElement(dom[0], canvas); element.setWlxmlTag(params.tag); if(params.klass) @@ -147,7 +150,7 @@ $.extend(DocumentNodeElement, { }, create: function(params, canvas) { - return this.fromHTMLElement(this.createDOM(params)[0]); + return this.fromHTMLElement(this.createDOM(params, canvas)[0], canvas); }, fromHTMLElement: function(htmlElement, canvas) { @@ -427,7 +430,7 @@ $.extend(DocumentTextElement.prototype, { if(params instanceof DocumentNodeElement) { element = params; } else { - element = DocumentNodeElement.create(params); + element = DocumentNodeElement.create(params, this.canvas); } this.dom().wrap('
'); this.dom().parent().after(element.dom()); @@ -441,7 +444,7 @@ $.extend(DocumentTextElement.prototype, { if(params instanceof DocumentNodeElement) { element = params; } else { - element = DocumentNodeElement.create(params); + element = DocumentNodeElement.create(params, this.canvas); } this.dom().wrap('
'); this.dom().parent().before(element.dom()); diff --git a/modules/documentCanvas/canvas/wlxmlManagers.js b/modules/documentCanvas/canvas/wlxmlManagers.js index 0a48fdc..8973a6c 100644 --- a/modules/documentCanvas/canvas/wlxmlManagers.js +++ b/modules/documentCanvas/canvas/wlxmlManagers.js @@ -8,6 +8,8 @@ define([ var DocumentElementWrapper = function(documentElement) { + this.documentElement = documentElement; + this.addWidget = function(widget) { documentElement.dom().find('.canvas-widgets').append(widget); }; @@ -32,6 +34,13 @@ var DocumentElementWrapper = function(documentElement) { this.toggle = function(toggle) { documentElement._container().toggle(toggle); } + + var eventBus = documentElement.bound() ? documentElement.canvas.eventBus : + {trigger: function() {}}; + this.trigger = function() { + eventBus.trigger.apply(eventBus, arguments); + } + } var getDisplayStyle = function(tag, klass) { @@ -94,14 +103,17 @@ $.extend(FootnoteManager.prototype, { this.hideButton = widgets.hideButton(closeHandler); this.el.addWidget(this.hideButton); - this.toggle(false); + this.toggle(false, {silent: true}); }, - toggle: function(toggle) { + toggle: function(toggle, options) { + options = options || {}; this.hideButton.toggle(toggle); this.footnoteHandler.toggle(!toggle); this.el.setDisplayStyle(toggle ? 'block' : 'inline'); this.el.toggle(toggle); + if(!options.silent) + this.el.trigger('elementToggled', toggle, this.el.documentElement); } }) managers.set('aside', 'footnote', FootnoteManager); -- 2.20.1 From c601e8aff30efc0b4fd69765d5671e3ee88c4bf4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 6 Aug 2013 12:06:52 +0200 Subject: [PATCH 03/16] canvas api: prepending element to node element --- modules/documentCanvas/canvas/canvas.test3.js | 19 +++++++++++++++++++ .../documentCanvas/canvas/documentElement.js | 5 ++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/modules/documentCanvas/canvas/canvas.test3.js b/modules/documentCanvas/canvas/canvas.test3.js index 3c53f09..ded28c2 100644 --- a/modules/documentCanvas/canvas/canvas.test3.js +++ b/modules/documentCanvas/canvas/canvas.test3.js @@ -315,6 +315,25 @@ describe('Canvas', function() { expect(children[0].getText()).to.equal('Alice'); }); + it('can put new NodeElement at the beginning', function() { + var c = canvas.fromXML('
'), + prepended = c.doc().prepend({tag: 'header', klass: 'some.class'}), + children = c.doc().children(); + + expect(children).to.have.length(2); + expect(children[0].sameNode(prepended)).to.be.true; + }); + + it('can put new TextElement at the beginning', function() { + var c = canvas.fromXML('
'), + prepended = c.doc().prepend({text: 'Alice'}), + children = c.doc().children(); + + expect(children).to.have.length(2) + expect(children[0].sameNode(prepended)).to.be.true; + expect(children[0].getText()).to.equal('Alice'); + }); + it('can put new NodeElement after another NodeElement', function() { var c = canvas.fromXML('
'), div = c.doc().children()[0], diff --git a/modules/documentCanvas/canvas/documentElement.js b/modules/documentCanvas/canvas/documentElement.js index 749a2ef..9768236 100644 --- a/modules/documentCanvas/canvas/documentElement.js +++ b/modules/documentCanvas/canvas/documentElement.js @@ -165,7 +165,7 @@ var manipulate = function(e, params, action) { } else { element = DocumentElement.create(params); } - var target = action === 'append' ? e._container() : e.dom(); + var target = (action === 'append' || action === 'prepend') ? e._container() : e.dom(); target[action](element.dom()); return element; }; @@ -257,6 +257,9 @@ $.extend(DocumentNodeElement.prototype, { this.data('orig-end', undefined); return manipulate(this, params, 'append'); }, + prepend: function(params) { + return manipulate(this, params, 'prepend'); + }, before: function(params) { return manipulate(this, params, 'before'); -- 2.20.1 From 808306985627fd87f75a1b61fbd2d2b0fa61b186 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 6 Aug 2013 12:07:46 +0200 Subject: [PATCH 04/16] canvas api: getting sibling parents of two elements --- modules/documentCanvas/canvas/canvas.js | 17 ++++++++++ modules/documentCanvas/canvas/canvas.test3.js | 33 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/modules/documentCanvas/canvas/canvas.js b/modules/documentCanvas/canvas/canvas.js index 9ce8a72..27f752d 100644 --- a/modules/documentCanvas/canvas/canvas.js +++ b/modules/documentCanvas/canvas/canvas.js @@ -319,6 +319,23 @@ $.extend(Canvas.prototype, { wrapperElement.after({text: suffixOutside}); return wrapperElement; }, + getSiblingParents: function(params) { + var parents1 = [params.element1].concat(params.element1.parents()).reverse(), + parents2 = [params.element2].concat(params.element2.parents()).reverse(), + noSiblingParents = null; + + if(parents1.length === 0 || parents2.length === 0 || !(parents1[0].sameNode(parents2[0]))) + return noSiblingParents; + + var i; + for(i = 0; i < Math.min(parents1.length, parents2.length); i++) { + if(parents1[i].sameNode(parents2[i])) + continue; + break; + } + return {element1: parents1[i], element2: parents2[i]}; + }, + getDocumentElement: function(from) { if(from instanceof HTMLElement || from instanceof Text) { return documentElement.DocumentElement.fromHTMLElement(from, this); diff --git a/modules/documentCanvas/canvas/canvas.test3.js b/modules/documentCanvas/canvas/canvas.test3.js index ded28c2..b327bce 100644 --- a/modules/documentCanvas/canvas/canvas.test3.js +++ b/modules/documentCanvas/canvas/canvas.test3.js @@ -207,6 +207,39 @@ describe('Canvas', function() { }); }); + describe('accessing sibling parents of two elements', function() { + it('returns elements themself if they have direct common parent', function() { + var c = canvas.fromXML('
\ +
\ +
A
\ +
B
\ +
\ +
'), + section = c.doc(), + wrappingDiv = c.doc().children()[0], + divA = wrappingDiv.children()[0], + divB = wrappingDiv.children()[1]; + + var siblingParents = c.getSiblingParents({element1: divA, element2: divB}); + + expect(siblingParents.element1.sameNode(divA)).to.equal(true, 'divA'); + expect(siblingParents.element2.sameNode(divB)).to.equal(true, 'divB'); + }); + + it('returns sibling parents - example 1', function() { + var c = canvas.fromXML('
Alice has a cat
'), + section = c.doc(), + aliceText = section.children()[0], + span = section.children()[1], + spanText = span.children()[0]; + + var siblingParents = c.getSiblingParents({element1: aliceText, element2: spanText}); + + expect(siblingParents.element1.sameNode(aliceText)).to.equal(true, 'aliceText'); + expect(siblingParents.element2.sameNode(span)).to.equal(true, 'span'); + }); + }) + describe('free text handling', function() { it('sees free text', function() { var c = canvas.fromXML('
Alice has a cat
'), -- 2.20.1 From 97b4fb7216407c6623f0e1307bae4b98b0c64b75 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 6 Aug 2013 12:09:06 +0200 Subject: [PATCH 05/16] canvas api: wrapElements - wraps one or more sibling elements with a node element --- modules/documentCanvas/canvas/canvas.js | 36 ++++++++++++++ modules/documentCanvas/canvas/canvas.test3.js | 48 +++++++++++++++++++ .../documentCanvas/canvas/documentElement.js | 1 + 3 files changed, 85 insertions(+) diff --git a/modules/documentCanvas/canvas/canvas.js b/modules/documentCanvas/canvas/canvas.js index 27f752d..9b57d93 100644 --- a/modules/documentCanvas/canvas/canvas.js +++ b/modules/documentCanvas/canvas/canvas.js @@ -319,6 +319,42 @@ $.extend(Canvas.prototype, { wrapperElement.after({text: suffixOutside}); return wrapperElement; }, + + wrapElements: function(params) { + if(!(params.element1.parent().sameNode(params.element2.parent()))) + return; + + var parent = params.element1.parent(), + parentChildren = parent.children(), + wrapper = documentElement.DocumentNodeElement.create({ + tag: params._with.tag, + klass: params._with.klass}), + idx1 = parent.childIndex(params.element1), + idx2 = parent.childIndex(params.element2); + + if(idx1 > idx2) { + var tmp = idx1; + idx1 = idx2; + idx2 = tmp; + } + + var insertingMethod, insertingTarget; + if(idx1 === 0) { + insertingMethod = 'prepend'; + insertingTarget = parent; + } else { + insertingMethod = 'after'; + insertingTarget = parentChildren[idx1-1]; + } + + for(var i = idx1; i <= idx2; i++) { + wrapper.append(parentChildren[i].detach()); + } + + insertingTarget[insertingMethod](wrapper); + return wrapper; + }, + getSiblingParents: function(params) { var parents1 = [params.element1].concat(params.element1.parents()).reverse(), parents2 = [params.element2].concat(params.element2.parents()).reverse(), diff --git a/modules/documentCanvas/canvas/canvas.test3.js b/modules/documentCanvas/canvas/canvas.test3.js index b327bce..ff30843 100644 --- a/modules/documentCanvas/canvas/canvas.test3.js +++ b/modules/documentCanvas/canvas/canvas.test3.js @@ -636,6 +636,54 @@ describe('Canvas', function() { expect(wrapperChildren[2].getText()).to.equal(' cat'); }); + + it('wraps multiple sibling Elements', function() { + var c = canvas.fromXML('
Alice
has
a cat
'), + section = c.doc(), + aliceText = section.children()[0], + firstDiv = section.children()[1], + lastDiv = section.children()[section.children().length -1]; + + var returned = c.wrapElements({ + element1: aliceText, + element2: lastDiv, + _with: {tag: 'header'} + }); + + var sectionChildren = section.children(), + header = sectionChildren[0], + headerChildren = header.children(); + + expect(sectionChildren).to.have.length(1); + expect(header.sameNode(returned)).to.equal(true, 'wrapper returned'); + expect(headerChildren).to.have.length(3); + expect(headerChildren[0].sameNode(aliceText)).to.equal(true, 'first node wrapped'); + expect(headerChildren[1].sameNode(firstDiv)).to.equal(true, 'second node wrapped'); + expect(headerChildren[2].sameNode(lastDiv)).to.equal(true, 'third node wrapped'); + }); + it('wraps multiple sibling Elements - middle case', function() { + var c = canvas.fromXML('
div>
'), + section = c.doc(), + div1 = section.children()[0], + div2 = section.children()[1], + div3 = section.children()[2], + div4 = section.children()[3]; + + var returned = c.wrapElements({ + element1: div2, + element2: div3, + _with: {tag: 'header'} + }); + + var sectionChildren = section.children(), + header = sectionChildren[1], + headerChildren = header.children(); + + expect(sectionChildren).to.have.length(3); + expect(headerChildren).to.have.length(2); + expect(headerChildren[0].sameNode(div2)).to.equal(true, 'first node wrapped'); + expect(headerChildren[1].sameNode(div3)).to.equal(true, 'second node wrapped'); + }); }); describe('unwrapping', function() { diff --git a/modules/documentCanvas/canvas/documentElement.js b/modules/documentCanvas/canvas/documentElement.js index 9768236..e76ba78 100644 --- a/modules/documentCanvas/canvas/documentElement.js +++ b/modules/documentCanvas/canvas/documentElement.js @@ -83,6 +83,7 @@ $.extend(DocumentElement.prototype, { detach: function() { this.dom().detach(); this.canvas = null; + return this; }, markAsCurrent: function() { -- 2.20.1 From 27aad148fb13a6705a8fd91ea693bd8ea46da396 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 6 Aug 2013 16:30:36 +0200 Subject: [PATCH 06/16] Fixing unwrapping text element from its parent Fixing case where parent of a text element has siblings other than text elements --- modules/documentCanvas/canvas/canvas.test3.js | 24 ++++++++++++++++--- .../documentCanvas/canvas/documentElement.js | 4 ++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/modules/documentCanvas/canvas/canvas.test3.js b/modules/documentCanvas/canvas/canvas.test3.js index ff30843..10f8348 100644 --- a/modules/documentCanvas/canvas/canvas.test3.js +++ b/modules/documentCanvas/canvas/canvas.test3.js @@ -686,8 +686,26 @@ describe('Canvas', function() { }); }); - describe('unwrapping', function() { - it('unwraps DocumentTextElement from its parent DocumentNodeElement if it\'s its only child', function() { + describe('unwrapping DocumentTextElement from its parent DocumentNodeElement if it\'s its only child', function() { + it('unwraps text element from its parent and stays between its old parent siblings', function() { + var c = canvas.fromXML('
Alice
has
a cat
'), + section = c.doc(), + sectionChildren = section.children(), + divAlice = sectionChildren[0], + divHas = sectionChildren[1], + textHas = divHas.children()[0], + divCat = sectionChildren[2]; + + var newTextContainer = textHas.unwrap(), + sectionChildren = section.children(); + + expect(sectionChildren[0].sameNode(divAlice)).to.equal(true, 'divAlice ok'); + expect(newTextContainer.sameNode(section)).to.equal(true, 'unwrap returns new text parent DocumentNodeElement'); + expect(sectionChildren[1].getText()).to.equal('has'); + expect(sectionChildren[2].sameNode(divCat)).to.equal(true, 'divCat ok'); + + }); + it('unwraps and join with its old parent adjacent text elements ', function() { var c = canvas.fromXML('
Alice has a cat
'), section = c.doc(), text = section.children()[1].children()[0]; @@ -697,7 +715,7 @@ describe('Canvas', function() { expect(section.children().length).to.equal(1, 'section has one child'); expect(section.children()[0].getText()).to.equal('Alice has a cat'); expect(newTextContainer.sameNode(c.doc())).to.equal(true, 'unwrap returns new text parent DocumentNodeElement'); - }) + }); }); }); diff --git a/modules/documentCanvas/canvas/documentElement.js b/modules/documentCanvas/canvas/documentElement.js index e76ba78..cfdac5d 100644 --- a/modules/documentCanvas/canvas/documentElement.js +++ b/modules/documentCanvas/canvas/documentElement.js @@ -479,6 +479,10 @@ $.extend(DocumentTextElement.prototype, { idx = grandParent.childIndex(parent), prev = idx - 1 > -1 ? grandParentChildren[idx-1] : null, next = idx + 1 < grandParentChildren.length ? grandParentChildren[idx+1] : null; + + prev = (prev instanceof DocumentTextElement) ? prev : null; + next = (next instanceof DocumentTextElement) ? next : null; + if(prev && next) { prev.setText(prev.getText() + this.getText() + next.getText()); next.detach(); -- 2.20.1 From 2139e7253380f71ffeb8d58728aeabfb66f739b2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 6 Aug 2013 16:52:42 +0200 Subject: [PATCH 07/16] Do note split root document node element on hitting enter --- modules/documentCanvas/canvas/canvas.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/documentCanvas/canvas/canvas.js b/modules/documentCanvas/canvas/canvas.js index 9b57d93..318a22f 100644 --- a/modules/documentCanvas/canvas/canvas.js +++ b/modules/documentCanvas/canvas/canvas.js @@ -146,7 +146,13 @@ $.extend(Canvas.prototype, { var cursor = canvas.getCursor(); if(!cursor.isSelecting()) { var position = cursor.getPosition(), - elements = position.element.split({offset: position.offset}), + element = position.element; + + if(!(element.parent().parent())) { + return false; // top level element is unsplittable + } + + var elements = position.element.split({offset: position.offset}), newEmpty, goto; -- 2.20.1 From 69df3ab4cbe3d3650e99b819b2ffbddb75dbdbc2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 6 Aug 2013 16:53:52 +0200 Subject: [PATCH 08/16] wrapping sibling parents of selected text in new node --- modules/documentCanvas/commands.js | 51 +++++++++++++++++++----------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/modules/documentCanvas/commands.js b/modules/documentCanvas/commands.js index b08348e..bb86991 100644 --- a/modules/documentCanvas/commands.js +++ b/modules/documentCanvas/commands.js @@ -76,27 +76,40 @@ commands.register('newNodeRequested', function(canvas, params) { selectionStart = cursor.getSelectionStart(), selectionEnd = cursor.getSelectionEnd(); - if(cursor.isSelecting() && cursor.isSelectingSiblings()) { - if(cursor.isSelectingWithinElement()) { - var newElement = selectionStart.element.wrapWithNodeElement({tag: params.wlxmlTag, klass: params.wlxmlClass, start: selectionStart.offset, end: selectionEnd.offset}), - caretTo = selectionStart.offset < selectionEnd.offset ? 'start' : 'end'; - canvas.setCurrentElement(newElement.children()[0], {caretTo: caretTo}); - } - else { - var parent = selectionStart.element.parent(), - caretTo = selectionStart.element.sameNode(cursor.getSelectionAnchor().element) ? 'end' : 'start'; - - var wrapper = canvas.wrapText({ - inside: parent, - _with: {tag: params.wlxmlTag, klass: params.wlxmlClass}, - offsetStart: selectionStart.offset, - offsetEnd: selectionEnd.offset, - textNodeIdx: [parent.childIndex(selectionStart.element), parent.childIndex(selectionEnd.element)] - }); - - canvas.setCurrentElement(wrapper.children()[caretTo === 0 ? 0 : wrapper.children().length - 1], {caretTo: caretTo}); + if(cursor.isSelecting()) { + if(cursor.isSelectingSiblings()) { + if(cursor.isSelectingWithinElement()) { + var newElement = selectionStart.element.wrapWithNodeElement({tag: params.wlxmlTag, klass: params.wlxmlClass, start: selectionStart.offset, end: selectionEnd.offset}), + caretTo = selectionStart.offset < selectionEnd.offset ? 'start' : 'end'; + canvas.setCurrentElement(newElement.children()[0], {caretTo: caretTo}); + } + else { + var parent = selectionStart.element.parent(), + caretTo = selectionStart.element.sameNode(cursor.getSelectionAnchor().element) ? 'end' : 'start'; + + var wrapper = canvas.wrapText({ + inside: parent, + _with: {tag: params.wlxmlTag, klass: params.wlxmlClass}, + offsetStart: selectionStart.offset, + offsetEnd: selectionEnd.offset, + textNodeIdx: [parent.childIndex(selectionStart.element), parent.childIndex(selectionEnd.element)] + }); + + canvas.setCurrentElement(wrapper.children()[caretTo === 0 ? 0 : wrapper.children().length - 1], {caretTo: caretTo}); + } + } else { + var siblingParents = canvas.getSiblingParents({element1: selectionStart.element, element2: selectionEnd.element}) + if(siblingParents) { + canvas.wrapElements({ + element1: siblingParents.element1, + element2: siblingParents.element2, + _with: {tag: params.wlxmlTag, klass: params.wlxmlClass} + }); + } } } + + }); commands.register('footnote', function(canvas, params) { -- 2.20.1 From 39f08569a5e595a7622b8bb4ab27dbfcab502c62 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 7 Aug 2013 09:48:50 +0200 Subject: [PATCH 09/16] Custom names for wlxml tags/classes - first approach --- modules/documentToolbar/documentToolbar.js | 4 +-- modules/documentToolbar/template.html | 6 ++-- modules/nodePane/nodePane.js | 7 +++-- modules/nodePane/template.html | 6 ++-- utils/wlxml.js | 36 ++++++++++++++++++++++ 5 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 utils/wlxml.js diff --git a/modules/documentToolbar/documentToolbar.js b/modules/documentToolbar/documentToolbar.js index e4d5039..e3bc1eb 100644 --- a/modules/documentToolbar/documentToolbar.js +++ b/modules/documentToolbar/documentToolbar.js @@ -1,11 +1,11 @@ -define(['libs/jquery-1.9.1.min', 'libs/underscore-min', 'libs/text!./template.html'], function($, _, template) { +define(['libs/jquery-1.9.1.min', 'libs/underscore-min', 'utils/wlxml', 'libs/text!./template.html'], function($, _, wlxmlUtils, template) { 'use strict'; return function(sandbox) { var view = { - node: $(_.template(template)()), + node: $(_.template(template)({tagNames: wlxmlUtils.wlxmlTagNames, classNames: wlxmlUtils.wlxmlClassNames})), setup: function() { var view = this; diff --git a/modules/documentToolbar/template.html b/modules/documentToolbar/template.html index e521f25..86357bc 100644 --- a/modules/documentToolbar/template.html +++ b/modules/documentToolbar/template.html @@ -7,13 +7,13 @@ diff --git a/modules/nodePane/nodePane.js b/modules/nodePane/nodePane.js index 3a4a0ef..b723e68 100644 --- a/modules/nodePane/nodePane.js +++ b/modules/nodePane/nodePane.js @@ -2,14 +2,15 @@ define([ 'libs/text!./template.html', 'libs/jquery-1.9.1.min', 'libs/underscore-min', -'modules/nodePane/metaWidget/metaWidget' -], function(templateSrc, $, _, metaWidget) { +'modules/nodePane/metaWidget/metaWidget', +'utils/wlxml' +], function(templateSrc, $, _, metaWidget, wlxmlUtils) { 'use strict'; return function(sandbox) { - var view = $(_.template(templateSrc)()); + var view = $(_.template(templateSrc)({tagNames: wlxmlUtils.wlxmlTagNames, classNames: wlxmlUtils.wlxmlClassNames})); view.on('change', 'select', function(e) { var target = $(e.target); diff --git a/modules/nodePane/template.html b/modules/nodePane/template.html index a40e711..b9a1254 100644 --- a/modules/nodePane/template.html +++ b/modules/nodePane/template.html @@ -6,16 +6,16 @@
diff --git a/utils/wlxml.js b/utils/wlxml.js new file mode 100644 index 0000000..5f1b84d --- /dev/null +++ b/utils/wlxml.js @@ -0,0 +1,36 @@ +define([ + +], function() { + +'use strict'; + + + +return { + wlxmlTagNames: { + '': '', + section: 'sekcja', + header: 'nagłówek', + div: 'blok', + span: 'tekst', + aside: 'poboczny' + }, + wlxmlClassNames: { + '': '', + author: 'autor', + title: 'tytuł', + cite: 'cytat', + 'cite.code': 'cytat.kod', + 'cite.code.xml': 'cytat.kod.xml', + 'list.items': 'lista', + 'list.items.enum': 'lista.numerowana', + item: 'element', + uri: 'uri', + p: 'paragraf', + footnote: 'przypis', + todo: 'todo', + emp: 'wyróżnienie' + } +}; + +}); \ No newline at end of file -- 2.20.1 From 739242f6208f3e58570194fb1000086bafd527ef Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 7 Aug 2013 10:04:09 +0200 Subject: [PATCH 10/16] Allow for creating footnote from selected text --- modules/documentCanvas/commands.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/documentCanvas/commands.js b/modules/documentCanvas/commands.js index bb86991..8152ccd 100644 --- a/modules/documentCanvas/commands.js +++ b/modules/documentCanvas/commands.js @@ -1,6 +1,6 @@ define([ - -], function() { +'modules/documentCanvas/canvas/documentElement' +], function(documentElement) { 'use strict'; @@ -113,11 +113,18 @@ commands.register('newNodeRequested', function(canvas, params) { }); commands.register('footnote', function(canvas, params) { - var position = canvas.getCursor().getPosition(); + var cursor = canvas.getCursor(), + position = cursor.getPosition(), + asideElement; + - var asideElement = position.element.divide({tag: 'aside', klass: 'footnote', offset: position.offset}); + if(cursor.isSelectingWithinElement()) { + asideElement = position.element.wrapWithNodeElement({tag: 'aside', klass: 'footnote', start: cursor.getSelectionStart().offset, end: cursor.getSelectionEnd().offset}); + } else { + asideElement = position.element.divide({tag: 'aside', klass: 'footnote', offset: position.offset}); + asideElement.append({text: ''}); + } - asideElement.append({text: ''}); asideElement.toggle(true); canvas.setCurrentElement(asideElement); }); -- 2.20.1 From 84c1c7eb511a9776480c863cee42954853cb0e96 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 7 Aug 2013 10:12:25 +0200 Subject: [PATCH 11/16] Margin for footnotes --- modules/documentCanvas/canvas/widgets.less | 3 ++- modules/documentCanvas/nodes.less | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/documentCanvas/canvas/widgets.less b/modules/documentCanvas/canvas/widgets.less index 0737aa7..0f43c89 100644 --- a/modules/documentCanvas/canvas/widgets.less +++ b/modules/documentCanvas/canvas/widgets.less @@ -44,9 +44,10 @@ .canvas-widget-hide-button { @line_height: 12px; @padding: 2px; + @temporary_footnote_hack: 10px; position: absolute; - top: -(@line_height + 2 * @padding); + top: -(@line_height + 2 * @padding) + @temporary_footnote_hack; right: 0; line-height: @line_height; padding: @padding; diff --git a/modules/documentCanvas/nodes.less b/modules/documentCanvas/nodes.less index de4fd7c..2deb807 100644 --- a/modules/documentCanvas/nodes.less +++ b/modules/documentCanvas/nodes.less @@ -128,4 +128,9 @@ padding: 5px; } +} + +[wlxml-tag="aside"] { + margin-top: 10px; + margin-bottom: 10px; } \ No newline at end of file -- 2.20.1 From 179b145075f8f90038cba27e30a4fcadd3a764ff Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 7 Aug 2013 10:45:36 +0200 Subject: [PATCH 12/16] Fixing element highlighting synchronization issue --- modules/documentCanvas/canvas/documentElement.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/documentCanvas/canvas/documentElement.js b/modules/documentCanvas/canvas/documentElement.js index cfdac5d..689df0e 100644 --- a/modules/documentCanvas/canvas/documentElement.js +++ b/modules/documentCanvas/canvas/documentElement.js @@ -367,8 +367,8 @@ $.extend(DocumentNodeElement.prototype, { this.toggleHighlight(toggle); }, - toggleHighlight: function(toogle) { - this._container().toggleClass('highlighted-element'); + toggleHighlight: function(toggle) { + this._container().toggleClass('highlighted-element', toggle); }, toggle: function(toggle) { -- 2.20.1 From 25e14ce74de1ecdd95f36cb19e231b5a66898038 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 7 Aug 2013 12:06:38 +0200 Subject: [PATCH 13/16] Fix: Detaching node element surrounded by text elements now merges these text elements together --- modules/documentCanvas/canvas/canvas.test3.js | 16 ++++++++++ .../documentCanvas/canvas/documentElement.js | 32 +++++++++++++++---- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/modules/documentCanvas/canvas/canvas.test3.js b/modules/documentCanvas/canvas/canvas.test3.js index 10f8348..0fe89cc 100644 --- a/modules/documentCanvas/canvas/canvas.test3.js +++ b/modules/documentCanvas/canvas/canvas.test3.js @@ -458,6 +458,22 @@ describe('Canvas', function() { }); }); + describe('Removing elements', function() { + it('merges left and right DocumentTextElement sibling of a detached DocumentNodeElement', function() { + var c = canvas.fromXML('
Alice
has
a cat
'), + section = c.doc(), + div = section.children()[1]; + + div.detach(); + + var sectionChildren = section.children(), + textElement = sectionChildren[0]; + + expect(sectionChildren).to.have.length(1); + expect(textElement.getText()).to.equal('Alicea cat'); + }); + }); + describe('Splitting text', function() { it('splits DocumentTextElement\'s parent into two DocumentNodeElements of the same type', function() { diff --git a/modules/documentCanvas/canvas/documentElement.js b/modules/documentCanvas/canvas/documentElement.js index 689df0e..4a236df 100644 --- a/modules/documentCanvas/canvas/documentElement.js +++ b/modules/documentCanvas/canvas/documentElement.js @@ -80,12 +80,6 @@ $.extend(DocumentElement.prototype, { return wrapper; }, - detach: function() { - this.dom().detach(); - this.canvas = null; - return this; - }, - markAsCurrent: function() { this.canvas.markAsCurrent(this); }, @@ -178,6 +172,24 @@ $.extend(DocumentNodeElement.prototype, { _container: function() { return this.dom().children('[document-element-content]'); }, + detach: function() { + var parent = this.parent(); + if(!parent) + return; + + var parentChildren = parent.children(), + myIdx = parent.childIndex(this); + + if(myIdx > 0 && myIdx < parentChildren.length) { + if((parentChildren[myIdx-1] instanceof DocumentTextElement) && (parentChildren[myIdx+1] instanceof DocumentTextElement)) { + parentChildren[myIdx-1].appendText(parentChildren[myIdx+1].getText()); + parentChildren[myIdx+1].detach(); + } + } + this.dom().detach(); + this.canvas = null; + return this; + }, data: function() { var dom = this.dom(), args = Array.prototype.slice.call(arguments, 0); @@ -417,9 +429,17 @@ $.extend(DocumentTextElement.prototype, { else this.$element = $element; }, + detach: function() { + this.dom().detach(); + this.canvas = null; + return this; + }, setText: function(text) { this.dom().contents()[0].data = text; }, + appendText: function(text) { + this.dom().contents()[0].data += text; + }, getText: function() { return this.dom().text().replace(utils.unicode.ZWS, ''); }, -- 2.20.1 From 3e4d8926aeff66f590b8a64f5b43cde1215d777a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 7 Aug 2013 12:23:52 +0200 Subject: [PATCH 14/16] Handling backspace on empty text element (wip) --- modules/documentCanvas/canvas/canvas.js | 36 ++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/modules/documentCanvas/canvas/canvas.js b/modules/documentCanvas/canvas/canvas.js index 318a22f..e706428 100644 --- a/modules/documentCanvas/canvas/canvas.js +++ b/modules/documentCanvas/canvas/canvas.js @@ -173,7 +173,8 @@ $.extend(Canvas.prototype, { ARROW_LEFT: 37, ARROW_UP: 38, ARROW_RIGHT: 39, - ARROW_DOWN: 40 + ARROW_DOWN: 40, + BACKSPACE: 8 } this.wrapper.on('keyup', function(e) { @@ -197,9 +198,10 @@ $.extend(Canvas.prototype, { }); this.wrapper.on('keydown', function(e) { + var position = canvas.getCursor().getPosition(), + element = position.element; if(e.which >= 37 && e.which <= 40) { - var position = canvas.getCursor().getPosition(), - element = position.element; + if(element && (element instanceof documentElement.DocumentTextElement)) { if(element.isEmpty()) { var direction, caretTo; @@ -227,6 +229,34 @@ $.extend(Canvas.prototype, { } + if(e.which === KEYS.BACKSPACE) { + if(element.getText().length === 1) { + e.preventDefault(); + element.setText(''); + } + else if(element.isEmpty()) { + e.preventDefault(); + + var parent = element.parent(), + grandParent = parent ? parent.parent() : null, + goto; + if(parent.children().length === 1) { + if(grandParent && grandParent.children().length === 1) { + goto = grandParent.append({text: ''}); + } else { + goto = canvas.getDocumentElement(utils.nearestInDocumentOrder('[document-text-element]', 'above', element.dom()[0])); + } + parent.detach(); + } else { + goto = canvas.getDocumentElement(utils.nearestInDocumentOrder('[document-text-element]', 'above', element.dom()[0])); + element.detach(); + } + canvas.setCurrentElement(goto, {caretTo: 'end'}); + } + else if(position.offset === 0) { + // todo + } + } }); this.wrapper.on('click', '[document-node-element], [document-text-element]', function(e) { -- 2.20.1 From 0e2f63417bf8442152e237949ba78abcc52b5157 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 7 Aug 2013 14:25:27 +0200 Subject: [PATCH 15/16] Handling del/ctrl-x (wip) - handling delete key on empty text element, - blocking ctrl-x on whole element text (todo: handle like del/backspace) - handling deletion of whole element text via del/backspace --- modules/documentCanvas/canvas/canvas.js | 50 +++++++++++++++++++++---- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/modules/documentCanvas/canvas/canvas.js b/modules/documentCanvas/canvas/canvas.js index e706428..25fe689 100644 --- a/modules/documentCanvas/canvas/canvas.js +++ b/modules/documentCanvas/canvas/canvas.js @@ -174,7 +174,9 @@ $.extend(Canvas.prototype, { ARROW_UP: 38, ARROW_RIGHT: 39, ARROW_DOWN: 40, - BACKSPACE: 8 + BACKSPACE: 8, + DELETE: 46, + X: 88 } this.wrapper.on('keyup', function(e) { @@ -198,7 +200,8 @@ $.extend(Canvas.prototype, { }); this.wrapper.on('keydown', function(e) { - var position = canvas.getCursor().getPosition(), + var cursor = canvas.getCursor(), + position = canvas.getCursor().getPosition(), element = position.element; if(e.which >= 37 && e.which <= 40) { @@ -229,12 +232,42 @@ $.extend(Canvas.prototype, { } - if(e.which === KEYS.BACKSPACE) { - if(element.getText().length === 1) { + + var selectsWholeTextElement = function() { + if(cursor.isSelecting() && cursor.getSelectionStart().offsetAtBeginning && cursor.getSelectionEnd().offsetAtEnd) + return true; + return false; + } + + if(e.which === KEYS.X && e.ctrlKey && selectsWholeTextElement()) { + e.preventDefault(); + } + + if(e.which === KEYS.BACKSPACE || e.which === KEYS.DELETE) { + if(cursor.isSelecting() && !cursor.isSelectingWithinElement()) { + e.preventDefault(); + return; + } + + var cursorAtOperationEdge = position.offsetAtBeginning; + if(e.which === KEYS.DELETE) { + cursorAtOperationEdge = position.offsetAtEnd; + } + + if(element.getText().length === 1 || selectsWholeTextElement()) { e.preventDefault(); element.setText(''); } else if(element.isEmpty()) { + + var direction = 'above', + caretTo = 'end'; + + if(e.which === KEYS.DELETE) { + direction = 'below'; + caretTo = 'start'; + } + e.preventDefault(); var parent = element.parent(), @@ -244,17 +277,18 @@ $.extend(Canvas.prototype, { if(grandParent && grandParent.children().length === 1) { goto = grandParent.append({text: ''}); } else { - goto = canvas.getDocumentElement(utils.nearestInDocumentOrder('[document-text-element]', 'above', element.dom()[0])); + goto = canvas.getDocumentElement(utils.nearestInDocumentOrder('[document-text-element]', direction, element.dom()[0])); } parent.detach(); } else { - goto = canvas.getDocumentElement(utils.nearestInDocumentOrder('[document-text-element]', 'above', element.dom()[0])); + goto = canvas.getDocumentElement(utils.nearestInDocumentOrder('[document-text-element]', direction, element.dom()[0])); element.detach(); } - canvas.setCurrentElement(goto, {caretTo: 'end'}); + canvas.setCurrentElement(goto, {caretTo: caretTo}); } - else if(position.offset === 0) { + else if(cursorAtOperationEdge) { // todo + e.preventDefault(); } } }); -- 2.20.1 From f0a4549eb9a1adc546f31c3e8b0c5958fbc28edd Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 7 Aug 2013 15:04:24 +0200 Subject: [PATCH 16/16] FIX: white-space: pre-wrap prevents collapsing spaces in document-text-element Without it is impossible to select whole text in a document-text-element that starts with one space (because of offset == 1 on selection.api) --- modules/documentCanvas/nodes.less | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/documentCanvas/nodes.less b/modules/documentCanvas/nodes.less index 2deb807..d02e872 100644 --- a/modules/documentCanvas/nodes.less +++ b/modules/documentCanvas/nodes.less @@ -3,6 +3,7 @@ display: inline; margin: 0 1px; border: 1px solid white; + white-space: pre-wrap; } [wlxml-tag] { -- 2.20.1