'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/elementsRegister',
+'modules/documentCanvas/canvas/genericElement',
+], function($, _, Backbone, logging, documentElement, keyboard, utils, wlxmlListener, ElementsRegister, genericElement) {
'use strict';
/* global document:false, window:false, Node:false, gettext */
});
-var Canvas = function(wlxmlDocument) {
+var Canvas = function(wlxmlDocument, elements) {
+ this.elementsRegister = new ElementsRegister(documentElement.DocumentNodeElement);
+
+ elements = [
+ {tag: 'section', klass: null, prototype: genericElement},
+ {tag: 'div', klass: null, prototype: genericElement},
+ {tag: 'header', klass: null, prototype: genericElement},
+ {tag: 'span', klass: null, prototype: genericElement},
+ {tag: 'aside', klass: null, prototype: genericElement}
+ ].concat(elements || []);
+
+ (elements).forEach(function(elementDesc) {
+ this.elementsRegister.register(elementDesc);
+ }.bind(this));
this.eventBus = _.extend({}, Backbone.Events);
this.wrapper = $('<div>').addClass('canvas-wrapper').attr('contenteditable', true);
this.wlxmlListener = wlxmlListener.create(this);
},
createElement: function(wlxmlNode) {
- var Factory = wlxmlNode.nodeType === Node.TEXT_NODE ? documentElement.DocumentTextElement : documentElement.DocumentNodeElement;
+ var Factory;
+ if(wlxmlNode.nodeType === Node.TEXT_NODE) {
+ Factory = documentElement.DocumentTextElement;
+ } else {
+ Factory = this.elementsRegister.getElement({tag: wlxmlNode.getTagName(), klass: wlxmlNode.getClass()});
+ }
return new Factory(wlxmlNode, this);
},
reloadRoot: function() {
this.rootElement = this.createElement(this.wlxmlDocument.root);
this.wrapper.empty();
- this.wrapper.append(this.rootElement.dom());
+ this.wrapper.append(this.rootElement.dom);
},
setupEventHandling: function() {
this.eventBus.on('elementToggled', function(toggle, element) {
if(!toggle) {
- canvas.setCurrentElement(element.getPreviousTextElement());
+ canvas.setCurrentElement(canvas.getPreviousTextElement(element));
}
});
},
},
toggleElementHighlight: function(node, toggle) {
- var element = utils.findCanvasElement(node);
+ var element = utils.getElementForNode(node);
element.toggleHighlight(toggle);
},
}
},
+ getPreviousTextElement: function(relativeToElement, includeInvisible) {
+ return this.getNearestTextElement('above', relativeToElement, includeInvisible);
+ },
+
+ getNextTextElement: function(relativeToElement, includeInvisible) {
+ return this.getNearestTextElement('below', relativeToElement, includeInvisible);
+ },
+
+ getNearestTextElement: function(direction, relativeToElement, includeInvisible) {
+ includeInvisible = includeInvisible !== undefined ? includeInvisible : false;
+ var selector = '[document-text-element]' + (includeInvisible ? '' : ':visible');
+ return this.getDocumentElement(utils.nearestInDocumentOrder(selector, direction, relativeToElement.dom[0]));
+ },
+
contains: function(element) {
- return element.dom().parents().index(this.wrapper) !== -1;
+ return element.dom.parents().index(this.wrapper) !== -1;
},
triggerSelectionChanged: function() {
}
if(!(element instanceof documentElement.DocumentElement)) {
- element = utils.findCanvasElement(element);
+ element = utils.getElementForNode(element);
}
if(!element || !this.contains(element)) {
if(byBrowser && byBrowser.parent().sameNode(nodeToLand)) {
return byBrowser;
}
- var children = e.children();
- for(var i = 0; i < children.length; i++) {
- if(children[i] instanceof documentElement.DocumentTextElement) {
- return children[i];
- }
- }
- return null;
+ return e.getVerticallyFirstTextElement();
}.bind(this);
var _markAsCurrent = function(element) {
if(element instanceof documentElement.DocumentTextElement) {
this.wrapper.find('.current-text-element').removeClass('current-text-element');
- element.dom().addClass('current-text-element');
+ element.dom.addClass('current-text-element');
} else {
this.wrapper.find('.current-node-element').removeClass('current-node-element');
element._container().addClass('current-node-element');
_moveCaretToTextElement: function(element, where) {
var range = document.createRange(),
- node = element.dom().contents()[0];
+ node = element.dom.contents()[0];
if(typeof where !== 'number') {
range.selectNodeContents(node);
}
},
- findCanvasElement: function(node) {
- return utils.findCanvasElement(node);
- },
-
toggleGrid: function() {
this.wrapper.toggleClass('grid-on');
this.trigger('changed');
if(selection.anchorNode === selection.focusNode) {
anchorFirst = selection.anchorOffset <= selection.focusOffset;
} else {
- anchorFirst = parent.childIndex(anchorElement) < parent.childIndex(focusElement);
+ anchorFirst = (parent.getFirst(anchorElement, focusElement) === anchorElement);
}
placeData = getPlaceData(anchorFirst);
} else {
});
return {
- fromXMLDocument: function(wlxmlDocument) {
- return new Canvas(wlxmlDocument);
+ fromXMLDocument: function(wlxmlDocument, elements) {
+ return new Canvas(wlxmlDocument, elements);
}
};
'libs/sinon',
'modules/documentCanvas/canvas/canvas',
'modules/documentCanvas/canvas/utils',
-'wlxml/wlxml'
-], function($, chai, sinon, canvas, utils, wlxml) {
+'modules/documentCanvas/canvas/documentElement',
+'wlxml/wlxml',
+], function($, chai, sinon, canvas, utils, documentElement, wlxml) {
'use strict';
/* global describe, it, beforeEach, afterEach */
var expect = chai.expect;
-var getCanvasFromXML = function(xml) {
- return canvas.fromXMLDocument(getDocumentFromXML(xml));
+var getCanvasFromXML = function(xml, elements) {
+ return canvas.fromXMLDocument(getDocumentFromXML(xml), elements);
};
var getDocumentFromXML = function(xml) {
};
+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>'),
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() {
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() {
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');
aTextElement;
canvas.fromXMLDocument(doc);
- aTextElement = utils.findCanvasElementInParent(aTextNode, aTextNode.parent()); // TODO: This really should be easier...
+ aTextElement = utils.getElementForNode(aTextNode);
aTextElement.setText('');
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();
});
});
});
+describe('Displaying span nodes', function() {
+ it('inlines a span element with a text', function() {
+ var c = getCanvasFromXML('<section><span>Alice</span></section>'),
+ spanElement = c.doc().children()[0];
+ expect(spanElement.isBlock()).to.equal(false);
+ });
+ it('renders non-span element as a block', function() {
+ var c = getCanvasFromXML('<section><span></span></section>'),
+ element = c.doc().children()[0],
+ node = element.wlxmlNode;
+
+ expect(element.isBlock()).to.equal(false, 'initially inline');
+ node = node.setTag('div');
+ expect(node.getData('canvasElement').isBlock()).to.equal(true, 'block');
+ });
+
+ it('inlines a span element if its block content gets removed', function() {
+ var c = getCanvasFromXML('<section><span>Alice <div>has</div> a cat!</span></section>'),
+ spanElement = c.doc().children()[0],
+ divNode = spanElement.wlxmlNode.contents()[1];
+
+ expect(spanElement.isBlock()).to.equal(true, 'initially a block');
+ divNode.detach();
+ expect(spanElement.isBlock()).to.equal(false, 'inlined after removing inner block');
+
+ spanElement.wlxmlNode.append({tagName: 'div'});
+
+ expect(spanElement.isBlock()).to.equal(true, 'block again after bringing back inner block');
+ });
+
+ it('keeps showing element as a block after changing its node tag to span if it contains elements of non-span nodes', function() {
+ var c = getCanvasFromXML('<section><div><div></div></div></section>'),
+ outerDivElement = c.doc().children()[0],
+ outerDivNode = outerDivElement.wlxmlNode;
+ outerDivNode = outerDivNode.setTag('span');
+ expect(c.doc().children()[0].isBlock()).to.equal(true);
+ });
+});
+
+
+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 moving text node to another parent', function() {
+ var c = getCanvasFromXML('<section>Alice<div><span>has</span></div>a cat.</section>'),
+ doc = c.wlxmlDocument,
+ text = doc.root.contents()[0],
+ div = doc.root.contents()[1];
+
+ div.append(text);
+
+ var sectionChildren = c.doc().children();
+ expect(sectionChildren.length).to.equal(2);
+ expect(sectionChildren[0].wlxmlNode.sameNode(div)).to.equal(true);
+ expect(sectionChildren[1].getText()).to.equal('a cat.');
+
+ expect(div.contents().length).to.equal(2);
+ expect(div.contents()[0].getTagName()).to.equal('span');
+ expect(div.contents()[1].getText()).to.equal('Alice');
+ });
+
+ 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 prototype = $.extend({}, documentElement.DocumentNodeElement.prototype, {
+ init: function() {
+ this._container().append('<test></test>');
+ }
+ }),
+ c = getCanvasFromXML('<section><div class="testClass"></div></section>', [
+ {tag: 'div', klass: 'testClass', prototype: prototype}
+ ]);
+
+ expect(c.doc().children()[0]._container().children('test').length).to.equal(1); // @!
+ });
+
+ it('allows handling changes to internal structure of rendered node', function() {
+ var prototype = $.extend({}, documentElement.DocumentNodeElement.prototype, {
+ init: function() {
+ this.header = $('<h1>');
+ this._container().append(this.header);
+ this.refresh2();
+ },
+ refresh2: function() {
+ this.header.text(this.wlxmlNode.contents().length);
+ },
+ onNodeAdded: function(event) {
+ void(event);
+ this.refresh2();
+ }
+ });
+
+ var c = getCanvasFromXML('<section><div class="testClass"><a></a></div></section>', [
+ {tag: 'div', klass: 'testClass', prototype: prototype}
+ ]);
+
+ 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;
it('returns position when browser selection collapsed', function() {
var c = getCanvasFromXML('<section>Alice has a cat</section>'),
- dom = c.doc().dom(),
+ dom = c.doc().dom,
text = findTextNode(dom, 'Alice has a cat');
expect(text.nodeType).to.equal(Node.TEXT_NODE, 'correct node selected');
it('recognizes selection start and end on document order', function() {
var c = getCanvasFromXML('<section><span>Alice</span><span>has a cat</span><div>abc<span>...</span>cde</div></section>'),
- dom = c.doc().dom(),
+ dom = c.doc().dom,
textFirst = findTextNode(dom, 'Alice'),
textSecond = findTextNode(dom, 'has a cat'),
textAbc = findTextNode(dom, 'abc'),
it('returns boundries of selection when browser selection not collapsed', function() {
var c = getCanvasFromXML('<section>Alice <span>has</span> a <span>big</span> cat</section>'),
- dom = c.doc().dom(),
+ dom = c.doc().dom,
text = {
alice: findTextNode(dom, 'Alice '),
has: findTextNode(dom, 'has'),
it('recognizes when browser selection boundries lies in sibling DocumentTextElements', function() {
var c = getCanvasFromXML('<section>Alice <span>has</span> a <span>big</span> cat</section>'),
- dom = c.doc().dom(),
+ dom = c.doc().dom,
text = {
alice: findTextNode(dom, 'Alice '),
has: findTextNode(dom, 'has'),
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 */
-
+/* global Node: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.$element.data('canvas-element', this);
+ this.dom = this.createDOM();
+ this.dom.data('canvas-element', this);
};
$.extend(DocumentElement.prototype, {
- bound: function() {
- return $.contains(document.documentElement, this.dom()[0]);
+ refreshPath: function() {
+ this.parents().forEach(function(parent) {
+ parent.refresh();
+ });
+ this.refresh();
},
- dom: function() {
- return this.$element;
+ refresh: function() {
+ // noop
},
parent: function() {
- var parents = this.$element.parents('[document-node-element]');
+ var parents = this.dom.parents('[document-node-element]');
if(parents.length) {
return this.canvas.getDocumentElement(parents[0]);
}
},
sameNode: function(other) {
- 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);
+ return other && (typeof other === typeof this) && other.dom[0] === this.dom[0];
},
- getNextTextElement: function(includeInvisible) {
- return this.getNearestTextElement('below', includeInvisible);
- },
+ trigger: function() {
+ this.canvas.eventBus.trigger.apply(this.canvas.eventBus, Array.prototype.slice.call(arguments, 0));
+ }
- getNearestTextElement: function(direction, includeInvisible) {
- includeInvisible = includeInvisible !== undefined ? includeInvisible : false;
- var selector = '[document-text-element]' + (includeInvisible ? '' : ':visible');
- 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));
- }
- }
});
var DocumentNodeElement = function(wlxmlNode, canvas) {
DocumentElement.call(this, wlxmlNode, canvas);
wlxmlNode.setData('canvasElement', this);
+ this.init(this.dom);
};
} 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;
};
$.extend(DocumentNodeElement.prototype, {
+ defaultDisplayStyle: 'block',
+ init: function() {},
+ addWidget: function(widget) {
+ this.dom.children('.canvas-widgets').append(widget.DOM ? widget.DOM : widget);
+ },
+ clearWidgets: function() {
+ this.dom.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));
- return dom;
+ return wrapper;
},
_container: function() {
- return this.dom().children('[document-element-content]');
+ return this.dom.children('[document-element-content]');
},
detach: function() {
- this.dom().detach();
+ var parents = this.parents();
+ this.dom.detach();
this.canvas = null;
- return this;
- },
- append: function(params) {
- return manipulate(this, params, 'append');
- },
- prepend: function(params) {
- return manipulate(this, params, 'prepend');
+ if(parents[0]) {
+ parents[0].refreshPath();
+ }
+ return this;
},
before: function(params) {
return manipulate(this, params, 'before');
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();
- },
toggleLabel: function(toggle) {
var displayCss = toggle ? 'inline-block' : 'none';
- var label = this.dom().children('.canvas-widgets').find('.canvas-widget-label');
+ var label = this.dom.children('.canvas-widgets').find('.canvas-widget-label');
label.css('display', displayCss);
this.toggleHighlight(toggle);
},
this._container().toggleClass('highlighted-element', toggle);
},
- toggle: function(toggle) {
- if(this.manager) {
- this.manager.toggle(toggle);
- }
+ isBlock: function() {
+ return this.dom.css('display') === 'block';
+ },
+
+ displayAsBlock: function() {
+ this.dom.css('display', 'block');
+ this._container().css('display', 'block');
+ },
+ 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);
}
});
$.extend(DocumentTextElement.prototype, {
createDOM: function() {
- return $('<div>')
+ var dom = $('<div>')
.attr('document-text-element', '')
.text(this.wlxmlNode.getText() || utils.unicode.ZWS);
+ return dom;
},
detach: function() {
- this.dom().detach();
+ this.dom.detach();
this.canvas = null;
return this;
},
setText: function(text) {
- this.dom().contents()[0].data = text;
+ if(text === '') {
+ text = utils.unicode.ZWS;
+ }
+ if(text !== this.getText()) {
+ this.dom.contents()[0].data = text;
+ }
},
getText: function(options) {
options = _.extend({raw: false}, options || {});
- var toret = this.dom().text();
+ var toret = this.dom.text();
if(!options.raw) {
toret = toret.replace(utils.unicode.ZWS, '');
}
},
isEmpty: function() {
// Having at least Zero Width Space is guaranteed be Content Observer
- return this.dom().contents()[0].data === utils.unicode.ZWS;
+ return this.dom.contents()[0].data === utils.unicode.ZWS;
},
after: function(params) {
if(params instanceof DocumentTextElement || params.text) {
} else {
element = this.canvas.createElement(params);
}
- this.dom().wrap('<div>');
- this.dom().parent().after(element.dom());
- this.dom().unwrap();
+ this.dom.wrap('<div>');
+ this.dom.parent().after(element.dom);
+ this.dom.unwrap();
+ this.refreshPath();
return element;
},
before: function(params) {
} else {
element = this.canvas.createElement(params);
}
- this.dom().wrap('<div>');
- this.dom().parent().before(element.dom());
- this.dom().unwrap();
+ this.dom.wrap('<div>');
+ this.dom.parent().before(element.dom);
+ this.dom.unwrap();
+ this.refreshPath();
return element;
},
toggleHighlight: function() {
// do nothing for now
+ },
+ children: function() {
+ return [];
}
+
});
+
return {
DocumentElement: DocumentElement,
DocumentNodeElement: DocumentNodeElement,
--- /dev/null
+[document-node-element] {
+ .canvas-widgets {
+ display: inline;
+ text-indent: 0;
+ }
+
+ .canvas-widget {
+ display: none;
+ }
+
+
+}
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+var _ = require('libs/underscore'),
+ wlxml = require('wlxml/wlxml');
+
+
+var ElementsRegister = function(BaseType) {
+ this._register = {};
+ this.BaseType = BaseType;
+};
+
+_.extend(ElementsRegister.prototype, {
+ createCanvasElementType: function(elementPrototype) {
+ var register = this;
+ var Constructor = function() {
+ register.BaseType.apply(this, Array.prototype.slice.call(arguments, 0));
+ };
+ Constructor.prototype = elementPrototype;
+ return Constructor;
+ },
+ register: function(params) {
+ params.klass = params.klass || '';
+ params.prototype = params.prototype || Object.create({});
+
+ this._register[params.tag] = this._register[params.tag] || {};
+ this._register[params.tag][params.klass] = this.createCanvasElementType(params.prototype);
+ },
+ getElement: function(params) {
+ params.klass = params.klass || '';
+ var Factory;
+ if(this._register[params.tag]) {
+ wlxml.getClassHierarchy(params.klass).reverse().some(function(klass) {
+ Factory = this._register[params.tag][klass];
+ if(Factory) {
+ return true;
+ }
+ }.bind(this));
+ }
+ if(!Factory) {
+ Factory = this.BaseType;
+ }
+ return Factory;
+ }
+});
+
+
+return ElementsRegister;
+
+});
--- /dev/null
+define(function(require) {
+
+'use strict';
+/* globals describe, it */
+
+
+var ElementsRegister = require('./elementsRegister.js'),
+ documentElement = require('./documentElement.js'),
+ chai = require('libs/chai');
+
+var expect = chai.expect;
+
+describe('Elements register', function() {
+ it('registers element for a tag', function() {
+ var register = new ElementsRegister(documentElement.DocumentNodeElement),
+ prototype = Object.create({});
+
+ register.register({
+ tag: 'div',
+ prototype: prototype,
+ });
+ var Element = register.getElement({tag: 'div'});
+ expect(Element.prototype).to.equal(prototype);
+ });
+ it('registers element for a class', function() {
+ var register = new ElementsRegister(documentElement.DocumentNodeElement),
+ prototype = Object.create({});
+
+ register.register({
+ tag: 'div',
+ klass: 'a.b',
+ prototype: prototype,
+ });
+ var Element = register.getElement({tag: 'div', klass: 'a.b.c'});
+ expect(Element.prototype).to.equal(prototype);
+ });
+});
+
+
+});
--- /dev/null
+define(function(require) {
+
+'use strict';
+
+var $ = require('libs/jquery'),
+ 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 DocumentNodeElement = documentElement.DocumentNodeElement;
+
+var generic = Object.create(DocumentNodeElement.prototype);
+
+$.extend(generic, {
+ init: function() {
+ DocumentNodeElement.prototype.init.call(this);
+ 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));
+ this.refresh();
+ },
+
+ refresh: function() {
+ if(this.wlxmlNode.getTagName() === 'span') {
+ if(this.containsBlock()) {
+ this.displayAsBlock();
+ } else {
+ this.displayInline();
+ }
+ } else {
+ this.displayAsBlock();
+ }
+ },
+
+ 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;
+ },
+
+ 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;
+ },
+
+ 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;
+ },
+
+ 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';
+ }
+
+ if(event.type === 'nodeMoved') {
+ actionArg = utils.getElementForNode(event.meta.node, event.meta.parent);
+ } else {
+ actionArg = event.meta.node;
+ }
+
+ referenceElement[referenceAction](actionArg);
+ },
+ onNodeMoved: function(event) {
+ return this.onNodeAdded.call(this, event);
+ },
+ 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;
+ }
+ });
+ },
+
+
+ ///
+
+ 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();
+ }
+ });
+ },
+
+ 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;
+ },
+
+ 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();
+ }
+});
+
+
+return generic;
+
+
+
+});
\ No newline at end of file
--- /dev/null
+.canvas-widget-label {
+ position: absolute;
+ display: none;
+ top: -20px;
+ left:0;
+ background-color: red;
+ color: white;
+ font-size:12px;
+ font-weight: bold;
+ padding: 1px 3px;
+ //width:300px;
+ opacity: 0.65;
+ font-family: monospace;
+ z-index:9999;
+ white-space: nowrap;
+}
\ No newline at end of file
description: gettext('Splitting text')
},
success: function(ret) {
- canvas.setCurrentElement(utils.findCanvasElement(ret), {caretTo: 'start'});
+ canvas.setCurrentElement(utils.getElementForNode(ret), {caretTo: 'start'});
}
});
description: gettext('Splitting text')
},
success: function(ret) {
- canvas.setCurrentElement(utils.findCanvasElement(ret), {caretTo: 'start'});
+ canvas.setCurrentElement(utils.getElementForNode(ret), {caretTo: 'start'});
}
});
gotoOptions = {caretTo: 'start'};
}
- canvas.setCurrentElement(utils.findCanvasElement(goto), gotoOptions);
+ canvas.setCurrentElement(utils.getElementForNode(goto), gotoOptions);
}
}
}
direction = 'below';
caretTo = 'start';
}
- var el = canvas.getDocumentElement(utils.nearestInDocumentOrder('[document-text-element]', direction, element.dom()[0]));
+ var el = canvas.getDocumentElement(utils.nearestInDocumentOrder('[document-text-element]', direction, element.dom[0]));
if(el) {
canvas.setCurrentElement(el, {caretTo: caretTo});
}
if(direction === 'above') {
if(start.offsetAtBeginning) {
- goto = start.element.getNearestTextElement('above');
+ goto = canvas.getNearestTextElement('above', start.element);
caretTo = 'end';
} else {
goto = start.element;
}
} else {
if(end.offsetAtEnd) {
- goto = start.element.getNearestTextElement('below');
+ goto = canvas.getNearestTextElement('below', start.element);
caretTo = 'start';
} else {
goto = end.element;
if(grandParent && grandParent.children().length === 1) {
goto = grandParent.wlxmlNode.append({text: ''});
} else {
- goto = element.getNearestTextElement(direction);
+ goto = canvas.getNearestTextElement(direction, element);
}
parent.wlxmlNode.detach();
} else {
- goto = element.getNearestTextElement(direction);
+ goto = canvas.getNearestTextElement(direction, element);
element.wlxmlNode.detach();
}
canvas.setCurrentElement(goto, {caretTo: caretTo});
}
else if(cursorAtOperationEdge) {
if(direction === 'below') {
- element = element.getNearestTextElement(direction);
+ element = canvas.getNearestTextElement(direction, element);
}
if(element) {
goto = element.wlxmlNode.mergeContentUp();
return null;
};
-var findCanvasElement = function(node, childOf) {
- if(node.nodeType === Node.ELEMENT_NODE) {
- return node.getData('canvasElement');
- }
+
+var getElementForNode = function(node, withParent) {
if(node.nodeType === Node.TEXT_NODE) {
- return findCanvasElementInParent(node, childOf || node.parent());
+ return _getElementForTextNode(node, withParent);
+ }
+ while(!node.getData('canvasElement')) {
+ node = node.parent();
}
+ return node.getData('canvasElement');
};
-/**
- * We take child and its parent as arguments separatly to
- * handle situation where child was removed from WLXMLDocument
- * and it lost reference to its parent (but we may still have it on canvas).
-*/
-var findCanvasElementInParent = function(wlxmlChildNode, wlxmlParentNode) {
- var parentElement, toret;
-
- if(wlxmlParentNode === null) {
- toret = wlxmlChildNode.getData('canvasElement');
- if(toret.parent()) {
- throw new Error('This should never happen: root canvas element doesn\'t render root document node!');
+var _getElementForTextNode = function(textNode, withParent) {
+ var parentElement = getElementForNode(withParent || textNode.parent()),
+ toret;
+ parentElement.children().some(function(child) {
+ if(child.wlxmlNode.sameNode(textNode)) {
+ toret = child;
+ return true;
}
- } else {
- parentElement = findCanvasElement(wlxmlParentNode);
- parentElement.children().forEach(function(child) {
- if(child.wlxmlNode.sameNode(wlxmlChildNode)) { // czemu tu, przy drugim undo child nie mial data?
- toret = child;
- }
- });
- }
+ });
return toret;
};
+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
};
});
+++ /dev/null
-define([
-'libs/jquery',
-'utils/wlxml'
-], function($, wlxmlUtils) {
-
-'use strict';
-
-return {
- labelWidget: function(tag, klass) {
- return $('<span>')
- .addClass('canvas-widget canvas-widget-label')
- .text(wlxmlUtils.getTagLabel(tag) + (klass ? ' / ' + wlxmlUtils.getClassLabel(klass) : ''));
- },
-
- 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;
- },
-
- hideButton: function(clickHandler) {
- var mydom = $('<span>x</span>')
- .addClass('canvas-widget canvas-widget-hide-button');
- 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;
- }
-
-};
-
-});
\ No newline at end of file
+++ /dev/null
-[document-node-element] {
- .canvas-widgets {
- display: inline;
- text-indent: 0;
- }
-
- .canvas-widget {
- display: none;
- }
-
- .canvas-widget-label {
- position: absolute;
- display: none;
- top: -20px;
- left:0;
- background-color: red;
- color: white;
- font-size:12px;
- font-weight: bold;
- padding: 1px 3px;
- //width:300px;
- opacity: 0.65;
- font-family: monospace;
- z-index:9999;
- white-space: nowrap;
- }
-
-
-
- .canvas-widget-footnote-handle {
- display: inline;
- outline: 0px solid transparent;
- cursor: pointer;
- counter-increment: footnote;
- vertical-align: super;
- color: blue;
- font-size: .8em;
-
- &::before, {
- content: "[" counter(footnote) "]";
- }
- }
-
- .canvas-widget-hide-button {
- @line_height: 12px;
- @padding: 2px;
- @temporary_footnote_hack: 10px;
-
- position: absolute;
- top: -(@line_height + 2 * @padding) + @temporary_footnote_hack;
- right: 0;
- line-height: @line_height;
- padding: @padding;
- font-weight: bold;
- color: white;
- background-image: linear-gradient(to bottom,#ee5f5b,#bd362f);
- border-radius: 1px;
- cursor: pointer;
- }
-
- .canvas-widget-comment-adnotation {
- position:absolute;
- top: 10px;
- right:10px;
- font-size: 10px;
- color: lighten(@black, 10%);
- z-index:1000;
- }
-}
\ No newline at end of file
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),
+ newElement;
if(event.meta.attr === 'class') {
- var canvasNode = utils.findCanvasElement(event.meta.node);
- canvasNode.setWlxmlClass(event.meta.newVal);
+ if(element.wlxmlNode.getClass() !== event.meta.attr) {
+ if(event.meta.node.isRoot()) {
+ this.canvas.reloadRoot();
+ } else {
+ newElement = this.canvas.createElement(event.meta.node);
+ element.dom.replaceWith(newElement.dom);
+ }
+ }
+
+ } else {
+ 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); //
+ //
},
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);
+ element.setText(event.meta.node.getText());
},
metadataChanged: _metadataEventHandler,
+++ /dev/null
-define([
-'libs/jquery',
-'modules/documentCanvas/canvas/widgets'
-], function($, widgets) {
-
-'use strict';
-
-
-var DocumentElementWrapper = function(documentElement) {
-
- this.documentElement = documentElement;
-
- this.addWidget = function(widget) {
- documentElement.dom().children('.canvas-widgets').append(widget.DOM ? widget.DOM : widget);
- };
-
- this.clearWidgets = function() {
- documentElement.dom().children('.canvas-widgets').empty();
- };
-
- this.setDisplayStyle = function(displayStyle) {
- documentElement.dom().css('display', displayStyle || '');
- documentElement._container().css('display', displayStyle || '');
- };
-
- this.tag = function() {
- return documentElement.getWlxmlTag();
- };
-
- this.klass = function() {
- return documentElement.getWlxmlClass();
- };
-
- this.toggle = function(toggle) {
- documentElement._container().toggle(toggle);
- };
-
- var eventBus = documentElement.canvas ? documentElement.canvas.eventBus :
- {trigger: function() {}};
- this.trigger = function() {
- eventBus.trigger.apply(eventBus, arguments);
- };
-
- this.node = documentElement.wlxmlNode;
-};
-
-var getDisplayStyle = function(tag, klass) {
- if(tag === 'metadata') {
- return 'none';
- }
- if(tag === 'span') {
- return 'inline';
- }
- if(klass && klass.substr(0, 4) === 'item') {
- return null;
- }
- if(klass === 'gap') {
- return 'inline';
- }
- return 'block';
-};
-
-var GenericManager = function(wlxmlElement) {
- this.el = wlxmlElement;
-};
-
-$.extend(GenericManager.prototype, {
- setup: function() {
- this.el.setDisplayStyle(getDisplayStyle(this.el.tag(), this.el.klass()));
-
- this.el.clearWidgets();
- },
- toggle: function(toggle) {
- this.el.toggle(toggle);
- }
-
-});
-
-var managers = {
- _m: {},
- set: function(tag, klass, manager) {
- if(!this._m[tag]) {
- this._m[tag] = {};
- }
- this._m[tag][klass] = manager;
- },
- get: function(tag,klass) {
- if(this._m[tag] && this._m[tag][klass]) {
- return this._m[tag][klass];
- }
- return GenericManager;
- }
-};
-
-var FootnoteManager = function(wlxmlElement) {
- this.el = wlxmlElement;
-};
-$.extend(FootnoteManager.prototype, {
- setup: function() {
- this.el.clearWidgets();
-
- var clickHandler = function() {
- this.toggle(true);
- }.bind(this);
- this.footnoteHandler = widgets.footnoteHandler(clickHandler);
- this.el.addWidget(this.footnoteHandler);
-
- var closeHandler = function() {
- this.toggle(false);
-
- }.bind(this);
- this.hideButton = widgets.hideButton(closeHandler);
- this.el.addWidget(this.hideButton);
-
- this.toggle(false, {silent: true});
- },
- 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);
-
-
-var ListItemManager = function(wlxmlElement) {
- this.el = wlxmlElement;
-};
-$.extend(ListItemManager.prototype, {
- setup: function() {
- this.el.clearWidgets();
- },
- toggleBullet: function(toggle) {
- this.el.documentElement._container().css({display : toggle ? 'list-item' : 'block'});
- }
-});
-managers.set('div', 'item', ListItemManager);
-
-
-var CommentManager = function(wlxmlElement) {
- this.el = wlxmlElement;
-};
-
-$.extend(CommentManager.prototype, {
- setup: function() {
- this.el.clearWidgets();
-
- this.widget = widgets.commentAdnotation(this.el.node);
- this.el.addWidget(this.widget);
- this.widget.DOM.show();
- },
- updateMetadata: function() {
- // var parts = [];
- // this.el.node.getMetadata().forEach(function(row) {
- // parts.push(row.getValue());
- // }, 'creator');
- // this.widget.text(parts.join(', '));
- this.widget.update(this.el.node);
- }
-});
-managers.set('aside', 'comment', CommentManager);
-
-return {
- getFor: function(documentElement) {
- var wlxmlElement = new DocumentElementWrapper(documentElement);
- return new (managers.get(wlxmlElement.tag(), wlxmlElement.klass()))(wlxmlElement);
-
- }
-};
-
-});
\ No newline at end of file
return function(sandbox) {
- var canvas = canvas3.fromXMLDocument(null);
+ var canvasElements = [];
+
+ sandbox.getPlugins().forEach(function(plugin) {
+ canvasElements = canvasElements.concat(plugin.canvasElements || []);
+ });
+
+ var canvas = canvas3.fromXMLDocument(null, canvasElements);
var canvasWrapper = $(template);
var shownAlready = false;
var scrollbarPosition = 0,
@import 'nodes.less';
-@import 'canvas/widgets.less';
+@import 'canvas/documentElement.less';
+@import 'canvas/genericElement.less';
#rng-module-documentCanvas {
height: 100%;
--- /dev/null
+define(function(require) {
+
+'use strict';
+var $ = require('libs/jquery'),
+ genericElement = require('modules/documentCanvas/canvas/genericElement'); // TODO: This should be accessible via plugin infrastructure
+
+
+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 = Object.create(genericElement);
+$.extend(comment, {
+ init: function() {
+ genericElement.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 = Object.create(genericElement);
+$.extend(footnote, {
+ init: function() {
+ genericElement.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);
+ }
+ }
+});
+
+
+return [
+ {tag: 'aside', klass: 'comment', prototype: comment},
+ {tag: 'aside', klass: 'footnote', prototype: footnote}
+];
+
+
+});
\ No newline at end of file
--- /dev/null
+.canvas-widget-footnote-handle {
+ display: inline;
+ outline: 0px solid transparent;
+ cursor: pointer;
+ counter-increment: footnote;
+ vertical-align: super;
+ color: blue;
+ font-size: .8em;
+
+ &::before, {
+ content: "[" counter(footnote) "]";
+ }
+}
+
+.canvas-widget-hide-button {
+ @line_height: 12px;
+ @padding: 2px;
+ @temporary_footnote_hack: 10px;
+
+ position: absolute;
+ top: -(@line_height + 2 * @padding) + @temporary_footnote_hack;
+ right: 0;
+ line-height: @line_height;
+ padding: @padding;
+ font-weight: bold;
+ color: white;
+ background-image: linear-gradient(to bottom,#ee5f5b,#bd362f);
+ border-radius: 1px;
+ cursor: pointer;
+}
+
+.canvas-widget-comment-adnotation {
+ position:absolute;
+ top: 10px;
+ right:10px;
+ font-size: 10px;
+ color: lighten(@black, 10%);
+ z-index:1000;
+}
\ No newline at end of file
switchTo = require('plugins/core/switch'),
lists = require('plugins/core/lists'),
plugin = {name: 'core', actions: [], canvas: {}, documentExtension: {textNode: {}}},
- Dialog = require('views/dialog/dialog');
+ Dialog = require('views/dialog/dialog'),
+ canvasElements = require('plugins/core/canvasElements');
+
plugin.documentExtension.textNode.transformations = {
templates.actions[0].params.template.options = config.templates;
};
+plugin.canvasElements = canvasElements;
+
return plugin;
});
\ No newline at end of file
--- /dev/null
+@import 'canvasElements.less';
\ No newline at end of file
--- /dev/null
+@import 'core/core.less';
\ No newline at end of file
@import 'mixins.less';
@import 'common.less';
+@import '../plugins/plugins.less';
@import '../views/openSelect/openSelect.less';
@import '../views/dialog/dialog.less';
@import '../modules/rng/rng.less';
getClass: function() {
return this.getAttr('class') || '';
},
+ getClassHierarchy: function() {
+ return getClassLists(this.getClass());
+ },
setClass: function(klass) {
if(klass !== this.klass) {
installObject(this, klass);
return this.WLXMLDocumentFromXML(xml).root;
},
- WLXMLDocument: WLXMLDocument
+ WLXMLDocument: WLXMLDocument,
+ getClassHierarchy: getClassLists
};
});
\ No newline at end of file
expect(node.getClass()).to.equal('class.subclass');
});
+ it('returns its class hierarchy', function() {
+ var node = nodeFromXML('<div class="a.b.c"></div>');
+ expect(node.getClassHierarchy()).to.eql(['', 'a', 'a.b', 'a.b.c']);
+ });
+
it('returns unregistered attributes', function() {
var testClasses = {
'testClass': {