--- /dev/null
+<div class="view-table">
+ <div class="view-row">
+ <div class="root-wrapper" contenteditable="true"></div>
+ </div>
+</div>
\ No newline at end of file
'modules/documentCanvas/canvas/wlxmlListener',
'modules/documentCanvas/canvas/elementsRegister',
'modules/documentCanvas/canvas/genericElement',
-], function($, _, Backbone, logging, documentElement, keyboard, utils, wlxmlListener, ElementsRegister, genericElement) {
+'modules/documentCanvas/canvas/nullElement',
+'modules/documentCanvas/canvas/gutter',
+'libs/text!./canvas.html'
+], function($, _, Backbone, logging, documentElement, keyboard, utils, wlxmlListener, ElementsRegister, genericElement, nullElement, gutter, canvasTemplate) {
'use strict';
/* global document:false, window:false, Node:false, gettext */
});
-var Canvas = function(wlxmlDocument, elements) {
- this.elementsRegister = new ElementsRegister(documentElement.DocumentNodeElement);
+var Canvas = function(wlxmlDocument, elements, metadata) {
+ this.metadata = metadata || {};
+ this.elementsRegister = new ElementsRegister(documentElement.DocumentNodeElement, nullElement);
elements = [
{tag: 'section', klass: null, prototype: genericElement},
this.elementsRegister.register(elementDesc);
}.bind(this));
this.eventBus = _.extend({}, Backbone.Events);
- this.wrapper = $('<div>').addClass('canvas-wrapper').attr('contenteditable', true);
+
+ this.dom = $(canvasTemplate);
+ this.rootWrapper = this.dom.find('.root-wrapper');
+
+
+ this.gutter = gutter.create();
+ this.gutterView = new gutter.GutterView(this.gutter);
+ this.dom.find('.view-row').append(this.gutterView.dom);
+
this.wlxmlListener = wlxmlListener.create(this);
this.loadWlxmlDocument(wlxmlDocument);
this.setupEventHandling();
$.extend(Canvas.prototype, Backbone.Events, {
+ getElementOffset: function(element) {
+ return element.dom.offset().top - this.dom.offset().top;
+ },
+
loadWlxmlDocument: function(wlxmlDocument) {
if(!wlxmlDocument) {
return false;
},
reloadRoot: function() {
+ if(this.rootElement) {
+ this.rootElement.detach();
+ }
this.rootElement = this.createElement(this.wlxmlDocument.root);
- this.wrapper.empty();
- this.wrapper.append(this.rootElement.dom);
+ this.rootWrapper.append(this.rootElement.dom);
},
setupEventHandling: function() {
var canvas = this;
- this.wrapper.on('keyup keydown keypress', function(e) {
+ this.rootWrapper.on('keyup keydown keypress', function(e) {
keyboard.handleKey(e, canvas);
});
- this.wrapper.on('mouseup', function() {
+ this.rootWrapper.on('mouseup', function() {
canvas.triggerSelectionChanged();
});
var mouseDown;
- this.wrapper.on('mousedown', '[document-node-element], [document-text-element]', function(e) {
+ this.rootWrapper.on('mousedown', '[document-node-element], [document-text-element]', function(e) {
mouseDown = e.target;
});
- this.wrapper.on('click', '[document-node-element], [document-text-element]', function(e) {
+ this.rootWrapper.on('click', '[document-node-element], [document-text-element]', function(e) {
e.stopPropagation();
if(e.originalEvent.detail === 3) {
e.preventDefault();
}
});
- this.wrapper.on('paste', function(e) {
+ this.rootWrapper.on('paste', function(e) {
e.preventDefault();
var clipboardData = e.originalEvent.clipboardData;
mutation.target.data = mutation.target.data.replace(utils.unicode.ZWS, '');
canvas._moveCaretToTextElement(canvas.getDocumentElement(mutation.target), 'end');
}
- observer.observe(canvas.wrapper[0], config);
+ observer.observe(canvas.dom[0], config);
var textElement = canvas.getDocumentElement(mutation.target),
toSet = mutation.target.data !== utils.unicode.ZWS ? mutation.target.data : '';
});
});
var config = { attributes: false, childList: false, characterData: true, subtree: true, characterDataOldValue: true};
- observer.observe(this.wrapper[0], config);
+ observer.observe(this.rootWrapper[0], config);
- this.wrapper.on('mouseover', '[document-node-element], [document-text-element]', function(e) {
- var el = canvas.getDocumentElement(e.currentTarget);
+ var hoverHandler = function(e) {
+ var el = canvas.getDocumentElement(e.currentTarget),
+ expose = {
+ mouseover: true,
+ mouseout: false
+ };
if(!el) {
return;
}
if(el instanceof documentElement.DocumentTextElement) {
el = el.parent();
}
- el.toggleLabel(true);
- });
- this.wrapper.on('mouseout', '[document-node-element], [document-text-element]', function(e) {
- var el = canvas.getDocumentElement(e.currentTarget);
- if(!el) {
- return;
- }
- e.stopPropagation();
- if(el instanceof documentElement.DocumentTextElement) {
- el = el.parent();
- }
- el.toggleLabel(false);
- });
+ el.updateState({exposed:expose[e.type]});
+ };
+
+ this.rootWrapper.on('mouseover', '[document-node-element], [document-text-element]', hoverHandler);
+ this.rootWrapper.on('mouseout', '[document-node-element], [document-text-element]', hoverHandler);
this.eventBus.on('elementToggled', function(toggle, element) {
if(!toggle) {
},
view: function() {
- return this.wrapper;
+ return this.dom;
},
doc: function() {
toggleElementHighlight: function(node, toggle) {
var element = utils.getElementForNode(node);
- element.toggleHighlight(toggle);
+ element.updateState({exposed: toggle});
},
getCursor: function() {
getCurrentNodeElement: function() {
- var htmlElement = this.wrapper.find('.current-node-element').parent()[0];
- if(htmlElement) {
- return this.getDocumentElement(htmlElement);
- }
+ return this.currentNodeElement;
},
getCurrentTextElement: function() {
- var htmlElement = this.wrapper.find('.current-text-element')[0];
+ var htmlElement = this.rootWrapper.find('.current-text-element')[0];
if(htmlElement) {
return this.getDocumentElement(htmlElement);
}
},
contains: function(element) {
- return element.dom.parents().index(this.wrapper) !== -1;
+ return element && element.dom && element.dom.parents().index(this.rootWrapper) !== -1;
},
triggerSelectionChanged: function() {
var s = this.getSelection(),
f = s.toDocumentFragment();
if(f && f instanceof f.RangeFragment) {
- var $current = this.wrapper.find('.current-node-element');
- var current = $current && this.getDocumentElement($current.parent()[0]);
-
- if($current) {
- $current.removeClass('current-node-element');
- }
- if(current) {
- current.markAsCurrent(false);
- }
+ if(this.currentNodeElement) {
+ this.currentNodeElement.updateState({active: false});
+ this.currentNodeElement = null;
+ }
}
},
}.bind(this);
var _markAsCurrent = function(element) {
if(element instanceof documentElement.DocumentTextElement) {
- this.wrapper.find('.current-text-element').removeClass('current-text-element');
+ this.rootWrapper.find('.current-text-element').removeClass('current-text-element');
element.dom.addClass('current-text-element');
} else {
- var $current = this.wrapper.find('.current-node-element');
- var current = this.getDocumentElement($current.parent()[0]);
- $current.removeClass('current-node-element');
-
- if(current) {
- current.markAsCurrent(false);
+ if(this.currentNodeElement) {
+ this.currentNodeElement.updateState({active: false});
}
- element._container().addClass('current-node-element');
- element.markAsCurrent(true);
+ element.updateState({active: true});
+ this.currentNodeElement = element;
}
}.bind(this);
currentNodeElement = this.getCurrentNodeElement();
if(currentTextElement && !(currentTextElement.sameNode(textElementToLand))) {
- this.wrapper.find('.current-text-element').removeClass('current-text-element');
+ this.rootWrapper.find('.current-text-element').removeClass('current-text-element');
}
if(textElementToLand) {
selection.removeAllRanges();
selection.addRange(range);
- this.wrapper.focus(); // FF requires this for caret to be put where range colllapses, Chrome doesn't.
+ this.rootWrapper.focus(); // FF requires this for caret to be put where range colllapses, Chrome doesn't.
},
setCursorPosition: function(position) {
},
toggleGrid: function() {
- this.wrapper.toggleClass('grid-on');
+ this.rootWrapper.toggleClass('grid-on');
this.trigger('changed');
},
isGridToggled: function() {
- return this.wrapper.hasClass('grid-on');
+ return this.rootWrapper.hasClass('grid-on');
}
});
});
return {
- fromXMLDocument: function(wlxmlDocument, elements) {
- return new Canvas(wlxmlDocument, elements);
+ fromXMLDocument: function(wlxmlDocument, elements, metadata) {
+ return new Canvas(wlxmlDocument, elements, metadata);
}
};
--- /dev/null
+.view-table {
+ display: table;
+ width: calc(~'100% - 100px');
+ margin: 10px 0 20px 100px;
+
+ .view-row {
+ display: table-row;
+ }
+}
onNodeAdded: function(event) {
void(event);
this.refresh2();
- }
+ },
+ onNodeTextChange: function(event) {
+ this.header.text(event.meta.node.getText());
+ },
+ children: function() { return []; }
});
var c = getCanvasFromXML('<section><div class="testClass"><a></a></div></section>', [
node.append({tagName: 'div'});
expect(header.text()).to.equal('2', 'added div');
+
+ var textNode = node.append({text: 'test'});
+
+ expect(header.text()).to.equal('3', 'added text node');
+
+ textNode.setText('test2');
+
+ expect(header.text()).to.equal('test2', 'text node change handled');
});
describe('Handling unknown class', function() {
--- /dev/null
+<div class="comment">
+ <div class="header">
+ <span class="author"><%= author %></span>
+ <span class="date"><%= date %></span>
+ </div>
+ <div style="clear: both"></div>
+ <div class="content">
+ <%= content %>
+ </div>
+ <div class="edit">
+ <textarea rows="1"></textarea>
+ <div>
+ <button class="btn btn-info btn-mini edit-save-btn" disabled><%= gettext('Save') %></button>
+ <button class="btn btn-mini edit-cancel-btn"><%= gettext('Cancel') %></button>
+ <div style="clear:both;"></div>
+ </div>
+ </div>
+ <div class="toolbox">
+ <a href="#" class="edit-start-btn"><%= gettext('edit') %></a> -
+ <a href="#" class="remove-btn"><%= gettext('remove') %></a>
+ </div>
+ <div class="deleteDialog">
+ <div><%= gettext('Delete this comment?') %></div>
+ <div>
+ <button class="btn btn-mini deleteDialog-confirm"><%= gettext('Delete') %></button>
+ <button class="btn btn-mini deleteDialog-cancel"><%= gettext('Cancel') %></button>
+ </div>
+ </div>
+</div>
--- /dev/null
+<div class="comments">
+ <div class="list"></div>
+ <div class="newComment">
+ <textarea rows="1"></textarea>
+ <button class="btn btn-info btn-mini btnAdd" disabled><%= gettext('Comment') %></button>
+ <button class="btn btn-mini btnCancel"><%= gettext('Cancel') %></button>
+ <div style="clear:both;"></div>
+ </div>
+</div>
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+/* globals gettext */
+
+
+var $ = require('libs/jquery'),
+ _ = require('libs/underscore'),
+ datetime = require('fnpjs/datetime'),
+ commentsTemplate = require('libs/text!./comments.html'),
+ commentTemplate = require('libs/text!./comment.html');
+
+
+var makeAutoResizable = function(textarea) {
+ textarea.on('input', function() {
+ resize(textarea);
+ });
+};
+
+var resize = function(textarea) {
+ if(textarea.prop('scrollHeight') > textarea.prop('clientHeight')) {
+ textarea.height(textarea.prop('scrollHeight'));
+ }
+};
+
+
+var CommentsView = function(node, user) {
+ this.node = node;
+ this.user = user;
+ this.dom = $(_.template(commentsTemplate)());
+ this.list = this.dom.find('.list');
+ this.textarea = this.dom.find('textarea');
+ this.addButton = this.dom.find('button.btnAdd');
+ this.cancelButton = this.dom.find('button.btnCancel');
+
+ this.textarea.on('input', function() {
+ this.addButton.attr('disabled', this.textarea.val() === '');
+ }.bind(this));
+ makeAutoResizable(this.textarea);
+
+ this.addButton.hide();
+ this.cancelButton.hide();
+ this.textarea.on('focus', function() {
+ this.addButton.show();
+ this.cancelButton.show();
+ }.bind(this));
+
+ this.addButton.on('click', function() {
+ if(!this.node) {
+ return;
+ }
+
+ this.node.document.transaction(function() {
+ var commentNode = this.node.document.createDocumentNode({tagName: 'aside', attrs: {'class': 'comment'}}),
+ metadata = commentNode.getMetadata(),
+ creator;
+
+ if(this.user) {
+ creator = this.user.name;
+ if(this.user.email) {
+ creator += ' (' + this.user.email + ')';
+ }
+ } else {
+ creator = 'anonymous';
+ }
+
+ metadata.add({key: 'creator', value: creator});
+ metadata.add({key: 'date', value: datetime.currentStrfmt()});
+ commentNode.append({text: this.textarea.val()});
+
+ this.node.append(commentNode);
+ }.bind(this), {
+ metadata: {
+ description: gettext('Add comment')
+ },
+ success: function() {
+ this.textarea.val('');
+ }.bind(this)
+ });
+
+ }.bind(this));
+
+ this.cancelButton.on('click', function() {
+ this.addButton.hide();
+ this.cancelButton.hide();
+ this.textarea.val('');
+ }.bind(this));
+
+ this.render();
+ this.onDeactivated();
+
+};
+
+_.extend(CommentsView.prototype, {
+ render: function() {
+ this.list.empty();
+ this.textarea.attr('placeholder', gettext('Comment'));
+
+ this.node.contents()
+ .filter(function(child) {
+ return child.is({tag: 'aside', klass: 'comment'});
+ })
+ .forEach(function(commentNode) {
+ var commentView = new CommentView(commentNode);
+ this.list.append(commentView.dom);
+ this.textarea.attr('placeholder', gettext('Respond') + '...');
+ }.bind(this));
+ },
+ onActivated: function() {
+ this.dom.find('.newComment').toggle(true);
+ },
+ onDeactivated: function() {
+ this.dom.find('.newComment').toggle(false);
+ this.addButton.hide();
+ this.cancelButton.hide();
+ },
+});
+
+
+var CommentView = function(commentNode) {
+ this.node = commentNode;
+
+ var metaData = this.node.getMetadata(),
+ author, date;
+
+ metaData.some(function(row) {
+ author = row.getValue();
+ if(author) {
+ author = author.split(' ')[0];
+ }
+ return true;
+ }, 'creator');
+
+ metaData.some(function(row) {
+ date = row.getValue();
+ if(/[0-9][0-9]:[0-9][0-9]:[0-9][0-9]$/g.test(date)) {
+ date = date.split(':');
+ date.pop();
+ date = date.join(':');
+ }
+ return true;
+ }, 'date');
+
+ this.dom = $(_.template(commentTemplate)({
+ author: author ||'?',
+ date: date || '?',
+ content: this.node.object.getText() || '?'
+ }));
+
+ this.contentElement = this.dom.find('.content');
+ this.editElement = this.dom.find('.edit');
+ this.deleteDialogElement = this.dom.find('.deleteDialog');
+
+ this.dom.find('.remove-btn').on('click', function() {
+ this.deleteDialogElement.show();
+ }.bind(this));
+
+ this.dom.find('.deleteDialog-confirm').on('click', function() {
+ this.node.document.transaction(function() {
+ this.node.detach();
+ }.bind(this), {
+ metadata: {
+ description: gettext('Remove comment')
+ }
+ });
+ }.bind(this));
+
+ this.dom.find('.deleteDialog-cancel').on('click', function() {
+ this.deleteDialogElement.hide();
+ }.bind(this));
+
+ this.dom.find('.edit-start-btn').on('click', function() {
+ this.startEditing();
+ }.bind(this));
+
+ this.dom.find('.edit-save-btn').on('click', function() {
+ this.saveEditing();
+ }.bind(this));
+
+ this.dom.find('.edit-cancel-btn').on('click', function() {
+ this.cancelEditing();
+ }.bind(this));
+
+ this.textarea = this.editElement.find('textarea');
+ this.textarea.on('input', function() {
+ this.dom.find('.edit-save-btn').attr('disabled', this.textarea.val() === '');
+ }.bind(this));
+ makeAutoResizable(this.textarea);
+};
+
+$.extend(CommentView.prototype, {
+ startEditing: function() {
+ this.contentElement.hide();
+ this.editElement.show();
+ this.textarea.val(this.node.object.getText());
+ resize(this.textarea);
+ this.textarea.focus();
+ },
+ saveEditing: function() {
+ var newContent = this.editElement.find('textarea').val();
+ this.node.document.transaction(function() {
+ this.node.object.setText(newContent);
+ }.bind(this), {
+ metadata: {
+ description: gettext('Edit comment')
+ }
+ });
+ },
+ cancelEditing: function() {
+ this.contentElement.show();
+ this.editElement.find('textarea').val('');
+ this.editElement.hide();
+ },
+});
+
+
+return CommentsView;
+
+});
\ No newline at end of file
--- /dev/null
+.comments {
+ @borderColor: darken(#FFFCB7, 45%);
+
+ font-size: 12px;
+
+ .comment {
+ position: relative;
+
+ border-color: @borderColor;
+ border-style: solid;
+ border-width: 1px 0;
+ margin-top: -1px;
+ padding-bottom: 10px;
+
+ &:first-child {
+ margin-top:0;
+ }
+
+ .header {
+ padding: 0 3px;
+ font-size: 10px;
+
+ .author {
+
+ }
+ .date {
+ float: right;
+ }
+ }
+
+ .content {
+ padding: 5px;
+ }
+
+ .edit {
+ display: none;
+ }
+
+ .deleteDialog {
+ display: none;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding-top: 15px;
+ color: white;
+ background-color: rgba(0,0,0,0.7);
+ text-align: center;
+ }
+ }
+
+ .newComment {
+ margin-top: 10px;
+ }
+
+ textarea {
+ display: block;
+ width: calc(~'100% - 4px');
+ padding: 2px 2px;
+ margin: 0 0 10px 0;
+ font-size: 12px;
+ resize: vertical;
+ outline: none !important;
+ box-shadow: none;
+
+ }
+
+}
\ No newline at end of file
var DocumentElement = function(wlxmlNode, canvas) {
this.wlxmlNode = wlxmlNode;
this.canvas = canvas;
+ this.state = {
+ exposed: false,
+ active: false
+ };
this.dom = this.createDOM();
this.dom.data('canvas-element', this);
refresh: function() {
// noop
},
+ updateState: function(toUpdate) {
+ var changes = {};
+ _.keys(toUpdate)
+ .filter(function(key) {
+ return this.state.hasOwnProperty(key);
+ }.bind(this))
+ .forEach(function(key) {
+ if(this.state !== toUpdate[key]) {
+ this.state[key] = changes[key] = toUpdate[key];
+ }
+ }.bind(this));
+ if(_.isFunction(this.onStateChange)) {
+ this.onStateChange(changes);
+ if(_.isBoolean(changes.active)) {
+ if(changes.active) {
+ var ptr = this;
+ while(ptr && ptr.wlxmlNode.getTagName() === 'span') {
+ ptr = ptr.parent();
+ }
+ if(ptr && ptr.gutterGroup) {
+ ptr.gutterGroup.show();
+ }
+ }
+ }
+ }
+ },
parent: function() {
var parents = this.dom.parents('[document-node-element]');
if(parents.length) {
} else {
element = e.canvas.createElement(params);
}
- e.dom[action](element.dom);
- e.refreshPath();
+ if(element.dom) {
+ e.dom[action](element.dom);
+ e.refreshPath();
+ }
return element;
};
clearWidgets: function() {
this.dom.children('.canvas-widgets').empty();
},
+ addToGutter: function(view) {
+ if(!this.gutterGroup) {
+ this.gutterGroup = this.canvas.gutter.createViewGroup({
+ offsetHint: function() {
+ return this.canvas.getElementOffset(this);
+ }.bind(this)
+ }, this);
+ }
+ this.gutterGroup.addView(view);
+ },
handle: function(event) {
var method = 'on' + event.type[0].toUpperCase() + event.type.substr(1);
if(this[method]) {
_container: function() {
return this.dom.children('[document-element-content]');
},
- detach: function() {
- var parents = this.parents();
- this.dom.detach();
- if(parents[0]) {
- parents[0].refreshPath();
+ detach: function(isChild) {
+ var parents;
+
+ if(this.gutterGroup) {
+ this.gutterGroup.remove();
}
- return this;
+ if(_.isFunction(this.children)) {
+ this.children().forEach(function(child) {
+ child.detach(true);
+ });
+ }
+
+ if(!isChild) {
+ parents = this.parents();
+ this.dom.detach();
+ if(parents[0]) {
+ parents[0].refreshPath();
+ }
+ }
+ return this;
},
before: function(params) {
return manipulate(this, params, 'before');
return manipulate(this, params, 'after');
},
- toggleLabel: function(toggle) {
- var displayCss = toggle ? 'inline-block' : 'none';
- var label = this.dom.children('.canvas-widgets').find('.canvas-widget-label');
- label.css('display', displayCss);
- this.toggleHighlight(toggle);
- },
-
- markAsCurrent: function() {},
-
- toggleHighlight: function(toggle) {
- this._container().toggleClass('highlighted-element', toggle);
- },
-
isBlock: function() {
return this.dom.css('display') === 'block';
},
.text(this.wlxmlNode.getText() || utils.unicode.ZWS);
return dom;
},
- detach: function() {
- this.dom.detach();
+ detach: function(isChild) {
+ if(!isChild) {
+ this.dom.detach();
+ }
return this;
},
setText: function(text) {
this.dom.contents()[0].data = text;
}
},
+ handle: function(event) {
+ this.setText(event.meta.node.getText());
+ },
getText: function(options) {
options = _.extend({raw: false}, options || {});
var toret = this.dom.text();
} else {
element = this.canvas.createElement(params);
}
- this.dom.wrap('<div>');
- this.dom.parent().after(element.dom);
- this.dom.unwrap();
- this.refreshPath();
+ if(element.dom) {
+ 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.refreshPath();
+ if(element.dom) {
+ 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 [];
}
wlxml = require('wlxml/wlxml');
-var ElementsRegister = function(BaseType) {
+var ElementsRegister = function(BaseType, NullType) {
this._register = {};
this.BaseType = BaseType;
+ this.NullType = NullType;
};
_.extend(ElementsRegister.prototype, {
},
register: function(params) {
params.klass = params.klass || '';
- params.prototype = params.prototype || Object.create({});
+ params.prototype = params.prototype || this.NullType;
this._register[params.tag] = this._register[params.tag] || {};
this._register[params.tag][params.klass] = this.createCanvasElementType(params.prototype);
'use strict';
var $ = require('libs/jquery'),
+ _ = require('libs/underscore'),
documentElement = require('./documentElement'),
utils = require('./utils'),
- wlxmlUtils = require('utils/wlxml');
+ wlxmlUtils = require('utils/wlxml'),
+ CommentsView = require('./comments/comments');
var labelWidget = function(tag, klass) {
return $('<span>')
.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);
+ var el = this.canvas.createElement(node);
+ if(el.dom) {
+ this._container().append(el.dom);
+ }
}.bind(this));
+
+ this.commentsView = new CommentsView(this.wlxmlNode, this.canvas.metadata.user);
+ this.addToGutter(this.commentsView);
+ this.commentTip = $('<div class="comment-tip"><i class="icon-comment"></i></div>');
+ this.addWidget(this.commentTip);
+
+ if(!this.wlxmlNode.hasChild({klass: 'comment'})) {
+ this.commentTip.hide();
+ }
+
this.refresh();
},
return;
}
- var nodeIndex = event.meta.node.getIndex(),
+ var ptr = event.meta.node.prev(),
referenceElement, referenceAction, actionArg;
+
+ while(ptr && !(referenceElement = utils.getElementForElementRootNode(ptr))) {
+ ptr = ptr.prev();
+ }
- if(nodeIndex === 0) {
+ if(referenceElement) {
+ referenceAction = 'after';
+ } else {
referenceElement = this;
referenceAction = 'prepend';
- } else {
- referenceElement = this.children()[nodeIndex-1];
- referenceAction = 'after';
}
if(event.meta.move) {
/* Let's check if this node had its own canvas element and it's accessible. */
actionArg = utils.getElementForElementRootNode(event.meta.node);
- if(actionArg && actionArg.sameNode(referenceElement)) {
- referenceElement = this.children()[nodeIndex];
- }
}
if(!actionArg) {
actionArg = event.meta.node;
}
referenceElement[referenceAction](actionArg);
+
+ if(event.meta.node.is('comment')) {
+ this.commentTip.show();
+ this.commentsView.render();
+ }
},
onNodeDetached: function(event) {
+ var isComment = event.meta.node.is('comment');
if(event.meta.node.sameNode(this)) {
this.detach();
} else {
return true;
}
});
+ if(isComment && !this.wlxmlNode.hasChild({klass: 'comment'})) {
+ this.commentTip.hide();
+ }
+ this.commentsView.render();
}
},
onNodeTextChange: function(event) {
- var toSet = event.meta.node.getText();
- this.children().some(function(child) {
- if(child.wlxmlNode.sameNode(event.meta.node)) {
+ var node = event.meta.node,
+ toSet = node.getText(),
+ handled;
+
+ handled = this.children().some(function(child) {
+ if(child.wlxmlNode.sameNode(node)) {
if(toSet === '') {
toSet = utils.unicode.ZWS;
}
return true;
}
});
+
+ if(!handled && node.parent() && node.parent().is('comment') && this.wlxmlNode.sameNode(node.parent().parent())) {
+ this.commentsView.render();
+ }
},
+ onStateChange: function(changes) {
+ if(_.isBoolean(changes.exposed) && !this.isSpan()) {
+ this._container().toggleClass('highlighted-element', changes.exposed);
+ }
+ if(_.isBoolean(changes.active) && !this.isSpan()) {
+ this._container().toggleClass('current-node-element', changes.active);
+ }
+ },
///
+
+ isSpan: function() {
+ return this.wlxmlNode.getTagName() === 'span';
+ },
containsBlock: function() {
return this.children()
} else {
element = this.canvas.createElement(param);
}
- this._container().prepend(element.dom);
- this.refreshPath();
+ if(element.dom) {
+ this._container().prepend(element.dom);
+ this.refreshPath();
+ }
return element;
},
font-family: monospace;
z-index:9999;
white-space: nowrap;
+}
+
+.comment-tip {
+ position:absolute;
+ left:-15px;
+ top:3px;
+ opacity: 0.25;
+}
+
+.current-node-element > .canvas-widgets > .comment-tip {
+ opacity: 1;
}
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+
+var $ = require('libs/jquery'),
+ _ = require('libs/underscore'),
+ Backbone = require('libs/backbone'),
+ gutterBoxTemplate = require('libs/text!./gutterBox.html');
+
+
+var GutterView = function(gutter) {
+ gutter.on('show', function(group) {
+ if(this.groupView) {
+ this.groupView.remove();
+ }
+ this.groupView = new GutterGroupView(this, group);
+ this.dom.append(this.groupView.dom);
+ this.groupView.dom.css({top: group.getOffsetHint()});
+ this.groupView.show();
+ }, this);
+ this.dom = $('<div class="gutter"></div>');
+};
+
+
+var GutterGroupView = function(gutterView, group) {
+ this.gutterView = gutterView;
+ this.group = group;
+ this.views = [];
+
+ this.dom = $(gutterBoxTemplate);
+
+ this.dom.on('click', function() {
+ if(!this.dom.hasClass('focused')) {
+ var canvas = this.group.meta.canvas;
+ canvas.setCurrentElement(this.group.meta);
+ }
+ }.bind(this));
+
+ this.group.views.forEach(function(view) {
+ this.onViewAdded(view);
+ }.bind(this));
+
+ this.group.on('viewAdded', this.onViewAdded, this);
+ this.group.on('focusToggled', this.onFocusToggled, this);
+ this.group.on('removed', this.remove, this);
+};
+$.extend(GutterGroupView.prototype, {
+ remove: function() {
+ this.group.off('viewAdded', this.onViewAdded);
+ this.group.off('focusToggled', this.onFocusToggled);
+ this.group.off('removed', this.removed);
+ this.dom.detach();
+ },
+ onViewAdded: function(view) {
+ this.views.push(view);
+ this.dom.append(view.dom);
+ },
+ show: function() {
+ this.dom.addClass('focused');
+ this.views.forEach(function(view) {
+ if(view.onActivated) {
+ view.onActivated();
+ }
+ });
+ }
+});
+
+
+
+/// model
+
+var ViewGroup = function(params, gutter, meta) {
+ this.gutter = gutter;
+ this.params = params;
+ this.meta = meta;
+ this.view = $(gutterBoxTemplate);
+ this.views = [];
+};
+$.extend(ViewGroup.prototype, Backbone.Events, {
+ getOffsetHint: function() {
+ return _.isFunction(this.params.offsetHint) ? this.params.offsetHint() : this.params.offsetHint;
+ },
+ addView: function(view) {
+ this.views.push(view);
+ this.trigger('viewAdded', view);
+ },
+ show: function() {
+ this.gutter.show(this);
+ },
+ remove: function() {
+ this.trigger('removed');
+ }
+});
+
+
+var Gutter = function() {
+};
+
+_.extend(Gutter.prototype, Backbone.Events, {
+ createViewGroup: function(params, meta) {
+ return new ViewGroup(params, this, meta);
+ },
+ show: function(group) {
+ this.trigger('show', group);
+ },
+});
+
+
+return {
+ create: function() {
+ return new Gutter();
+ },
+ GutterView: GutterView,
+ GutterGroupView: GutterGroupView
+};
+
+});
\ No newline at end of file
--- /dev/null
+.gutter {
+ display: table-cell;
+ width: calc(~'100% - 800px');
+ min-width: 250px;
+ padding: 0 25px;
+}
+
+.gutter-box {
+ border: 1px solid darken(#ddd, 10%);
+ padding: 5px 10px;
+ position: relative;
+ background-color: darken(#FFFCB7, 15%);
+}
+
--- /dev/null
+<div class="gutter-box"></div>
--- /dev/null
+define(function(require) {
+
+'use strict';
+var documentElement = require('./documentElement');
+
+
+var NullElement = Object.create(documentElement.DocumentNodeElement.prototype);
+
+NullElement.init = function() {
+ this.dom = null;
+ this.wlxmlNode.setData('canvasElement', undefined);
+};
+
+return NullElement;
+
+});
\ No newline at end of file
return true;
}
});
- return toret;
+ return toret || parentElement;
};
var getElementForDetachedNode = function(node, originalParent) {
},
nodeTextChange: function(event) {
var element = utils.getElementForNode(event.meta.node);
- element.setText(event.meta.node.getText());
+ element.handle(event);
},
metadataChanged: _metadataEventHandler,
canvasElements = canvasElements.concat(plugin.canvasElements || []);
});
- var canvas = canvas3.fromXMLDocument(null, canvasElements);
+ var canvas = canvas3.fromXMLDocument(null, canvasElements, {
+ user: sandbox.getConfig().user
+ });
var canvasWrapper = $(template);
var shownAlready = false;
var scrollbarPosition = 0,
},
setDocument: function(wlxmlDocument) {
canvas.loadWlxmlDocument(wlxmlDocument);
- canvasWrapper.find('#rng-module-documentCanvas-content').empty().append(canvas.view());
+ canvasWrapper.find('#rng-module-documentCanvas-contentWrapper').empty().append(canvas.view());
},
highlightElement: function(node) {
canvas.toggleElementHighlight(node, true);
@import 'nodes.less';
+@import 'canvas/canvas.less';
@import 'canvas/documentElement.less';
@import 'canvas/genericElement.less';
+@import 'canvas/gutter.less';
+@import 'canvas/comments/comments.less';
#rng-module-documentCanvas {
height: 100%;
}
-#rng-module-documentCanvas-mainArea {
- height: 100%;
- margin-bottom: 20px;
-}
-
#rng-module-documentCanvas-contentWrapper {
- border-color: #ddd;
- border-style: solid;
- border-width: 1px;
+ background-color: #ddd;
float:left;
width: 100%;
height: 100%;
overflow-y: scroll;
- padding: 0 10px;
+ &:before {
+ content: "";
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: -15px;
+ right: -15px;
+ box-shadow: inset 0 20px 12px -20px rgba(0,0,0,0.6);
+ pointer-events:none;
+ z-index: 1;
+ }
+
+ &:after {
+ content: "";
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: -15px;
+ right: -15px;
+ box-shadow: inset 0px -20px 12px -20px rgba(0,0,0,0.6);
+ pointer-events:none;
+ z-index: 1;
+ }
+
&::-webkit-scrollbar {
.rng-mixin-scrollbar;
}
.rng-mixin-scrollbar-thumb-window-inactive;
}
- .canvas-wrapper {
+ .root-wrapper {
+ display: table-cell;
+ vertical-align: top;
+ width: 600px;
outline: 0px solid transparent;
+ padding: 0 100px;
+ background-color: white;
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.2), inset 0 0 10px rgba(0, 0, 0, 0.1);
}
.current-text-element {
<div id="rng-module-documentCanvas">
- <div id="rng-module-documentCanvas-mainArea">
- <div id="rng-module-documentCanvas-contentWrapper">
- <div id="rng-module-documentCanvas-content"></div>
- </div>
+ <div id="rng-module-documentCanvas-contentWrapper">
</div>
</div>
\ No newline at end of file
trigger.data('originalContent', trigger.html());
trigger.text(disabledText);
}
+ },
+ setSummaryView: function(summaryView) {
+ view.find('.bottom').prepend(summaryView);
}
};
border-style: solid;
margin: 0 5px 0 0;
padding: 0 5px 0 0;
- &:last-child {
- margin-right: 0;
- padding-right: 0;
- }
+
}
ul {
list-style-type: none;
+ display: inline-block;
+ color: #8F8A8A;
+ margin: 0;
+ &:last-child {
+ li:last-child {
+ margin-right: 0;
+ padding-right: 0;
+ }
+ }
}
.top {
<%= userName %> (<a href="/"><%= gettext('Exit') %></a>)
</div>
<div style="clear:both;"></div>
- <ul class="bottom">
- <li><a href="#" data-cmd="drop-draft" data-disabled-text="<%= gettext('no draft exists') %>"><%= gettext('drop a working draft') %></a></li>
- <li><button class="btn btn-mini btn-info" data-cmd="save"><%= gettext('Save') %></button></li>
- </ul>
+ <div class="bottom">
+ <ul>
+ <li><a href="#" data-cmd="drop-draft" data-disabled-text="<%= gettext('no draft exists') %>"><%= gettext('drop a working draft') %></a></li>
+ <li><button class="btn btn-mini btn-info" data-cmd="save"><%= gettext('Save') %></button></li>
+ </ul>
+ </div>
</div>
\ No newline at end of file
-<div>
- <h1 class="title"><%= title %></h1>
- <table>
- <% properties.forEach(function(propertyDesc) { %>
- <tr>
- <td><%= propertyDesc.label %></td>
- <td><%= propertyValues[propertyDesc.name] %></td>
- <% }); %>
- <tr>
- <td><%= gettext('Draft Saved') %></td>
- <td class="draft"></td>
- </tr>
- </table>
-</div>
\ No newline at end of file
+<li><%= gettext('Draft Saved') %>: <span class="draft"></span></li>
+<% properties.forEach(function(propertyDesc) { %>
+ <li>
+ <%= propertyDesc.label %>: <%= propertyValues[propertyDesc.name] %>
+ </li>
+<% }); %>
var view = {
- dom: $('<div class="documentSummary"></div>'),
+ dom: $('<ul></ul>'),
init: function(config, doc) {
this.config = config;
this.doc = doc;
<div class="fnp-module-rng-editingLayout">
<div fnpjs-place="toolbar"></div>
<div class="rng-module-rng2-left" fnpjs-place="leftColumn"></div>
- <div class="rng-module-rng2-right" fnpjs-place="rightColumn"></div>
- <div style="clear:both;"></div>
- <div class="rng-module-rng2-statusBar" fnpjs-place="statusBar"></div>
</div>
\ No newline at end of file
width: 600px;*/
}
-.rng-module-rng2-right {
- /*float: right;
- position: relative;
- width: 258px;
- margin-left: 50px;*/
-
- border-width: 1px 1px 1px 1px;
- border-style: solid;
- border-color: #ddd;
- padding: 5px 15px;
-
- p, td, label, input, select {
- font-size: 11px;
- line-height:13px;
- }
-
- select {
- -webkit-appearance: button;
- -moz-appearance: button;
- appearance: button;
- height: auto;
- line-height: 14px;
- }
-
- legend {
- font-size:11px;
- height:30px;
- }
-
-
- .rng-view-tabs-tabBar {
- position:absolute;
- top:-1px;
- right:-50px;
- border-width: 1px 1px 1px 0px;
- border-style: solid;
- border-color: #ddd;
- padding: 5px;
- background: #ededed;
- }
-
- label + select {
- position:relative;
- top: 5px;
- }
-
-}
-
.rng-module-rng2-statusBar {
margin: 10px 5px;
font-size:0.9em;
.fnp-module-rng-editingLayout {
+ margin-left: 60px;
+
[fnpjs-place="statusBar"] {
position: absolute;
bottom: 0;
}
- [fnpjs-place="leftColumn"], [fnpjs-place="rightColumn"] {
+ [fnpjs-place="leftColumn"] {
position: absolute;
top: 30px; //
- bottom: 50px; //
- }
-
- [fnpjs-place="leftColumn"] {
+ bottom: 0px; //
left:0;
- right: 360px;
- }
-
- [fnpjs-place="rightColumn"] {
- right: 0px;
- width: 290px;
-
+ right: 0;
}
-
-
}
\ No newline at end of file
position: fixed;
top: 5px;
bottom: 5px;
- left: 80px;
- right: 80px;
+ left: 0px;
+ right: 0px;
[fnpjs-place="messages"] {
position: absolute;
float: right;
position: relative;
z-index: 2;
+ margin-right: 60px;
}
[fnpjs-place="mainView"] {
> .rng-view-tabs {
position: relative;
height: 100%;
+ padding-left: 60px;
> .rng-view-tabs-content {
position: absolute;
'./documentSummary',
'libs/underscore',
'fnpjs/layout',
-'fnpjs/vbox',
'fnpjs/logging/logging',
'views/tabs/tabs',
'libs/text!./mainLayout.html',
'libs/text!./editingLayout.html',
'libs/text!./diffLayout.html',
-], function(documentSummary, _, layout, vbox, logging, tabs, mainLayoutTemplate, visualEditingLayoutTemplate, diffLayoutTemplate) {
+], function(documentSummary, _, layout, logging, tabs, mainLayoutTemplate, visualEditingLayoutTemplate, diffLayoutTemplate) {
'use strict';
if(fragment && fragment.node) {
elementParent = fragment.node.getNearestElementNode();
sandbox.getModule('nodeBreadCrumbs').setNodeElement(elementParent);
- sandbox.getModule('metadataEditor').setNodeElement(elementParent);
} else {
sandbox.getModule('nodeBreadCrumbs').setNodeElement(null);
- sandbox.getModule('metadataEditor').setNodeElement(null);
}
},
};
mainLayout: new layout.Layout(mainLayoutTemplate),
mainTabs: (new tabs.View()).render(),
visualEditing: new layout.Layout(visualEditingLayoutTemplate),
- visualEditingSidebar: (new tabs.View({stacked: true})).render(),
- currentNodePaneLayout: new vbox.VBox(),
diffLayout: new layout.Layout(diffLayoutTemplate)
};
- views.visualEditing.setView('rightColumn', views.visualEditingSidebar.getAsView());
addMainTab(gettext('Editor'), 'editor', views.visualEditing.getAsView());
addMainTab(gettext('Source'), 'sourceEditor', '');
addMainTab(gettext('History'), 'history', views.diffLayout.getAsView());
sandbox.getDOM().append(views.mainLayout.getAsView());
- views.visualEditingSidebar.addTab({icon: 'pencil'}, 'edit', views.currentNodePaneLayout.getAsView());
-
var wlxmlDocument, documentIsDirty;
/* Events handling */
documentSummary.init(sandbox.getConfig().documentSummaryView, wlxmlDocument);
documentSummary.render();
documentSummary.setDraftField(usingDraft ? (draftTimestamp || '???') : '-');
- views.currentNodePaneLayout.appendView(documentSummary.dom);
+ sandbox.getModule('mainBar').setSummaryView(documentSummary.dom);
sandbox.getModule('mainBar').setCommandEnabled('drop-draft', usingDraft);
sandbox.getModule('mainBar').setCommandEnabled('save', usingDraft);
- _.each(['sourceEditor', 'documentCanvas', 'documentToolbar', 'metadataEditor', 'nodeBreadCrumbs', 'mainBar', 'indicator', 'documentHistory', 'diffViewer', 'statusBar'], function(moduleName) {
+ _.each(['sourceEditor', 'documentCanvas', 'documentToolbar', 'mainBar', 'indicator', 'documentHistory', 'diffViewer', 'statusBar'], function(moduleName) {
sandbox.getModule(moduleName).start();
});
}
};
- eventHandlers.metadataEditor = {
- ready: function() {
- sandbox.getModule('metadataEditor').setDocument(sandbox.getModule('data').getDocument());
- views.visualEditingSidebar.addTab({icon: 'info-sign'}, 'metadataEditor', sandbox.getModule('metadataEditor').getView());
- }
- };
-
eventHandlers.documentToolbar = {
ready: function() {
views.visualEditing.setView('toolbar', sandbox.getModule('documentToolbar').getView());
return [
- {tag: 'aside', klass: 'comment', prototype: comment},
+ {tag: 'aside', klass: 'comment', prototype: null},
{tag: 'aside', klass: 'footnote', prototype: footnote},
{tag: 'span', klass: 'link', prototype: linkElement}
];
return true; // break
}
});
+ newNodes.second.contents()
+ .filter(function(child) {
+ return child.object.describesParent;
+ })
+ .forEach(function(child) {
+ //child.detach();
+ newNodes.first.append(child);
+ });
return _.extend(newNodes, {emptyText: emptyText});
},
getChangeRoot: function() {
this.box.hide();
this.addWidget(this.box);
},
- markAsCurrent: function(toggle) {
- this.box.toggle(toggle);
+ onStateChange: function(changes) {
+ genericElement.onStateChange.call(this, changes);
+ if(_.isBoolean(changes.active)) {
+ this.box.toggle(changes.active);
+ }
},
onNodeAttrChange: function(event) {
if(event.meta.attr === 'href') {
.rng-mixin-scrollbar {
- width: 9px;
+ width: 12px;
}
.rng-mixin-scrollbar-track {
return;
}
+ this.contents()
+ .filter(function(child) {
+ return child.getProperty('describesParent');
+ }.bind(this))
+ .forEach(function(child) {
+ child.detach();
+ });
+
var myContents = this.contents(),
myIdx = parent.indexOf(this);
-
if(myContents.length === 0) {
return this.detach();
}
-
var childrenLength = this.contents().length,
first = true,
shiftRange = false;
}
for(var i = idx1; i <= idx2; i++) {
- wrapper.append(parentContents[i].detach());
+ if(!parentContents[i].getProperty('describesParent')) {
+ wrapper.append(parentContents[i].detach());
+ }
}
insertingTarget[insertingMethod](wrapper);
wrapperElement.append({text: prefixInside});
}
for(var i = idx1 + 1; i < idx2; i++) {
- wrapperElement.append(contentsInside[i]);
+ if(!contentsInside[i].getProperty('describesParent')) {
+ wrapperElement.append(contentsInside[i]);
+ }
}
if(suffixInside.length > 0) {
wrapperElement.append({text: suffixInside});
throw new Error('undefined document for a node');
}
this.document = document;
+ this.object = {};
this._setNativeNode(nativeNode);
};
$.extend(DocumentNode.prototype, {
+ getProperty: function(propName) {
+ var toret = this.object[propName];
+ if(toret && _.isFunction(toret)) {
+ toret = toret.call(this);
+ }
+ return toret;
+ },
+
transform: function(Transformation, args) {
var transformation = new Transformation(this.document, this, args);
return this.document.transform(transformation);
expect(node.contents()[2].getText()).to.equal(' a cat!');
});
+ it('removes parent-describing sibling nodes of unwrapped node', function() {
+ var doc = getDocumentFromXML('<root><div><a></a><x></x><a></a></div></root>'),
+ div = doc.root.contents()[0],
+ x = div.contents()[1];
+
+ doc.registerExtension({documentNode: {methods: {
+ object: {
+ describesParent: function() {
+ return this.getTagName() === 'x';
+ }
+ }
+ }}});
+
+ div.unwrapContent();
+ expect(doc.root.contents().length).to.equal(2);
+ expect(x.isInDocument()).to.be.false;
+ });
+
it('unwrap single element node from its parent', function() {
var doc = getDocumentFromXML('<div><a><b></b></a></div>'),
div = doc.root,
expect(wrapperContents[1].contents().length).to.equal(1);
expect(wrapperContents[1].contents()[0].getText()).to.equal('small');
});
+
+ it('keeps parent-describing nodes in place', function() {
+ var doc = getDocumentFromXML('<root>Alice<x></x> has a cat</root>'),
+ root = doc.root,
+ x = root.contents()[1];
+
+ doc.registerExtension({documentNode: {methods: {
+ object: {
+ describesParent: function() {
+ return this.getTagName() === 'x';
+ }
+ }
+ }}});
+
+ root.wrapText({
+ _with: {tagName: 'span', attrs: {'attr1': 'value1'}},
+ offsetStart: 1,
+ offsetEnd: 4,
+ textNodeIdx: [0,2]
+ });
+ expect(x.parent().sameNode(root)).to.be.true;
+ });
});
describe('Wrapping Nodes', function() {
expect(headerChildren[0].sameNode(div2)).to.equal(true, 'first node wrapped');
expect(headerChildren[1].sameNode(div3)).to.equal(true, 'second node wrapped');
});
+
+ it('keeps parent-describing nodes in place', function() {
+ var section = elementNodeFromXML('<section>Alice<x></x><div>a cat</div></section>'),
+ aliceText = section.contents()[0],
+ x = section.contents()[1],
+ lastDiv = section.contents()[2];
+
+ section.document.registerExtension({documentNode: {methods: {
+ object: {
+ describesParent: function() {
+ return this.getTagName() === 'x';
+ }
+ }
+ }}});
+
+ section.document.wrapNodes({
+ node1: aliceText,
+ node2: lastDiv,
+ _with: {tagName: 'header'}
+ });
+
+ expect(x.parent().sameNode(section)).to.be.true;
+ });
});
});
--- /dev/null
+define(function() {
+
+'use strict';
+
+var extension = {wlxmlClass: {comment: {
+ methods: {
+ describesParent: true,
+ getText: function() {
+ var text = '';
+ this.contents()
+ .filter(function(node) {
+ /* globals Node */
+ return node && node.nodeType === Node.TEXT_NODE;
+ })
+ .forEach(function(node) {
+ text = text + node.getText();
+ });
+ return text;
+ },
+ setText: function(text) {
+ var contents = this.contents();
+ if(contents.length === 1 && contents[0].nodeType === Node.TEXT_NODE) {
+ contents[0].setText(text);
+ } else {
+ contents.forEach(function(node) {
+ node.detach();
+ });
+ this.append({text: text});
+ }
+ }
+ }
+}}};
+
+return extension;
+
+});
\ No newline at end of file
'libs/underscore',
'smartxml/smartxml',
'smartxml/transformations',
- 'wlxml/extensions/metadata/metadata'
-], function($, _, smartxml, transformations, metadataExtension) {
+ 'wlxml/extensions/metadata/metadata',
+ 'wlxml/extensions/comments/comments'
+], function($, _, smartxml, transformations, metadataExtension, commentExtension) {
'use strict';
});
instance.object = Object.create(_.extend({}, methods, transformations));
_.keys(methods).concat(_.keys(transformations)).forEach(function(key) {
- instance.object[key] = _.bind(instance.object[key], instance);
+ if(_.isFunction(instance.object[key])) {
+ instance.object[key] = _.bind(instance.object[key], instance);
+ }
});
};
return (_.isUndefined(query.klass) || this.getClass().substr(0, query.klass.length) === query.klass) &&
(_.isUndefined(query.tagName) || this.getTagName() === query.tagName);
},
+ hasChild: function(query) {
+ return this.contents().some(function(child) {
+ return child.is(query);
+ }.bind(this));
+ },
getMetaAttributes: function() {
var toret = new AttributesList(),
classParts = [''].concat(this.getClass().split('.')),
smartxml.TextNode.apply(this, arguments);
};
WLXMLTextNode.prototype = Object.create(smartxml.TextNode.prototype);
-$.extend(WLXMLTextNode.prototype, WLXMLDocumentNodeMethods);
+$.extend(WLXMLTextNode.prototype, WLXMLDocumentNodeMethods, {
+ is: function() { return false; }
+});
var WLXMLDocument = function(xml, options) {
this.classMethods = {};
this.classTransformations = {};
- smartxml.Document.call(this, xml, [metadataExtension]);
+ smartxml.Document.call(this, xml, [metadataExtension, commentExtension]);
this.options = options;
};
expect(testClassNode.object.testMethod().sameNode(testClassNode)).to.equal(true, '1');
});
+ it('allows adding non-function properties to an ElementNode of specific class', function() {
+ extension = {wlxmlClass: {test_class: {methods: {
+ testProp: 123
+ }}}};
+ doc.registerExtension(extension);
+ testClassNode = doc.root.contents()[1];
+ expect(testClassNode.object.testProp).to.equal(123);
+ });
+
it('allows adding transformation to an ElementNode of specific class', function() {
extension = {wlxmlClass: {test_class: {transformations: {
testTransformation: function() { return this; },