second batch: works, but failing tests - ie blocking span
authorAleksander Łukasz <aleksander.lukasz@nowoczesnapolska.org.pl>
Thu, 10 Apr 2014 08:51:53 +0000 (10:51 +0200)
committerAleksander Łukasz <aleksander.lukasz@nowoczesnapolska.org.pl>
Mon, 5 May 2014 11:09:14 +0000 (13:09 +0200)
src/editor/modules/documentCanvas/canvas/canvas.js
src/editor/modules/documentCanvas/canvas/canvas.test.js
src/editor/modules/documentCanvas/canvas/documentElement.js
src/editor/modules/documentCanvas/canvas/genericElement.js [new file with mode: 0644]
src/editor/modules/documentCanvas/canvas/utils.js
src/editor/modules/documentCanvas/canvas/wlxmlListener.js
src/editor/modules/documentCanvas/documentCanvas.js
src/editor/plugins/core/canvasElements.js [new file with mode: 0644]

index c6a7c82..af1ed28 100644 (file)
@@ -3,11 +3,13 @@ define([
 'libs/underscore',
 'libs/backbone',
 'fnpjs/logging/logging',
+'wlxml/wlxml',
 'modules/documentCanvas/canvas/documentElement',
 'modules/documentCanvas/canvas/keyboard',
 'modules/documentCanvas/canvas/utils',
-'modules/documentCanvas/canvas/wlxmlListener'
-], function($, _, Backbone, logging, documentElement, keyboard, utils, wlxmlListener) {
+'modules/documentCanvas/canvas/wlxmlListener',
+'modules/documentCanvas/canvas/genericElement'
+], function($, _, Backbone, logging, wlxml, documentElement, keyboard, utils, wlxmlListener, genericElement) {
     
 'use strict';
 /* global document:false, window:false, Node:false, gettext */
@@ -54,8 +56,49 @@ $.extend(TextHandler.prototype, {
 
 });
 
+var ElementsRegister = function() {
+    this._register = {
+        '': ElementsRegister.createCanvasElementType(genericElement, documentElement.DocumentNodeElement)
+    };
+
+}
+_.extend(ElementsRegister, {
+    createCanvasElementType: function(elementPrototype, inheritFrom) {
+        var Constructor = function() {
+            if(!this.super) {
+                this.super = inheritFrom.prototype;
+            }
+            inheritFrom.apply(this, Array.prototype.slice.call(arguments, 0));
+            
+        };
+        Constructor.prototype = Object.create(inheritFrom.prototype);
+        _.extend(Constructor.prototype, elementPrototype);
+        return Constructor;
+    }
+});
+_.extend(ElementsRegister.prototype, {
+    register: function(klass, elementPrototype) {
+        this._register[klass] = ElementsRegister.createCanvasElementType(elementPrototype, this.getFactoryFor(''));
+    },
+    getFactoryFor: function(klass) {
+        var Factory;
+        wlxml.getClassHierarchy(klass).reverse().some(function(klass) {
+            Factory = this._register[klass];
+            if(Factory) {
+                return true;
+            }
+        }.bind(this));
+        return Factory;
+    }
+});
+
+
 
-var Canvas = function(wlxmlDocument) {
+var Canvas = function(wlxmlDocument, elements) {
+    this.elementsRegister = new ElementsRegister();
+    (elements || []).forEach(function(elementDesc) {
+        this.elementsRegister.register(elementDesc.klass, elementDesc.element);
+    }.bind(this));
     this.eventBus = _.extend({}, Backbone.Events);
     this.wrapper = $('<div>').addClass('canvas-wrapper').attr('contenteditable', true);
     this.wlxmlListener = wlxmlListener.create(this);
@@ -77,7 +120,15 @@ $.extend(Canvas.prototype, Backbone.Events, {
     },
 
     createElement: function(wlxmlNode) {
-        var Factory = wlxmlNode.nodeType === Node.TEXT_NODE ?  documentElement.DocumentTextElement : documentElement.factoryForTag(wlxmlNode.getTagName());
+        var Factory;
+        if(wlxmlNode.nodeType === Node.TEXT_NODE) {
+            Factory = documentElement.DocumentTextElement
+        } else {
+            if(wlxmlNode.getClass() === 'p') {
+               // debugger;
+            }
+            Factory = this.elementsRegister.getFactoryFor(wlxmlNode.getClass());
+        }
         return new Factory(wlxmlNode, this);
     },
 
@@ -558,8 +609,8 @@ $.extend(Cursor.prototype, {
 });
 
 return {
-    fromXMLDocument: function(wlxmlDocument) {
-        return new Canvas(wlxmlDocument);
+    fromXMLDocument: function(wlxmlDocument, elements) {
+        return new Canvas(wlxmlDocument, elements);
     }
 };
 
index 642438a..8e3c1c8 100644 (file)
@@ -4,7 +4,7 @@ define([
 'libs/sinon',
 'modules/documentCanvas/canvas/canvas',
 'modules/documentCanvas/canvas/utils',
-'wlxml/wlxml'
+'wlxml/wlxml',
 ], function($, chai, sinon, canvas, utils, wlxml) {
     
 'use strict';
@@ -13,7 +13,7 @@ define([
 var expect = chai.expect;
 
 var getCanvasFromXML = function(xml) {
-    return canvas.fromXMLDocument(getDocumentFromXML(xml));
+    return canvas.fromXMLDocument(getDocumentFromXML(xml), null);
 };
 
 var getDocumentFromXML = function(xml) {
@@ -26,6 +26,17 @@ var wait = function(callback, timeout) {
 };
 
 
+describe('wtf', function() {
+    it('wtf!', function() {
+        var c = getCanvasFromXML('<section>Alice</section>'),
+            doc = c.wlxmlDocument;
+
+        var txtNode = doc.root.contents()[0];
+        txtNode.wrapWith({tagName: 'header', start: 1, end: 2});
+        expect(c.doc().children().length).to.equal(3);
+    });
+})
+
 describe('new Canvas', function() {
     it('abc', function() {
         var doc = wlxml.WLXMLDocumentFromXML('<section>Alice <span>has</span> a cat!</div>'),
@@ -70,11 +81,12 @@ describe('Listening to document changes', function() {
             b = doc.root.contents()[1],
             c = canvas.fromXMLDocument(doc);
 
+        debugger;
         a.before(b);
         var sectionChildren = c.doc().children();
         expect(sectionChildren.length).to.equal(2);
-        expect(sectionChildren[0].getWlxmlTag()).to.equal('b');
-        expect(sectionChildren[1].getWlxmlTag()).to.equal('a');
+        expect(sectionChildren[0].wlxmlNode.getTagName()).to.equal('b');
+        expect(sectionChildren[1].wlxmlNode.getTagName()).to.equal('a');
     });
 
     it('Handling text node moved', function() {
@@ -87,7 +99,7 @@ describe('Listening to document changes', function() {
         var sectionChildren = c.doc().children();
         expect(sectionChildren.length).to.equal(2);
         expect(sectionChildren[0].getText()).to.equal('Alice');
-        expect(sectionChildren[1].getWlxmlTag()).to.equal('a');
+        expect(sectionChildren[1].wlxmlNode.getTagName()).to.equal('a');
     });
 
     it('Handles nodeTagChange event', function() {
@@ -100,7 +112,7 @@ describe('Listening to document changes', function() {
         var headerNode = doc.root.contents()[0],
             headerElement = c.doc().children()[0];
 
-        expect(headerElement.getWlxmlTag()).to.equal('header', 'element ok');
+        expect(headerElement.wlxmlNode.getTagName()).to.equal('header', 'element ok');
 
         /* Make sure we handle invalidation of reference to wlxmlNode after changing its tag */
         expect(headerNode.getData('canvasElement').sameNode(headerElement)).to.equal(true, 'node->element');
@@ -122,7 +134,7 @@ describe('Listening to document changes', function() {
             expect(aTextElement.getText({raw:true})).to.equal(utils.unicode.ZWS, 'canvas represents this as empty node');
             aTextElement.wlxmlNode.detach();
             expect(parent.children().length).to.equal(1);
-            expect(parent.children()[0].getWlxmlTag()).to.equal('span');
+            expect(parent.children()[0].wlxmlNode.getTagName()).to.equal('span');
             done();
         });
     });
@@ -167,6 +179,104 @@ describe('Displaying span nodes', function() {
     });
 });
 
+
+describe('Default document changes handling', function() {
+    it('handles added node', function() {
+        var c = getCanvasFromXML('<section></section>');
+        c.wlxmlDocument.root.append({tagName:'div'});
+        expect(c.doc().children().length).to.equal(1);
+        c.wlxmlDocument.root.prepend({tagName:'div'});
+        expect(c.doc().children().length).to.equal(2);
+
+        var node = c.wlxmlDocument.root.contents()[1];
+        node.before({tagName: 'div'});
+        expect(c.doc().children().length).to.equal(3);
+        node.after({tagName: 'div'});
+        expect(c.doc().children().length).to.equal(4);
+    });
+
+    it('handles attribute value change for a class attribute', function() {
+        var c = getCanvasFromXML('<section></section>');
+        c.wlxmlDocument.root.setAttr('class', 'test');
+        expect(c.doc().wlxmlNode.getClass()).to.equal('test');
+    });
+
+    it('handles detached node', function() {
+        var c = getCanvasFromXML('<section><div></div></section>');
+        c.wlxmlDocument.root.contents()[0].detach();
+        expect(c.doc().children().length).to.equal(0);
+    });
+
+    it('handles moved node', function() {
+        var doc = getDocumentFromXML('<section><a></a><b></b></section>'),
+            a = doc.root.contents()[0],
+            b = doc.root.contents()[1],
+            c = canvas.fromXMLDocument(doc);
+
+        a.before(b);
+        var sectionChildren = c.doc().children();
+        expect(sectionChildren.length).to.equal(2);
+        expect(sectionChildren[0].wlxmlNode.getTagName()).to.equal('b');
+        expect(sectionChildren[1].wlxmlNode.getTagName()).to.equal('a');
+    });
+
+    it('handles change in a text node', function() {
+        var c = getCanvasFromXML('<section>Alice</section>');
+        c.wlxmlDocument.root.contents()[0].setText('cat');
+        expect(c.doc().children()[0].getText()).to.equal('cat');
+    });
+});
+    
+describe('Custom elements based on wlxml class attribute', function() {
+    it('allows custom rendering', function() {
+        var c = getCanvasFromXML('<section><div class="testClass"></div></section>', {
+            testClass: {
+                init: function() {
+                    debugger;
+                    this.dom.append('<test></test>');
+                }
+            }
+        });
+        expect(c.doc().children()[0]._container().children('test').length).to.equal(1); // @!
+    });
+
+    it('allows handling changes to internal structure of rendered node', function() {
+        var c = getCanvasFromXML('<section><div class="testClass"><a></a></div></section>', {
+            testClass: {
+                init: function() {
+                    this.header = $('<h1>');
+                    this.dom.append(this.header);
+                    this.refresh2();
+                },
+                refresh2: function() {
+                    this.header.text(this.el.wlxmlNode.contents().length);
+                },
+                onNodeAdded: function(event) {
+                    void(event);
+                    this.refresh2();
+                }
+            }
+        });
+
+        var node = c.wlxmlDocument.root.contents()[0],
+            element = node.getData('canvasElement');
+
+        var header = element.dom().find('h1');
+        expect(header.text()).to.equal('1', 'just <a>');
+
+        node.append({tagName: 'div'});
+
+        expect(header.text()).to.equal('2', 'added div');
+    });
+
+    describe('Handling unknown class', function() {
+        it('Inherits default behavior', function() {
+            var c = getCanvasFromXML('<section><div class="unknown">Hi!</div></section>');
+            expect(c.doc().children()[0].children()[0].getText()).to.equal('Hi!');
+        });
+    });
+});
+
 describe('Cursor', function() {
     /* globals Node */
     var getSelection;
index 2265f92..283f873 100644 (file)
@@ -1,20 +1,18 @@
 define([
 'libs/jquery',
 'libs/underscore',
-'modules/documentCanvas/canvas/utils',
-'modules/documentCanvas/canvas/wlxmlManagers'
-], function($, _, utils, wlxmlManagers) {
+'modules/documentCanvas/canvas/utils'
+], function($, _, utils) {
     
 'use strict';
 /* global Node:false, document:false */
 
-
 // DocumentElement represents a text or an element node from WLXML document rendered inside Canvas
 var DocumentElement = function(wlxmlNode, canvas) {
     this.wlxmlNode = wlxmlNode;
     this.canvas = canvas;
 
-    this.$element = this.createDOM();
+    this.createDOM();
     this.$element.data('canvas-element', this);
 };
 
@@ -56,22 +54,6 @@ $.extend(DocumentElement.prototype, {
         return other && (typeof other === typeof this) && other.dom()[0] === this.dom()[0];
     },
 
-    getVerticallyFirstTextElement: function() {
-        var toret;
-        this.children().some(function(child) {
-            if(child instanceof DocumentTextElement) {
-                toret = child;
-                return true; // break
-            } else {
-                toret = child.getVerticallyFirstTextElement();
-                if(toret) {
-                    return true; // break
-                }
-            }
-        });
-        return toret;
-    },
-
     getPreviousTextElement: function(includeInvisible) {
         return this.getNearestTextElement('above', includeInvisible);
     },
@@ -86,11 +68,11 @@ $.extend(DocumentElement.prototype, {
         return this.canvas.getDocumentElement(utils.nearestInDocumentOrder(selector, direction, this.dom()[0]));
     },
 
-    exec: function(method) {
-        if(this.manager && this.manager[method]) {
-            return this.manager[method].apply(this.manager, Array.prototype.slice.call(arguments, 1));
-        }
+    trigger: function() {
+        //this.canvas.bus.trigger()
     }
+
+
 });
 
 
@@ -98,6 +80,10 @@ $.extend(DocumentElement.prototype, {
 var DocumentNodeElement = function(wlxmlNode, canvas) {
     DocumentElement.call(this, wlxmlNode, canvas);
     wlxmlNode.setData('canvasElement', this);
+    if(this.init) {
+        this.init();
+    }
+
 };
 
 
@@ -108,8 +94,7 @@ var manipulate = function(e, params, action) {
     } else {
         element = e.canvas.createElement(params);
     }
-    var target = (action === 'append' || action === 'prepend') ? e._container() : e.dom();
-    target[action](element.dom());
+    e.dom()[action](element.dom());
     e.refreshPath();
     return element;
 };
@@ -118,33 +103,31 @@ DocumentNodeElement.prototype = Object.create(DocumentElement.prototype);
 
 
 $.extend(DocumentNodeElement.prototype, {
-    init: function() {
-        // noo[]
+    defaultDisplayStyle: 'block',
+    addWidget: function(widget) {
+        this.$element.children('.canvas-widgets').append(widget.DOM ? widget.DOM : widget);
+    },
+    clearWidgets: function() {
+        this.$element.children('.canvas-widgets').empty();
+    },
+    handle: function(event) {
+        var method = 'on' + event.type[0].toUpperCase() + event.type.substr(1);
+        if(this[method]) {
+            this[method](event);
+        }
     },
     createDOM: function() {
-        var dom = $('<div>')
-                .attr('document-node-element', ''),
+        var wrapper = $('<div>').attr('document-node-element', ''),
             widgetsContainer = $('<div>')
                 .addClass('canvas-widgets')
                 .attr('contenteditable', false),
-            container = $('<div>')
+            contentContainer = $('<div>')
                 .attr('document-element-content', '');
         
-        dom.append(widgetsContainer, container);
-        // Make sure widgets aren't navigable with arrow keys
+        wrapper.append(widgetsContainer, contentContainer);
         widgetsContainer.find('*').add(widgetsContainer).attr('tabindex', -1);
-        this.$element = dom; //@!!!
-
-        this.setWlxmlTag(this.wlxmlNode.getTagName());
-        this.setWlxmlClass(this.wlxmlNode.getClass());
-
-        this.wlxmlNode.contents().forEach(function(node) {
-            container.append(this.canvas.createElement(node).dom());
-        }.bind(this));
-
-        this.init();
-
-        return dom;
+        this.$element = wrapper;
+        this.displayAs(this.defaultDisplayStyle);
     },
     _container: function() {
         return this.dom().children('[document-element-content]');
@@ -158,12 +141,6 @@ $.extend(DocumentNodeElement.prototype, {
         }
          return this;
     },
-    append: function(params) {
-        return manipulate(this, params, 'append');
-    },
-    prepend: function(params) {
-        return manipulate(this, params, 'prepend');
-    },
     before: function(params) {
         return manipulate(this, params, 'before');
 
@@ -171,63 +148,7 @@ $.extend(DocumentNodeElement.prototype, {
     after: function(params) {
         return manipulate(this, params, 'after');
     },
-    children: function() {
-        var toret = [];
-        if(this instanceof DocumentTextElement) {
-            return toret;
-        }
-
 
-        var elementContent = this._container().contents();
-        var element = this;
-        elementContent.each(function() {
-            var childElement = element.canvas.getDocumentElement(this);
-            if(childElement === undefined) {
-                return true;
-            }
-            toret.push(childElement);
-        });
-        return toret;
-    },
-    childIndex: function(child) {
-        var children = this.children(),
-            toret = null;
-        children.forEach(function(c, idx) {
-            if(c.sameNode(child)) {
-                toret = idx;
-                return false;
-            }
-        });
-        return toret;
-    },
-    getWlxmlTag: function() {
-        return this._container().attr('wlxml-tag');
-    },
-    setWlxmlTag: function(tag) {
-        this._container().attr('wlxml-tag', tag);
-    },
-    getWlxmlClass: function() {
-        var klass = this._container().attr('wlxml-class');
-        if(klass) {
-            return klass.replace(/-/g, '.');
-        }
-        return undefined;
-    },
-    setWlxmlClass: function(klass) {
-        if(klass === this.getWlxmlClass()) {
-            return;
-        }
-        if(klass) {
-            this._container().attr('wlxml-class', klass.replace(/\./g, '-'));
-        }
-        else {
-            this._container().removeAttr('wlxml-class');
-        }
-        this.manager = wlxmlManagers.getFor(this);
-        this.manager.setup();
-
-        this.refreshPath();
-    },
     toggleLabel: function(toggle) {
         var displayCss = toggle ? 'inline-block' : 'none';
         var label = this.dom().children('.canvas-widgets').find('.canvas-widget-label');
@@ -239,30 +160,10 @@ $.extend(DocumentNodeElement.prototype, {
         this._container().toggleClass('highlighted-element', toggle);
     },
 
-    toggle: function(toggle) {
-        if(this.manager) {
-            this.manager.toggle(toggle);
-        }
-    },
-
     isBlock: function() {
         return this.dom().css('display') === 'block';
     },
 
-    containsBlock: function() {
-        return this.children()
-            .filter(function(child) {
-                return child instanceof DocumentNodeElement;
-            })
-            .some(function(child) {
-                if(child.isBlock()) {
-                    return true;
-                } else {
-                    return child.containsBlock();
-                }
-            });
-    },
-
     displayAsBlock: function() {
         this.dom().css('display', 'block');
         this._container().css('display', 'block');
@@ -270,6 +171,18 @@ $.extend(DocumentNodeElement.prototype, {
     displayInline: function() {
         this.dom().css('display', 'inline');
         this._container().css('display', 'inline');
+    },
+    displayAs: function(what) {
+        // [this.dom(), this._container()].forEach(e) {
+        //     var isBlock = window.getComputedStyle(e).display === 'block';
+        //     if(!isBlock && what === 'block') {
+        //         e.css('display', what);
+        //     } else if(isBlock && what === 'inline') {
+        //         e.css('display')
+        //     }
+        // })
+        this.dom().css('display', what);
+        this._container().css('display', what);   
     }
 });
 
@@ -289,7 +202,7 @@ DocumentTextElement.prototype = Object.create(DocumentElement.prototype);
 
 $.extend(DocumentTextElement.prototype, {
     createDOM: function() {
-        return $('<div>')
+        this.$element = $('<div>')
             .attr('document-text-element', '')
             .text(this.wlxmlNode.getText() || utils.unicode.ZWS);
     },
@@ -348,6 +261,9 @@ $.extend(DocumentTextElement.prototype, {
 
     toggleHighlight: function() {
         // do nothing for now
+    },
+    children: function() {
+        return [];
     }
 
 });
@@ -356,6 +272,7 @@ var SpanElement = function() {
     DocumentNodeElement.apply(this, Array.prototype.slice.call(arguments, 0));
 };
 SpanElement.prototype = $.extend(Object.create(DocumentNodeElement.prototype), {
+    defaultDisplayStyle: 'inline',
     init: function() {
         if(this.containsBlock()) {
             this.displayAsBlock();
diff --git a/src/editor/modules/documentCanvas/canvas/genericElement.js b/src/editor/modules/documentCanvas/canvas/genericElement.js
new file mode 100644 (file)
index 0000000..7d5d986
--- /dev/null
@@ -0,0 +1,182 @@
+define(function(require) {
+    
+'use strict';
+
+var documentElement = require('./documentElement'),
+    utils = require('./utils'),
+    wlxmlUtils = require('utils/wlxml');
+
+var labelWidget = function(tag, klass) {
+    return $('<span>')
+        .addClass('canvas-widget canvas-widget-label')
+        .text(wlxmlUtils.getTagLabel(tag) + (klass ? ' / ' + wlxmlUtils.getClassLabel(klass) : ''));
+};
+void(labelWidget); // for linters; labelWidget is unused on purpose for now
+
+
+var generic = {
+    onNodeAttrChange: function(event) {
+        if(event.meta.attr === 'class') {
+            this.setWlxmlClass(event.meta.newVal); //
+        }
+    },
+    onNodeAdded: function(event) {
+        if(event.meta.node.isRoot()) {
+            this.canvas.reloadRoot();
+            return;
+        }
+
+        var nodeIndex = event.meta.node.getIndex(),
+            referenceElement, referenceAction, actionArg;
+
+        if(nodeIndex === 0) {
+            referenceElement = this;
+            referenceAction = 'prepend';
+        } else {
+            referenceElement = this.children()[nodeIndex-1];
+            referenceAction = 'after';
+        }
+
+        actionArg = (event.type === 'nodeMoved' && utils.findCanvasElement(event.meta.node, event.meta.parent)) || event.meta.node;
+        referenceElement[referenceAction](actionArg);
+    },
+    onNodeMoved: function(event) {
+        return this.onNodeAdded.call(this, event, true);
+    },
+    onNodeDetached: function(event) {
+        if(event.meta.node.sameNode(this)) {
+            this.detach();
+        } else {
+            this.children().some(function(child) {
+                if(child.wlxmlNode.sameNode(event.meta.node)) {
+                    child.detach();
+                    return true;
+                }
+            });
+        }
+    },
+    onNodeTextChange: function(event) {
+        var toSet = event.meta.node.getText();
+        this.children().some(function(child) {
+            if(child.wlxmlNode.sameNode(event.meta.node)) {
+                if(toSet === '') {
+                    toSet = utils.unicode.ZWS;
+                }
+                if(toSet !== child.getText()) {
+                    child.setText(toSet);
+                }
+                return true;
+            }
+        });
+    },
+
+    prepend: function(param) {
+        var element;
+        if(param instanceof documentElement.DocumentElement) {
+            element = param;
+        } else {
+            element = this.canvas.createElement(param);
+        }
+        this._container().prepend(element.dom());
+        this.refreshPath();
+        return element;
+    },
+
+    children: function() {
+        var element = this,
+            toret = [];
+        this._container().contents().each(function() {
+            var childElement = element.canvas.getDocumentElement(this);
+            if(childElement === undefined) {
+                return true;
+            }
+
+            toret.push(childElement);
+        });
+        return toret;
+    },
+
+    getFirst: function(e1, e2) {
+        var idx1 = this.childIndex(e1),
+            idx2 = this.childIndex(e2);
+        if(e1 === null || e2 === null) {
+            return undefined;
+        }
+        return idx1 <= idx2 ? e1: e2;
+    },
+
+    childIndex: function(child) {
+        var children = this.children(),
+            toret = null;
+        children.forEach(function(c, idx) {
+            if(c.sameNode(child)) {
+                toret = idx;
+                return false;
+            }
+        });
+        return toret;
+    },
+
+    getWlxmlClass: function() {
+        var klass = this._container().attr('wlxml-class');
+        if(klass) {
+            return klass.replace(/-/g, '.');
+        }
+        return undefined;
+    },
+    setWlxmlClass: function(klass) {
+        if(klass === this.getWlxmlClass()) {
+            return;
+        }
+        if(klass) {
+            this._container().attr('wlxml-class', klass.replace(/\./g, '-'));
+        }
+        else {
+            this._container().removeAttr('wlxml-class');
+        }
+        this.refreshPath();
+    },
+    init: function() {
+        this._container()
+            .attr('wlxml-tag', this.wlxmlNode.getTagName());
+        this.setWlxmlClass(this.wlxmlNode.getClass());
+        this.wlxmlNode.contents().forEach(function(node) {
+            this._container().append(this.canvas.createElement(node).dom());
+        }.bind(this));
+    },
+    containsBlock: function() {
+        return this.children()
+            .filter(function(child) {
+                return child instanceof documentElement.DocumentNodeElement;
+            })
+            .some(function(child) {
+                if(child.isBlock()) {
+                    return true;
+                } else {
+                    return child.containsBlock();
+                }
+            });
+    },
+    getVerticallyFirstTextElement: function() {
+        var toret;
+        this.children().some(function(child) {
+            if(child instanceof documentElement.DocumentTextElement) {
+                toret = child;
+                return true; // break
+            } else {
+                toret = child.getVerticallyFirstTextElement();
+                if(toret) {
+                    return true; // break
+                }
+            }
+        });
+        return toret;
+    },
+};
+
+
+return generic;
+
+
+
+});
\ No newline at end of file
index 56093f6..89ed192 100644 (file)
@@ -53,13 +53,35 @@ var findCanvasElementInParent = function(wlxmlChildNode, wlxmlParentNode) {
     return toret;
 };
 
+var getElementForNode = function(node) {
+
+    var ptr = node.nodeType === Node.TEXT_NODE ? node.parent() : node;
+    while(!ptr.getData('canvasElement')) {
+        ptr = ptr.parent();
+    }
+    return ptr.getData('canvasElement');
+};
+
+var getElementForDetachedNode = function(node, originalParent) {
+    var ptr = originalParent;
+    if(ptr === null) {
+        return node.getData('canvasElement');
+    }
+    while(!ptr.getData('canvasElement')) {
+        ptr = ptr.parent();
+    }
+    return ptr.getData('canvasElement');
+};
+
 return {
     nearestInDocumentOrder: nearestInDocumentOrder,
     findCanvasElement: findCanvasElement,
     findCanvasElementInParent: findCanvasElementInParent,
     unicode: {
         ZWS: '\u200B'
-    }
+    },
+    getElementForNode: getElementForNode,
+    getElementForDetachedNode: getElementForDetachedNode
 };
 
 });
index 7d19e94..faee6c8 100644 (file)
@@ -35,54 +35,45 @@ $.extend(Listener.prototype, {
 
 
 var _metadataEventHandler = function(event) {
-    var canvasNode = utils.findCanvasElement(event.meta.node);
-    canvasNode.exec('updateMetadata');
+    var element = utils.getElementForNode(event.meta.node);
+    element.handle(event);
 };
 
+
 var handlers = {
     nodeAttrChange: function(event) {
+        var element = utils.getElementForNode(event.meta.node),
+            objectChanged;
         if(event.meta.attr === 'class') {
-            var canvasNode = utils.findCanvasElement(event.meta.node);
-            canvasNode.setWlxmlClass(event.meta.newVal);
+            objectChanged = element.updateObject();
+        }
+
+        if(!objectChanged) {
+            element.handle(event);
         }
     },
-    nodeAdded: function(event, checkForExistence) {
+    nodeAdded: function(event) {
         if(event.meta.node.isRoot()) {
             this.canvas.reloadRoot();
             return;
         }
-        var parentElement = utils.findCanvasElement(event.meta.node.parent()),
-            nodeIndex = event.meta.node.getIndex(),
-            referenceElement, referenceAction, actionArg;
 
-        if(nodeIndex === 0) {
-            referenceElement = parentElement;
-            referenceAction = 'prepend';
-        } else {
-            referenceElement = parentElement.children()[nodeIndex-1];
-            referenceAction = 'after';
-        }
+        var containingNode = event.meta.node.parent(),
+            containingElement = utils.getElementForNode(containingNode);
 
-        actionArg = (checkForExistence && utils.findCanvasElement(event.meta.node, event.meta.parent)) || event.meta.node;
-        referenceElement[referenceAction](actionArg);
+        containingElement.handle(event);
     },
     nodeMoved: function(event) {
-        return handlers.nodeAdded.call(this, event, true);
+        return handlers.nodeAdded.call(this, event, true); //
+        //
     },
     nodeDetached: function(event) {
-        var canvasNode = utils.findCanvasElementInParent(event.meta.node, event.meta.parent);
-        canvasNode.detach();
+        var element = utils.getElementForDetachedNode(event.meta.node, event.meta.parent);
+        element.handle(event);
     },
     nodeTextChange: function(event) {
-        //console.log('wlxmlListener: ' + event.meta.node.getText());
-        var canvasElement = utils.findCanvasElement(event.meta.node),
-            toSet = event.meta.node.getText();
-        if(toSet === '') {
-            toSet = utils.unicode.ZWS;
-        }
-        if(toSet !== canvasElement.getText()) {
-            canvasElement.setText(toSet);
-        }
+        var element = utils.getElementForNode(event.meta.node.parent());
+        element.handle(event);
     },
 
     metadataChanged: _metadataEventHandler,
index 03b0cf7..2cb3fa1 100644 (file)
@@ -14,9 +14,9 @@ var logger = logging.getLogger('documentCanvas');
 
 return function(sandbox) {
 
-    var canvasElements;
+    var canvasElements = [];
 
-    sandbox.getPlugins(function(plugin) {
+    sandbox.getPlugins().forEach(function(plugin) {
         canvasElements = canvasElements.concat(plugin.canvasElements || []);
     });
 
diff --git a/src/editor/plugins/core/canvasElements.js b/src/editor/plugins/core/canvasElements.js
new file mode 100644 (file)
index 0000000..37eefb8
--- /dev/null
@@ -0,0 +1,114 @@
+define(function(require) {
+    
+'use strict';
+
+
+var widgets = {
+    footnoteHandler: function(clickHandler) {
+        var mydom = $('<span>')
+            .addClass('canvas-widget canvas-widget-footnote-handle')
+            .css('display', 'inline')
+            .show();
+
+        mydom.click(function(e) {
+            e.stopPropagation();
+            clickHandler();
+        });
+
+        return mydom;
+    },
+    commentAdnotation: function(node) {
+        var widget = {
+            DOM: $('<div>').addClass('canvas-widget canvas-widget-comment-adnotation'),
+            update: function(node) {
+                var parts = [],
+                    metadata = node.getMetadata(),
+                    dt;
+                metadata.forEach(function(row) {
+                    parts.push(row.getValue());
+                }, 'creator');
+                metadata.some(function(row) {
+                    dt = row.getValue();
+                    return true; // break
+                }, 'date');
+                if(dt) {
+                    parts.push(dt);
+                }
+                this.DOM.text(parts.join(', '));
+            }
+        };
+        widget.update(node);
+        return widget;
+    },
+    hideButton: function(clickHandler) {
+        var mydom = $('<span>x</span>')
+            .addClass('canvas-widget canvas-widget-hide-button');
+        mydom.click(function(e) {
+            e.stopPropagation();
+            clickHandler();
+        });
+        return mydom;
+    }
+}
+
+
+var comment = {
+    init: function() {
+        this.super.init.call(this);
+        this.commentAdnotation = widgets.commentAdnotation(this.wlxmlNode);
+        this.addWidget(this.commentAdnotation, 'show');
+        this.commentAdnotation.DOM.show();
+    },
+
+    onMetadataChanged: function(event) {
+        this.commentAdnotation.update(event.meta.node);
+    },
+    onMetadataAdded: function(event) {
+        return this.onMetadataChanged(event);
+    },
+    onMetadataRemoved: function(event) {
+        return this.onMetadataChanged(event);
+    }
+};
+
+var footnote = {
+    init: function() {
+        this.super.init.call(this);
+        var clickHandler = function() {
+            this.toggle(true);
+        }.bind(this);
+        this.footnoteHandler = widgets.footnoteHandler(clickHandler);
+        this.addWidget(this.footnoteHandler);
+
+        var closeHandler = function() {
+            this.toggle(false);
+        }.bind(this);
+        this.hideButton = widgets.hideButton(closeHandler);
+        this.addWidget(this.hideButton);
+        this.toggle(false, {silent: true});
+    },
+    toggle: function(toggle, options) {
+        options = options || {};
+        this.hideButton.toggle(toggle);
+        this.footnoteHandler.toggle(!toggle);
+        
+        if(toggle) {
+            this.displayAsBlock();
+        } else {
+            this.displayInline();
+        }
+        this._container().toggle(toggle);
+        if(!options.silent) {
+            this.trigger('elementToggled', toggle, this.documentElement);
+        }
+    }
+}
+
+
+return [
+    {klass: 'comment', element: comment},
+    {klass: 'footnote', element: footnote}
+];
+
+
+});
\ No newline at end of file