data: require('modules/data/data'),
rng: require('modules/rng/rng'),
mainBar: require('modules/mainBar/mainBar'),
+ statusBar: require('modules/statusBar/statusBar'),
indicator: require('modules/indicator/indicator'),
sourceEditor: require('modules/sourceEditor/sourceEditor'),
], function($, _, Backbone, logging, documentElement, keyboard, utils, wlxmlListener) {
'use strict';
-/* global document:false, window:false, Node:false */
+/* global document:false, window:false, Node:false, gettext */
var logger = logging.getLogger('canvas');
},
setText: function(text, node) {
//this.canvas.wlxmlDocument.transform('setText', {node:node, text: text});
- node.setText(text);
+ node.document.transaction(function() {
+ node.setText(text);
+ }, {
+ metadata:{
+ description: gettext('Changing text')
+ }
+ });
}
});
-var Canvas = function(wlxmlDocument, publisher) {
+var Canvas = function(wlxmlDocument) {
this.eventBus = _.extend({}, Backbone.Events);
this.wrapper = $('<div>').addClass('canvas-wrapper').attr('contenteditable', true);
this.wlxmlListener = wlxmlListener.create(this);
this.loadWlxmlDocument(wlxmlDocument);
this.setupEventHandling();
- this.publisher = publisher ? publisher : function() {};
this.textHandler = new TextHandler(this);
};
-$.extend(Canvas.prototype, {
+$.extend(Canvas.prototype, Backbone.Events, {
loadWlxmlDocument: function(wlxmlDocument) {
if(!wlxmlDocument) {
setupEventHandling: function() {
var canvas = this;
+
this.wrapper.on('keyup keydown keypress', function(e) {
- keyboard.handleKey(e, this);
- }.bind(this));
+ keyboard.handleKey(e, canvas);
+ });
+
+ this.wrapper.on('mouseup', function() {
+ canvas.triggerSelectionChanged();
+ });
var mouseDown;
this.wrapper.on('mousedown', '[document-node-element], [document-text-element]', function(e) {
return element.dom().parents().index(this.wrapper) !== -1;
},
+ triggerSelectionChanged: function() {
+ this.trigger('selectionChanged', this.getSelection());
+ },
+
+ getSelection: function() {
+ return new Selection(this);
+ },
+
setCurrentElement: function(element, params) {
if(!element) {
logger.debug('Invalid element passed to setCurrentElement: ' + element);
if(params.caretTo || !textElementToLand.sameNode(this.getCursor().getPosition().element)) {
this._moveCaretToTextElement(textElementToLand, params.caretTo); // as method on element?
}
- if(!(textElementToLand.sameNode(currentTextElement))) {
- this.publisher('currentTextElementSet', textElementToLand.wlxmlNode);
- }
} else {
document.getSelection().removeAllRanges();
}
if(!(currentNodeElement && currentNodeElement.sameNode(nodeElementToLand))) {
_markAsCurrent(nodeElementToLand);
-
- this.publisher('currentNodeElementSet', nodeElementToLand.wlxmlNode);
}
+ this.triggerSelectionChanged();
},
_moveCaretToTextElement: function(element, where) {
if(typeof where !== 'number') {
range.selectNodeContents(node);
} else {
- range.setStart(node, where);
+ range.setStart(node, Math.min(node.data.length, where));
}
if(where !== 'whole') {
if(position.element) {
this._moveCaretToTextElement(position.element, position.offset);
}
+ },
+
+ findCanvasElement: function(node) {
+ return utils.findCanvasElement(node);
+ },
+
+ toggleGrid: function() {
+ this.wrapper.toggleClass('grid-on');
+ this.trigger('changed');
+ },
+ isGridToggled: function() {
+ return this.wrapper.hasClass('grid-on');
}
});
+var isText = function(node) {
+ return node && node.nodeType === Node.TEXT_NODE && $(node.parentNode).is('[document-text-element]');
+};
+
+var Selection = function(canvas) {
+ this.canvas = canvas;
+ var nativeSelection = this.nativeSelection = window.getSelection();
+ Object.defineProperty(this, 'type', {
+ get: function() {
+ if(nativeSelection.focusNode) {
+ if(nativeSelection.isCollapsed && isText(nativeSelection.focusNode)) {
+ return 'caret';
+ }
+ if(isText(nativeSelection.focusNode) && isText(nativeSelection.anchorNode)) {
+ return 'textSelection';
+ }
+ }
+ if(canvas.getCurrentNodeElement()) {
+ return 'node';
+ }
+ }
+ });
+};
+
+$.extend(Selection.prototype, {
+ toDocumentFragment: function() {
+ var doc = this.canvas.wlxmlDocument,
+ anchorElement = this.canvas.getDocumentElement(this.nativeSelection.anchorNode),
+ focusElement = this.canvas.getDocumentElement(this.nativeSelection.focusNode),
+ anchorNode = anchorElement ? anchorElement.wlxmlNode : null,
+ focusNode = focusElement ? focusElement.wlxmlNode : null;
+ if(this.type === 'caret') {
+ return doc.createFragment(doc.CaretFragment, {node: anchorNode, offset: this.nativeSelection.anchorOffset});
+ }
+ if(this.type === 'textSelection') {
+ if(anchorNode.isSiblingOf(focusNode)) {
+ return doc.createFragment(doc.TextRangeFragment, {
+ node1: anchorNode,
+ offset1: this.nativeSelection.anchorOffset,
+ node2: focusNode,
+ offset2: this.nativeSelection.focusOffset,
+ });
+ }
+ else {
+ var siblingParents = doc.getSiblingParents({node1: anchorNode, node2: focusNode});
+ return doc.createFragment(doc.RangeFragment, {
+ node1: siblingParents.node1,
+ node2: siblingParents.node2
+ });
+ }
+ }
+ if(this.type === 'node') {
+ return doc.createFragment(doc.NodeFragment, {node: this.canvas.getCurrentNodeElement().wlxmlNode});
+ }
+ },
+ sameAs: function(other) {
+ void(other);
+ }
+});
+
var Cursor = function(canvas) {
this.canvas = canvas;
+ this.selection = window.getSelection();
};
$.extend(Cursor.prototype, {
+ sameAs: function(other) {
+ var same = true;
+ if(!other) {
+ return false;
+ }
+
+ ['focusNode', 'focusOffset', 'anchorNode', 'anchorOffset'].some(function(prop) {
+ same = same && this.selection[prop] === other.selection[prop];
+ if(!same) {
+ return true; // break
+ }
+ }.bind(this));
+
+ return same;
+ },
isSelecting: function() {
var selection = window.getSelection();
return !selection.isCollapsed;
});
return {
- fromXMLDocument: function(wlxmlDocument, publisher) {
- return new Canvas(wlxmlDocument, publisher);
+ fromXMLDocument: function(wlxmlDocument) {
+ return new Canvas(wlxmlDocument);
}
};
], function(documentElement, utils) {
'use strict';
-
+/* globals gettext */
var KEYS = {
ENTER: 13,
event.preventDefault();
var cursor = canvas.getCursor(),
position = cursor.getPosition(),
- element = position.element,
- added;
+ element = position.element;
if(Object.keys(cursor.getPosition()).length === 0) {
var currentElement = canvas.getCurrentNodeElement();
if(currentElement) {
- canvas.wlxmlDocument.startTransaction();
- added = currentElement.wlxmlNode.after({
- tagName: currentElement.getWlxmlTag() || 'div',
- attrs: {'class': currentElement.getWlxmlClass() || 'p'}
+ canvas.wlxmlDocument.transaction(function() {
+ var added = currentElement.wlxmlNode.after({
+ tagName: currentElement.getWlxmlTag() || 'div',
+ attrs: {'class': currentElement.getWlxmlClass() || 'p'}
+ });
+ added.append({text:''});
+ return added;
+ }, {
+ metadata: {
+ description: gettext('Splitting text')
+ },
+ success: function(ret) {
+ canvas.setCurrentElement(utils.findCanvasElement(ret), {caretTo: 'start'});
+ }
});
- added.append({text:''});
- canvas.wlxmlDocument.endTransaction();
- canvas.setCurrentElement(utils.findCanvasElement(added), {caretTo: 'start'});
+
}
return;
}
element = element.parent();
}
- canvas.wlxmlDocument.startTransaction();
- added = element.wlxmlNode.after(
- {tagName: element.getWlxmlTag() || 'div', attrs: {'class': element.getWlxmlClass() || 'p'}}
- );
- added.append({text: ''});
- canvas.wlxmlDocument.endTransaction();
- canvas.setCurrentElement(utils.findCanvasElement(added), {caretTo: 'start'});
+ canvas.wlxmlDocument.transaction(function() {
+ var added = element.wlxmlNode.after(
+ {tagName: element.getWlxmlTag() || 'div', attrs: {'class': element.getWlxmlClass() || 'p'}}
+ );
+ added.append({text: ''});
+ return added;
+ }, {
+ metadata: {
+ description: gettext('Splitting text')
+ },
+ success: function(ret) {
+ canvas.setCurrentElement(utils.findCanvasElement(ret), {caretTo: 'start'});
+ }
+ });
} else {
// goto = nodes.second;
// gotoOptions = {caretTo: 'start'};
// }
+ var node = position.element.wlxmlNode,
+ result, goto, gotoOptions;
+
+ node.document.transaction(function() {
+ result = position.element.wlxmlNode.breakContent({offset: position.offset});
+ }, {
+ metadata: {
+ description: gettext('Splitting text')
+ }
+ });
- var result = position.element.wlxmlNode.breakContent({offset: position.offset}),
- goto, gotoOptions;
if(result.emptyText) {
goto = result.emptyText;
gotoOptions = {};
}
event.preventDefault();
}
+ }, {
+ metadata: {
+ description: gettext('Remove text')
+ }
});
}
});
}
}, this);
+ wlxmlDocument.on('operationEnd', function() {
+ this.canvas.triggerSelectionChanged();
+ }, this);
+
wlxmlDocument.on('contentSet', function() {
this.canvas.loadWlxmlDocument(wlxmlDocument);
}, this);
+++ /dev/null
-define([
-'./canvas/utils',
-'views/dialog/dialog',
-'fnpjs/datetime'
-], function(utils, Dialog, datetime) {
-
-'use strict';
-/* globals gettext */
-
-
-var gridToggled = false;
-
-var commands = {
- _cmds: {},
- register: function(name, command) {
- this._cmds[name] = command;
- },
-
- run: function(name, params, canvas, user) {
- return this._cmds[name](canvas, params, user);
- }
-};
-
-commands.register('undo', function(canvas) {
- var doc = canvas.wlxmlDocument;
-
- doc.undo();
-});
-
-commands.register('redo', function(canvas) {
- var doc = canvas.wlxmlDocument;
-
- doc.redo();
-});
-
-commands.register('remove-node', function(canvas) {
- canvas.getCurrentNodeElement().wlxmlNode.detach();
-});
-
-commands.register('unwrap-node', function(canvas) {
- var cursor = canvas.getCursor(),
- selectionStart = cursor.getSelectionStart(),
- selectionEnd = cursor.getSelectionEnd(),
- parent1 = selectionStart.element.parent() || undefined,
- parent2 = selectionEnd.element.parent() || undefined;
-
- var selectionAnchor = cursor.getSelectionAnchor(),
- node1 = parent1.wlxmlNode,
- node2 = parent2.wlxmlNode,
- doc = node1.document;
- if(doc.areItemsOfSameList({node1: node1, node2: node2})) {
- doc.extractItems({item1: node1, item2: node2});
- canvas.setCurrentElement(selectionAnchor.element, {caretTo: selectionAnchor.offset});
- } else if(!cursor.isSelecting()) {
- var nodeToUnwrap = cursor.getPosition().element.wlxmlNode,
- parentNode = nodeToUnwrap.unwrap();
- if(parentNode) {
- canvas.setCurrentElement(utils.findCanvasElement(parentNode));
- }
- }
-});
-
-commands.register('wrap-node', function(canvas) {
- var cursor = canvas.getCursor(),
- selectionStart = cursor.getSelectionStart(),
- selectionEnd = cursor.getSelectionEnd(),
- parent1 = selectionStart.element.parent() || undefined,
- parent2 = selectionEnd.element.parent() || undefined;
-
- var node1 = parent1.wlxmlNode,
- node2 = parent2.wlxmlNode,
- doc = node1.document;
-
- if(doc.areItemsOfSameList({node1: node1, node2: node2})) {
- doc.createList({node1: node1, node2: node2});
- }
-});
-
-commands.register('list', function(canvas, params) {
- void(params);
- var cursor = canvas.getCursor(),
- selectionStart = cursor.getSelectionStart(),
- selectionEnd = cursor.getSelectionEnd(),
- parent1 = selectionStart.element.parent() || undefined,
- parent2 = selectionEnd.element.parent() || undefined,
- selectionFocus = cursor.getSelectionFocus(),
- node1 = parent1.wlxmlNode,
- node2 = parent2.wlxmlNode,
- doc = node1.document;
-
- if(cursor.isSelecting()) {
- doc.transaction(function() {
- doc.createList({node1: node1, node2: node2, klass: params.meta === 'num' ? 'list.enum' : 'list'});
- }, {
- success: function() {
- canvas.setCurrentElement(selectionFocus.element, {caretTo: selectionFocus.offset});
- }
- });
- } else {
- var list;
- if(node1.isInside('list')) {
- list = node1.getParent('list');
- if((params.meta === 'num' && list.getClass() === 'list.enum') || params.meta !== 'num' && list.getClass() === 'list') {
- list.object.extractAllItems();
- } else {
- list.setClass(params.meta === 'num' ? 'list.enum' : 'list');
- }
- }
- }
-});
-
-commands.register('toggle-grid', function(canvas, params) {
- canvas.doc().dom().parent().toggleClass('grid-on', params.toggle);
- gridToggled = params.toggle;
-});
-
-commands.register('newNodeRequested', function(canvas, params, user) {
- var cursor = canvas.getCursor(),
- selectionStart = cursor.getSelectionStart(),
- selectionEnd = cursor.getSelectionEnd(),
- wlxmlNode, caretTo, wrapperCanvasElement;
-
- var insertNode = function(insertion, callback) {
- var doc = canvas.wlxmlDocument,
- metadata, creator, dialog;
-
- var execCallback = function(node) {
- if(callback) {
- callback(node);
- }
- };
-
- if(params.wlxmlTag === 'aside' && params.wlxmlClass === 'comment') {
- doc.transaction(function() {
- var node = insertion();
- if(user) {
- creator = user.name;
- if(user.email) {
- creator += ' (' + user.email + ')';
- }
- } else {
- creator = 'anonymous';
- }
-
- metadata = node.getMetadata();
- metadata.add({key: 'creator', value: creator});
- metadata.add({key: 'date', value: datetime.currentStrfmt()});
- return node;
- }, {
- success: execCallback
- });
- } else if(params.wlxmlClass === 'link') {
- dialog = Dialog.create({
- title: gettext('Create link'),
- executeButtonText: gettext('Apply'),
- cancelButtonText: gettext('Cancel'),
- fields: [
- {label: gettext('Link'), name: 'href', type: 'input'}
- ]
- });
- dialog.on('execute', function(event) {
- doc.transaction(function() {
- var node = insertion();
- node.setAttr('href', event.formData.href);
- event.success();
- return node;
- }, {
- success: execCallback
- });
- });
- dialog.show();
- } else {
- doc.transaction(function() {
- return insertion();
- }, {success: execCallback});
- }
- };
-
- if(cursor.isSelecting()) {
- if(cursor.isSelectingSiblings()) {
- if(cursor.isSelectingWithinElement()) {
- wlxmlNode = selectionStart.element.wlxmlNode;
- caretTo = selectionStart.offset < selectionEnd.offset ? 'start' : 'end';
-
- insertNode(
- function() {
- return wlxmlNode.wrapWith({tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}, start: selectionStart.offset, end: selectionEnd.offset});
- },
- function(wrapper) {
- wrapperCanvasElement = utils.findCanvasElement(wrapper);
- canvas.setCurrentElement(wrapperCanvasElement.children()[0], {caretTo: caretTo});
- }
- );
- }
- else {
- wlxmlNode = selectionStart.element.wlxmlNode.parent();
- caretTo = selectionStart.element.sameNode(cursor.getSelectionAnchor().element) ? 'end' : 'start';
-
- insertNode(
- function() {
- return wlxmlNode.wrapText({
- _with: {tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}},
- offsetStart: selectionStart.offset,
- offsetEnd: selectionEnd.offset,
- textNodeIdx: [wlxmlNode.indexOf(selectionStart.element.wlxmlNode), wlxmlNode.indexOf(selectionEnd.element.wlxmlNode)] //parent.childIndex(selectionEnd.element)]
- });
- },
- function(wrapper) {
- wrapperCanvasElement = utils.findCanvasElement(wrapper);
- canvas.setCurrentElement(wrapperCanvasElement.children()[caretTo === 0 ? 0 : wrapperCanvasElement.children().length - 1], {caretTo: caretTo});
- }
- );
- }
- } else {
- var node1 = selectionStart.element.wlxmlNode,
- node2 = selectionEnd.element.wlxmlNode,
- siblingParents = canvas.wlxmlDocument.getSiblingParents({node1: node1, node2: node2});
-
- if(siblingParents) {
- insertNode(
- function() {
- return canvas.wlxmlDocument.wrapNodes({
- node1: siblingParents.node1,
- node2: siblingParents.node2,
- _with: {tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}}
- });
- }
- );
- }
- }
- } else if(canvas.getCurrentNodeElement()) {
- wlxmlNode = canvas.getCurrentNodeElement().wlxmlNode;
-
- var linkFound = [wlxmlNode].concat(wlxmlNode.parents()).some(function(node) {
- if(node.getClass() === 'link') {
- var dialog = Dialog.create({
- title: gettext('Edit link'),
- executeButtonText: gettext('Apply'),
- cancelButtonText: gettext('Cancel'),
- fields: [
- {label: gettext('Link'), name: 'href', type: 'input', initialValue: node.getAttr('href')},
- ]
- });
- dialog.on('execute', function(event) {
- canvas.wlxmlDocument.transaction(function() {
- node.setAttr('href', event.formData.href);
- event.success();
- });
- });
- dialog.show();
- return true;
- }
- });
- if(linkFound) {
- return;
- }
-
- if(params.ctrlKey) {
- insertNode(
- function() {
- return wlxmlNode.wrapWith({tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}});
- },
- function(wrapper) {
- canvas.setCurrentElement(utils.findCanvasElement(wrapper));
- }
- );
- } else {
- insertNode(
- function() {
- var node = wlxmlNode.after({tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}});
- node.append({text:''});
- return node;
- }, function(wrapper) {
- canvas.setCurrentElement(utils.findCanvasElement(wrapper));
- }
- );
- }
- }
-});
-
-commands.register('footnote', function(canvas, params) {
- void(params);
- var cursor = canvas.getCursor(),
- position = cursor.getPosition(),
- asideNode, asideElement, node;
-
-
- if(cursor.isSelectingWithinElement()) {
- asideNode = position.element.wlxmlNode.wrapWith({tagName: 'aside', attrs:{'class': 'footnote'}, start: cursor.getSelectionStart().offset, end: cursor.getSelectionEnd().offset});
- } else {
- node = position.element.wlxmlNode;
- node.document.transaction(function() {
- asideNode = node.divideWithElementNode({tagName: 'aside', attrs:{'class': 'footnote'}}, {offset: position.offset});
- asideNode.append({text: ''});
- });
- }
-
- asideElement = utils.findCanvasElement(asideNode);
- asideElement.toggle(true);
- canvas.setCurrentElement(asideElement);
-});
-
-commands.register('take-away-node', function(canvas) {
- var position = canvas.getCursor().getPosition(),
- element = position.element,
- nodeElement = element ? element.parent() : canvas.getCurrentNodeElement();
-
- if(!nodeElement || !(nodeElement.parent())) {
- return;
- }
-
- var range = nodeElement.wlxmlNode.unwrapContent();
-
- if(element) {
- var elementIsFirstChild = nodeElement.childIndex(element);
- if(element.bound()) {
- canvas.setCurrentElement(element, {caretTo: position.offset});
- } else {
- if(elementIsFirstChild) {
- canvas.setCurrentElement(utils.findCanvasElement(range.element1), {caretTo: 'end'});
- } else {
- canvas.setCurrentElement(utils.findCanvasElement(range.element2), {caretTo: 'end'});
- }
- }
- } else {
- canvas.setCurrentElement(utils.findCanvasElement(range.element1), {caretTo: 'start'});
- }
-
-});
-
-
-return {
- run: function(name, params, canvas, user) {
- return commands.run(name, params, canvas, user);
- }
-};
-
-});
\ No newline at end of file
define([
'libs/jquery',
+'libs/underscore',
+'fnpjs/logging/logging',
'./canvas/canvas',
-'./commands',
-'libs/text!./template.html'], function($, canvas3, commands, template) {
+'libs/text!./template.html'], function($, _, logging, canvas3, template) {
'use strict';
+
+var logger = logging.getLogger('documentCanvas');
+
return function(sandbox) {
- var canvas = canvas3.fromXMLDocument(null, sandbox.publish);
+ var canvas = canvas3.fromXMLDocument(null);
var canvasWrapper = $(template);
var shownAlready = false;
var scrollbarPosition = 0,
+ actionHandlers = {},
cursorPosition;
+
+ canvas.on('selectionChanged', function(selection) {
+ sandbox.publish('selectionChanged', selection);
+ });
+
canvasWrapper.onShow = function() {
if(!shownAlready) {
shownAlready = true;
/* public api */
return {
- start: function() { sandbox.publish('ready'); },
+ start: function() {
+ sandbox.getPlugins().forEach(function(plugin) {
+ var handlers;
+ if(plugin.canvas) {
+ handlers = plugin.canvas.actionHandlers;
+ if(handlers && !_.isArray(handlers)) {
+ handlers = [handlers];
+ }
+ actionHandlers[plugin.name] = handlers;
+ }
+ });
+ sandbox.publish('ready');
+ },
getView: function() {
return canvasWrapper;
},
+ getCanvas: function() {
+ return canvas;
+ },
setDocument: function(wlxmlDocument) {
canvas.loadWlxmlDocument(wlxmlDocument);
canvasWrapper.find('#rng-module-documentCanvas-content').empty().append(canvas.view());
jumpToElement: function(node) {
canvas.setCurrentElement(node);
},
- command: function(command, params) {
- commands.run(command, params, canvas, sandbox.getConfig().user);
+ onAfterActionExecuted: function(action, ret) {
+ if(ret && ret instanceof canvas.wlxmlDocument.CaretFragment && ret.isValid()) {
+ logger.debug('The action returned a valid fragment');
+ canvas.setCurrentElement(ret.node, {caretTo: ret.offset});
+ return;
+ }
+ logger.debug('No valid fragment returned from the action');
+
+ (actionHandlers[action.getPluginName()] || []).forEach(function(handler) {
+ handler(canvas, action, ret);
+ });
}
};
--- /dev/null
+define(function(require) {
+
+'use strict';
+/* globals gettext */
+
+var $ = require('libs/jquery'),
+ Backbone = require('libs/backbone'),
+ _ = require('libs/underscore'),
+ viewTemplate = require('libs/text!modules/documentToolbar/templates/actionView.html'),
+ buttonTemplate = require('libs/text!modules/documentToolbar/templates/actionViewButton.html'),
+ selectionTemplate = require('libs/text!modules/documentToolbar/templates/actionViewSelection.html');
+
+
+viewTemplate = _.template(viewTemplate);
+buttonTemplate = _.template(buttonTemplate);
+selectionTemplate = _.template(selectionTemplate);
+
+var iconExists = function(iconName) {
+ /* globals window */
+ var el = $('<i>').addClass('icon-' + iconName);
+ $('body').append(el);
+ var style = window.getComputedStyle(el[0]);
+ var toret = /glyphicons/.test(style.backgroundImage) && !/14px 14px/.test(style.backgroundPosition);
+ el.remove();
+ return toret;
+};
+
+var ActionView = Backbone.View.extend({
+ events: {
+ 'mousedown .btn': 'onMouseDown',
+ 'click .btn': 'onExecute',
+ 'change select': 'onSelectionChange',
+ 'mouseenter': 'onMouseEnter',
+ 'mouseleave': 'onMouseLeave'
+ },
+ initialize: function() {
+ this.action = this.options.action;
+ this.action.on('paramsChanged', function() {
+ this.render();
+ }, this);
+ this.setElement(viewTemplate());
+ },
+ render: function() {
+ /* globals document */
+
+ var actionState = this.action.getState();
+
+ if(!actionState) {
+ this.$el.html(buttonTemplate({label: gettext('error :('), iconName:''}));
+ this._button().attr('disabled', true);
+ return;
+ }
+
+ var templateContext = {
+ label: actionState.label || '?',
+ iconName: (iconExists(actionState.icon)) ? actionState.icon : null,
+ iconStyle: actionState.iconStyle
+ },
+ hovered = document.querySelectorAll(':hover'),
+ hovers = false,
+ button = this._button();
+
+ if(hovered.length && _.last(hovered) === button[0]) {
+ hovers = true;
+ }
+
+ this.$el.empty();
+ _.pairs(this.action.definition.params).forEach(function(pair) {
+ var paramName = pair[0],
+ paramDesc = pair[1],
+ widget;
+ if(paramDesc.type === 'select') {
+ widget = $(selectionTemplate({
+ paramName: paramName,
+ options: paramDesc.options
+ }));
+ if(this.action.params[paramName]) {
+ widget.find('option[value=' + this.action.params[paramName].id + ']').attr('selected', true);
+ }
+ this.$el.append(widget);
+ }
+ }.bind(this));
+
+ this.$el.append(buttonTemplate(templateContext));
+ button = this._button();
+
+ if(!actionState.allowed) {
+ button.attr('disabled', true);
+ button.wrap('<div style="position: relative;">');
+ button.after('<div style="position: absolute; top:0; bottom:0; left:0; right: 0"></div>');
+ }
+
+ if(actionState.toggled !== undefined) {
+ button.toggleClass('active', actionState.toggled);
+ }
+
+ if(hovers) {
+ this.trigger('hover');
+ }
+ },
+ onMouseEnter: function() {
+ this.trigger('hover');
+ },
+ onMouseLeave: function() {
+ this.trigger('leave');
+ },
+ onMouseDown: function() {
+ this.trigger('mousedown');
+ },
+ onExecute: function() {
+ this.action.execute(function(ret) {
+ this.trigger('actionExecuted', this.action, ret);
+ }.bind(this));
+ },
+ onSelectionChange: function(e) {
+ var select = $(e.target),
+ paramName = select.attr('param');
+
+ this.action.definition.params[paramName].options.some(function(option) {
+ if(option.id.toString() === select.val()) {
+ this.action.updateWidgetParam(paramName, option);
+ return true; // break
+ }
+ }.bind(this));
+ },
+ _button: function() {
+ return this.$el.find('button');
+ }
+});
+
+var create = function(action) {
+ var view = new ActionView({action:action});
+ view.render();
+
+ return {
+ on: function() {
+ view.on.apply(view, Array.prototype.slice.call(arguments, 0));
+ },
+ dom: view.$el,
+ };
+};
+
+return {
+ create: create
+};
+
+});
\ No newline at end of file
-define(['libs/jquery', 'libs/underscore', 'utils/wlxml', 'libs/text!./template.html'], function($, _, wlxmlUtils, template) {
+define(['libs/jquery', 'libs/underscore', 'modules/documentToolbar/actionView', 'libs/text!./template.html'], function($, _, actionView, template) {
'use strict';
-/* globals Node */
+
return function(sandbox) {
- var documentTemplates = sandbox.getBootstrappedData(),
- currentNode;
+ var addedActions = [],
+ contextParams = {},
+ document, canvas;
var view = {
- node: $(_.template(template)({wlxmlUtils: wlxmlUtils, templates: documentTemplates})),
- setup: function() {
- var view = this;
+ node: $(_.template(template)()),
+ getOption: function(option) {
+ return this.node.find('.rng-module-documentToolbar-toolbarOption[data-option=' + option +']').val();
+ },
+ addAction: function(group, actionDescription) {
+ var action = sandbox.createAction(actionDescription.actionName, actionDescription.actionConfig),
+ view;
+ addedActions.push(action);
+ view = actionView.create(action);
- this.node.find('button').click(function(e) {
- e.stopPropagation();
-
- var btn = $(e.currentTarget),
- btnName = btn.attr('data-name'),
- meta = btn.attr('data-meta'),
- params = {},
- command = btnName;
-
- if(myHandlers[btnName]) {
- myHandlers[btnName](btn);
- } else {
- if(btn.attr('data-btn-type') === 'toggle') {
- command = 'toggle-' + command;
- btn.toggleClass('active');
- params.toggle = btn.hasClass('active');
- }
-
- if(btnName === 'new-node') {
- command = 'newNodeRequested';
- params.wlxmlTag = view.getOption('newTag-tag');
- params.wlxmlClass = view.getOption('newTag-class');
- if(meta) {
- var split = meta.split('/');
- params.wlxmlTag = split[0];
- params.wlxmlClass = split[1];
- }
- } else {
- params.meta = meta;
- }
-
- if(command === 'undo' || command === 'redo') {
- params.callback = function(disable) {
- btn.attr('disabled', !disable);
- };
- }
+ _.pairs(contextParams).forEach(function(pair) {
+ var name = pair[0],
+ value = pair[1];
+ action.updateContextParam(name, value);
+ });
- _.extend(params, {ctrlKey: e.ctrlKey});
+ group.append(view.dom);
+ view.on('actionExecuted', function(action, ret) {
+ sandbox.publish('actionExecuted', action, ret);
+ });
- sandbox.publish('command', command, params);
- }
+ view.on('hover', function() {
+ sandbox.publish('actionHovered', action);
+ });
+ view.on('leave', function() {
+ sandbox.publish('actionOff', action);
});
},
- getOption: function(option) {
- return this.node.find('.rng-module-documentToolbar-toolbarOption[data-option=' + option +']').val();
+ addActionsGroup: function() {
+ var div = $('<div>');
+ div.addClass('rng-module-documentToolbar-toolbarGroup');
+ this.node.append(div);
+ return div;
}
};
- var myHandlers = {
- templatesBtn: function() {
- if(currentNode && currentNode.nodeType === Node.ELEMENT_NODE) {
- var templateId = parseInt(view.node.find('[data-name=templates-select]').val(), 10);
- documentTemplates.forEach(function(template) {
- if(template.id === templateId) {
- var toAdd = currentNode.document.createDocumentNode(template.content);
- currentNode.after(toAdd);
- }
- });
- }
- }
+ var setContextParam = function(what, ctx) {
+ contextParams[what] = ctx;
+ addedActions.forEach(function(action) {
+ action.updateContextParam(what, contextParams[what]);
+ });
};
- view.setup();
+ sandbox.registerKeyHandler('keydown', function(e) {
+ if(e.keyCode === 17) {
+ addedActions.forEach(function(action) {
+ action.updateKeyParam('ctrl', true);
+ });
+ }
+ });
+ sandbox.registerKeyHandler('keyup', function(e) {
+ if(e.keyCode === 17) {
+ addedActions.forEach(function(action) {
+ action.updateKeyParam('ctrl', false);
+ });
+ }
+ });
return {
- start: function() { sandbox.publish('ready'); },
+ start: function() {
+ var config = sandbox.getConfig().toolbar || {};
+ config.forEach(function(actionsGroup) {
+ var group = view.addActionsGroup();
+ actionsGroup.forEach(function(actionDescription) {
+ if(typeof actionDescription === 'string') {
+ actionDescription = {actionName: actionDescription, actionConfig: {}};
+ }
+ view.addAction(group, actionDescription);
+ });
+ });
+ sandbox.publish('ready');
+ },
getView: function() { return view.node; },
- setNodeElement: function(node) {
- currentNode = node;
+ setDocumentFragment: function(fragment) {
+ if(!document) {
+ document = fragment.document;
+ document.on('operationEnd', function() {
+ setContextParam('document', document);
+ });
+ }
+ setContextParam('fragment', fragment);
+
+ },
+ setCanvas: function(_canvas) {
+ setContextParam('canvas', _canvas);
+ if(!canvas) {
+ canvas = _canvas;
+ canvas.on('changed', function() {
+ setContextParam('canvas', _canvas);
+ });
+ }
},
getOption: function(option) { return view.getOption(option); }
};
border-color: #ddd;
padding: 0 8px 0 0;
margin: 0 8px 0 0;
- float:left;
+ display: inline-block;
+ }
+
+ .toolbar-widget {
+ display: inline-block;
+ margin: 0 5px;
+ div {
+ display: inline-block;
+ }
}
}
-<div class="rng-module-documentToolbar">
- <div class="rng-module-documentToolbar-toolbarGroup">
- <button data-name="undo" data-btn-type="cmd" data-meta="bullets" class="btn btn-mini"><i class="icon-share-alt" style="-webkit-transform: scale(-1,1); transform: scale(-1, 1);"></i></button>
- <button data-name="redo" data-btn-type="cmd" class="btn btn-mini"><i class="icon-share-alt"></i></button>
- </div>
-
- <div class="rng-module-documentToolbar-toolbarGroup">
- <button data-name="new-node" data-meta="header/" data-btn-type="cmd" class="btn btn-mini">nagłówek</button>
- <button data-name="list" data-btn-type="cmd" data-meta="bullets" class="btn btn-mini">lista pkt</button>
- <button data-name="list" data-btn-type="cmd" data-meta="num" class="btn btn-mini">lista num</button>
- </div>
-
- <div class="rng-module-documentToolbar-toolbarGroup">
- <button data-name="new-node" data-meta="span/emp" data-btn-type="cmd" class="btn btn-mini">wyróżnienie</button>
- <button data-name="new-node" data-meta="span/cite" data-btn-type="cmd" class="btn btn-mini">cytat</i></button>
- <button data-name="new-node" data-meta="span/link" data-btn-type="cmd" class="btn btn-mini">link</i></button>
- </div>
-
- <% if(templates) { %>
- <div class="rng-module-documentToolbar-toolbarGroup">
- <select data-name="templates-select">
- <% templates.forEach(function(template) { %>
- <option value="<%= template.id %>"><%= template.name %></option>
- <% }); %>
- </select>
- <button class="btn btn-mini" data-name="templatesBtn">+</button>
- </div>
- <% } %>
-
- <div class="rng-module-documentToolbar-toolbarGroup">
- <button data-name="new-node" data-meta="aside/comment" data-btn-type="cmd" class="btn btn-mini"><i class="icon-comment"></i></button>
- </div>
-
- <div style="clear: both;"></div>
-</div>
\ No newline at end of file
+<div class="rng-module-documentToolbar"></div>
--- /dev/null
+<div class="toolbar-widget"></div>
\ No newline at end of file
--- /dev/null
+<button class="btn btn-mini">
+<% if(iconName) { %>
+ <i class="icon-<%= iconName %>" style="<%= iconStyle || '' %>"></i>
+<% } else { %>
+ <%= label %>
+<% } %>
+</button>
\ No newline at end of file
--- /dev/null
+<select param="<%= paramName %>">
+ <option value=""></option>
+ <% options.forEach(function(option) { %>
+ <option value="<%= option.id %>"><%= option.name %></option>
+ <% }); %>
+</select>
\ No newline at end of file
], function($, _, mainTemplate, itemTemplate, OpenSelectView) {
'use strict';
+/* globals gettext */
return function(sandbox) {
this.node.find('.rng-module-metadataEditor-addBtn').click(function() {
adding = true;
- currentNode.getMetadata().add('','');
+ currentNode.document.transaction(function() {
+ currentNode.getMetadata().add('','');
+ }, this, gettext('Add metadata row'));
});
this.metaTable.on('click', '.rng-visualEditor-metaRemoveBtn', function(e) {
- $(e.target).closest('tr').data('row').remove();
+ currentNode.document.transaction(function() {
+ $(e.target).closest('tr').data('row').remove();
+ }, this, gettext('Remove metadata row'));
});
this.metaTable.on('keydown', '[contenteditable]', function(e) {
row = editable.parents('tr').data('row'),
isKey = _.last(editable.attr('class').split('-')) === 'metaItemKey',
method = isKey ? 'setKey' : 'setValue';
- row[method](toSet);
+ row.metadata.node.document.transaction(function() {
+ row[method](toSet);
+ }, this, gettext('Metadata edit'));
}
}, 500));
},
clear: function() {
},
setMetadata: function(node) {
+ this.node.find('.rng-module-metadataEditor-addBtn').attr('disabled', !node);
if(!node) {
this.metaTable.html('');
return;
if(event.type === 'metadataRemoved' && event.meta.node.sameNode(currentNode)) {
view.removeMetadataRow(event.meta.row);
}
- if(event.type === 'nodeDetached' && event.meta.node.sameNode(currentNode)) {
+ if(event.type === 'nodeDetached' && event.meta.node.containsNode(currentNode)) {
view.setMetadata(null);
}
});
return function(sandbox) {
- var template = _.template(templateSrc);
+ var template = _.template(templateSrc),
+ listens = false;
var view = {
dom: $('<div>' + template({node:null, parents: null}) + '</div>'),
setNodeElement: function(nodeElement) {
this.dom.empty();
this.currentNodeElement = nodeElement;
- var parents = nodeElement.parents();
+ var parents;
+ if(nodeElement) {
+ parents = nodeElement.parents();
+ }
+
this.dom.html(template({node: nodeElement, parents: parents, utils: wlxmlUtils}));
this.dom.find('li > a[href="#"]').each(function(idx, a) {
start: function() { sandbox.publish('ready'); },
getView: function() { return view.dom; },
setNodeElement: function(nodeElement) {
+ if(!listens && nodeElement) {
+ nodeElement.document.on('change', function() {
+ if(view.currentNodeElement && !view.currentNodeElement.isInDocument()) {
+ view.setNodeElement(null);
+ }
+ });
+ listens = true;
+ }
view.setNodeElement(nodeElement);
},
highlightNode: function(id) { view.highlightNode(id); },
<li><a href="#"> <%= utils.getTagLabel(parents[i].getTagName()) %><% if(parents[i].getClass()) { %>.<%= utils.getClassLabel(parents[i].getClass()) %> <% } %></a><span class="divider">/</span></li>
<% } %>
<li class="active"><%= utils.getTagLabel(node.getTagName()) %><% if(node.getClass()) { %>.<%= utils.getClassLabel(node.getClass()) %> <% } %></span></li>
- <% } %>
+ <% } else { %> <% } %>
</ul>
</div>
\ No newline at end of file
listens = true;
document.on('change', function(event) {
if(event.type === 'nodeTextChange' && event.meta.node.parent().sameNode(view.currentNodeElement)) {
- view.setElement();
+ view.setElement(view.currentNodeElement);
}
}, this);
};
});
},
setElement: function(element) {
- element = element || this.currentNodeElement;
- var textElement = element.getText ? element : null,
- nodeElement = element.getText ? element.parent() : element, // TODO: better type detection
- nodeElementParent = nodeElement.parent(),
- parent;
-
- this.currentNodeElement = nodeElement;
- items = [];
+ var contents = [],
+ parent, nodeElementParent;
- if(nodeElementParent) {
- items.push(nodeElementParent);
- parent = {
- id: items.length - 1,
- repr: wlxmlUtils.getTagLabel(nodeElementParent.getTagName()) + (nodeElementParent.getClass() ? ' / ' + wlxmlUtils.getClassLabel(nodeElementParent.getClass()) : '')
- };
+ if(element) {
+ element = element || this.currentNodeElement;
+ var textElement = element.getText ? element : null,
+ nodeElement = element.getText ? element.parent() : element, // TODO: better type detection
+ items;
- }
-
- var nodeContents = nodeElement.contents(),
- contents = [];
- nodeContents.forEach(function(child) {
- if(child.getText) {
- var text = child.getText();
- if(!text) {
- text = '<pusty tekst>';
- }
- else {
- if(text.length > 13) {
- text = text.substr(0,13) + '...';
+ this.currentNodeElement = nodeElement;
+ items = [];
+ nodeElementParent = nodeElement.parent();
+
+ if(nodeElementParent) {
+ items.push(nodeElementParent);
+ parent = {
+ id: items.length - 1,
+ repr: wlxmlUtils.getTagLabel(nodeElementParent.getTagName()) + (nodeElementParent.getClass() ? ' / ' + wlxmlUtils.getClassLabel(nodeElementParent.getClass()) : '')
+ };
+
+ }
+
+ var nodeContents = nodeElement.contents();
+ nodeContents.forEach(function(child) {
+ if(child.getText) {
+ var text = child.getText();
+ if(!text) {
+ text = '<pusty tekst>';
+ }
+ else {
+ if(text.length > 13) {
+ text = text.substr(0,13) + '...';
+ }
+ text = '"' + text + '"';
}
- text = '"' + text + '"';
+ contents.push({
+ id: items.length,
+ repr: _.escape(text), bold: child.sameNode(textElement)
+ });
+ } else {
+ contents.push({
+ id: items.length,
+ repr: wlxmlUtils.getTagLabel(child.getTagName()) + (child.getClass() ? ' / ' + wlxmlUtils.getClassLabel(child.getClass()) : '')
+ });
}
- contents.push({
- id: items.length,
- repr: _.escape(text), bold: child.sameNode(textElement)
- });
- } else {
- contents.push({
- id: items.length,
- repr: wlxmlUtils.getTagLabel(child.getTagName()) + (child.getClass() ? ' / ' + wlxmlUtils.getClassLabel(child.getClass()) : '')
- });
- }
- items.push(child);
- });
+ items.push(child);
+ });
+ }
this.dom.empty();
this.dom.append($(template({parent: parent, contents: contents})));
sandbox.publish('ready');
},
setElement: function(element) {
- if(!listens) {
+ if(!listens && element) {
startListening(element.document);
}
- if(!(element.sameNode(view.currentNodeElement))) {
+ if(!element || !(element.sameNode(view.currentNodeElement))) {
view.setElement(element);
}
},
return function(sandbox) {
var view = $(_.template(templateSrc)({utils: wlxmlUtils})),
- currentNode;
+ listens = false,
+ currentNode,
+ msgs = {
+ Tag: gettext('Tag editing'),
+ Class: gettext('Class editing')
+ };
view.on('change', 'select', function(e) {
var target = $(e.target);
var attr = target.attr('class').split('-')[3] === 'tagSelect' ? 'Tag' : 'Class',
- value = target.val().replace(/-/g, '.');
- currentNode['set' + attr](value);
+ value = target.val().replace(/-/g, '.'),
+ oldValue = attr === 'Tag' ? currentNode.getTagName() : currentNode.getClass();
+ currentNode.document.transaction(function() {
+ currentNode['set' + attr](value);
+ }, this, msgs[attr] + ': ' + oldValue + ' -> ' + value);
});
+
+
return {
start: function() {
return view;
},
setNodeElement: function(wlxmlNodeElement) {
- var module = this;
- if(!currentNode) {
- wlxmlNodeElement.document.on('change', function(event) {
- if(event.type === 'nodeAttrChange' && event.meta.node.sameNode(currentNode)) {
- module.setNodeElement(currentNode);
- }
- });
- }
+ if(wlxmlNodeElement) {
+ var module = this;
+ if(!listens) {
+ wlxmlNodeElement.document.on('change', function(event) {
+ if(currentNode && !currentNode.isInDocument()) {
+ module.setNodeElement(null);
+ }
+ if(event.type === 'nodeAttrChange' && event.meta.node.sameNode(currentNode)) {
+ module.setNodeElement(currentNode);
+ }
+ });
+ listens = true;
+ }
- view.find('.rng-module-nodePane-tagSelect').val(wlxmlNodeElement.getTagName());
+ view.find('.rng-module-nodePane-tagSelect').attr('disabled', false).val(wlxmlNodeElement.getTagName());
- var escapedClassName = (wlxmlNodeElement.getClass() || '').replace(/\./g, '-');
- view.find('.rng-module-nodePane-classSelect').val(escapedClassName);
-
- var attrs = _.extend(wlxmlNodeElement.getMetaAttributes(), wlxmlNodeElement.getOtherAttributes());
- var widget = metaWidget.create({attrs:attrs});
- widget.on('valueChanged', function(key, value) {
- wlxmlNodeElement.setMetaAttribute(key, value);
- //wlxmlNodeElement.setMetaAttribute(key, value);
- });
- view.find('.metaFields').empty().append(widget.el);
+ var escapedClassName = (wlxmlNodeElement.getClass() || '').replace(/\./g, '-');
+ view.find('.rng-module-nodePane-classSelect').attr('disabled', false).val(escapedClassName);
+ var attrs = _.extend(wlxmlNodeElement.getMetaAttributes(), wlxmlNodeElement.getOtherAttributes());
+ var widget = metaWidget.create({attrs:attrs});
+ widget.on('valueChanged', function(key, value) {
+ wlxmlNodeElement.setMetaAttribute(key, value);
+ //wlxmlNodeElement.setMetaAttribute(key, value);
+ });
+ view.find('.metaFields').empty().append(widget.el);
+ } else {
+ view.find('.rng-module-nodePane-tagSelect').attr('disabled', true).val('');
+ view.find('.rng-module-nodePane-classSelect').attr('disabled', true).val('');
+ view.find('.metaFields').empty();
+ }
currentNode = wlxmlNodeElement;
}
};
<div style="clear:both"></div>
<div fnpjs-place="mainView">
</div>
+ <div fnpjs-place="bottomPanel"></div>
</div>
\ No newline at end of file
top: 15px;
left:0;
right:0;
- bottom:0;
+ bottom:20px;
z-index: 1;
> .rng-view-tabs {
}
}
+ [fnpjs-place="bottomPanel"] {
+ position: absolute;
+ bottom:0;
+ height: 20px;
+ width:100%
+ }
}
\ No newline at end of file
jumpToDocumentElement: function(element) {
sandbox.getModule('documentCanvas').jumpToElement(element);
},
- updateCurrentNodeElement: function(nodeElement) {
- sandbox.getModule('nodePane').setNodeElement(nodeElement);
- sandbox.getModule('nodeFamilyTree').setElement(nodeElement);
- sandbox.getModule('nodeBreadCrumbs').setNodeElement(nodeElement);
- sandbox.getModule('documentToolbar').setNodeElement(nodeElement);
- sandbox.getModule('metadataEditor').setNodeElement(nodeElement);
+ refreshCanvasSelection: function(selection) {
+ var fragment = selection.toDocumentFragment(),
+ elementParent;
+
+ sandbox.getModule('documentToolbar').setDocumentFragment(fragment);
+
+ if(fragment && fragment.node) {
+ elementParent = fragment.node.getNearestElementNode();
+ sandbox.getModule('nodePane').setNodeElement(elementParent);
+ sandbox.getModule('nodeFamilyTree').setElement(fragment.node);
+ sandbox.getModule('nodeBreadCrumbs').setNodeElement(elementParent);
+ sandbox.getModule('metadataEditor').setNodeElement(elementParent);
+ } else {
+ sandbox.getModule('nodePane').setNodeElement(null);
+ sandbox.getModule('nodeFamilyTree').setElement(null);
+ sandbox.getModule('nodeBreadCrumbs').setNodeElement(null);
+ sandbox.getModule('metadataEditor').setNodeElement(null);
+ }
},
- updateCurrentTextElement: function(textElement) {
- sandbox.getModule('nodeFamilyTree').setElement(textElement);
- }
};
sandbox.getModule('mainBar').setCommandEnabled('drop-draft', usingDraft);
sandbox.getModule('mainBar').setCommandEnabled('save', usingDraft);
- _.each(['sourceEditor', 'documentCanvas', 'documentToolbar', 'metadataEditor', 'nodeBreadCrumbs', 'mainBar', 'indicator', 'documentHistory', 'diffViewer'], function(moduleName) {
+ _.each(['sourceEditor', 'documentCanvas', 'documentToolbar', 'metadataEditor', 'nodeBreadCrumbs', 'mainBar', 'indicator', 'documentHistory', 'diffViewer', 'statusBar'], function(moduleName) {
sandbox.getModule(moduleName).start();
});
views.visualEditing.setView('leftColumn', sandbox.getModule('documentCanvas').getView());
},
- currentTextElementSet: function(textElement) {
- commands.updateCurrentTextElement(textElement);
- },
-
- currentNodeElementSet: function(nodeElement) {
- commands.updateCurrentNodeElement(nodeElement);
- },
-
- currentNodeElementChanged: function(nodeElement) {
- commands.updateCurrentNodeElement(nodeElement);
- },
-
nodeHovered: function(canvasNode) {
commands.highlightDocumentNode(canvasNode);
},
nodeBlured: function(canvasNode) {
commands.dimDocumentNode(canvasNode);
+ },
+
+ selectionChanged: function(selection) {
+ commands.refreshCanvasSelection(selection);
}
};
eventHandlers.documentToolbar = {
ready: function() {
views.visualEditing.setView('toolbar', sandbox.getModule('documentToolbar').getView());
+ sandbox.getModule('documentToolbar').setCanvas(sandbox.getModule('documentCanvas').getCanvas());
},
- command: function(cmd, params) {
- sandbox.getModule('documentCanvas').command(cmd, params);
+ actionExecuted: function(action, ret) {
+ sandbox.getModule('documentCanvas').onAfterActionExecuted(action, ret);
}
};
}
};
+ eventHandlers.statusBar = {
+ ready: function() {
+ views.mainLayout.setView('bottomPanel', sandbox.getModule('statusBar').getView());
+ }
+ };
+
+ eventHandlers.__all__ = {
+ actionHovered: function(action) {
+ sandbox.getModule('statusBar').showAction(action);
+ },
+ actionOff: function() {
+ sandbox.getModule('statusBar').clearAction();
+ }
+ };
+
window.addEventListener('beforeunload', function(event) {
var txt = gettext('Do you really want to exit?');
if(documentIsDirty) {
return {
start: function() {
+ sandbox.registerActionsAppObject({
+ getUser: function() {
+ return sandbox.getConfig().user;
+ }
+ });
sandbox.getModule('data').start();
},
handleEvent: function(moduleName, eventName, args) {
if(eventHandlers[moduleName] && eventHandlers[moduleName][eventName]) {
logger.debug('Handling event ' + eventRepr);
eventHandlers[moduleName][eventName].apply(eventHandlers, args);
- } else {
- logger.warning('No event handler for ' + eventRepr);
+ return;
+ }
+
+ if(eventHandlers.__all__[eventName]) {
+ logger.debug('Handling event ' + eventRepr);
+ eventHandlers.__all__[eventName].apply(eventHandlers.__all__, args);
+ return;
}
+ logger.warning('No event handler for ' + eventRepr);
}
};
};
--- /dev/null
+<div id="rng-module-statusBar"></div>
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+/* globals gettext */
+
+var $ = require('libs/jquery'),
+ template = require('libs/text!modules/statusBar/statusBar.html'),
+ logging = require('fnpjs/logging/logging');
+
+var logger = logging.getLogger('statusBar');
+
+return function(sandbox){
+
+ var view = $(template);
+
+ return {
+ start: function() {
+ return sandbox.publish('ready');
+ },
+ getView: function() {
+ return view;
+ },
+ showAction: function(action) {
+ var state = action.getState(),
+ description;
+
+ if(!state) {
+ description = gettext('error :(');
+ logger.error('Got undefined action state: ' + action.name);
+ } else {
+ description = state.description;
+ if(!description) {
+ description = state.allowed ? gettext('Undescribed action') : gettext('Action not allowed');
+ logger.info('Undescribed action: ' + action.name);
+ }
+ }
+
+ view.text(description);
+ if(!state.allowed) {
+ view.prepend('<span class="badge badge-warning" style="margin-right: 5px">!</span>');
+ }
+ },
+ clearAction: function() {
+ view.text('');
+ }
+ };
+
+};
+
+});
\ No newline at end of file
--- /dev/null
+#rng-module-statusBar {
+ border-width: 1px 0 0 0;
+ border-style: solid;
+ border-color: #ddd;
+ font-size: 0.8em;
+ height: 100%;
+ padding: 5px;
+}
\ No newline at end of file
define(function(require) {
'use strict';
+/* globals gettext */
var _ = require('libs/underscore'),
- plugin = {documentExtension: {textNode: {}}};
+ templates = require('plugins/core/templates'),
+ footnote = require('plugins/core/footnote'),
+ switchTo = require('plugins/core/switch'),
+ lists = require('plugins/core/lists'),
+ plugin = {name: 'core', actions: [], canvas: {}, documentExtension: {textNode: {}}},
+ Dialog = require('views/dialog/dialog');
plugin.documentExtension.textNode.transformations = {
}
};
+var undoRedoAction = function(dir) {
+ return {
+ name: dir,
+ params: {
+ document: {type: 'context', name: 'document'},
+ },
+ stateDefaults: {
+ label: dir === 'undo' ? '<-' : '->',
+ icon: 'share-alt',
+ iconStyle: dir === 'undo' ? '-webkit-transform: scale(-1,1); transform: scale(-1, 1)' : '',
+ execute: function(callback, params) {
+ params.document[dir]();
+ callback();
+ },
+ },
+ getState: function(params) {
+ var allowed = params.document && !!(params.document[dir+'Stack'].length),
+ desc = dir === 'undo' ? gettext('Undo') : gettext('Redo'),
+ descEmpty = dir === 'undo' ? gettext('There is nothing to undo') : gettext('There is nothing to redo');
+ if(allowed) {
+ var metadata = _.last(params.document[dir+'Stack']).metadata;
+ if(metadata) {
+ desc += ': ' + (metadata.description || gettext('unknown operation'));
+ }
+ }
+ return {
+ allowed: allowed,
+ description: allowed ? desc : descEmpty
+ };
+ }
+ };
+};
+
+var pad = function(number) {
+ if(number < 10) {
+ number = '0' + number;
+ }
+ return number;
+};
+
+var commentAction = {
+ name: 'comment',
+ params: {
+ fragment: {type: 'context', name: 'fragment'}
+ },
+ stateDefaults: {
+ icon: 'comment',
+ execute: function(callback, params, editor) {
+ /* globals Node */
+ var node = params.fragment.node,
+ action = this;
+ if(node.nodeType === Node.TEXT_NODE) {
+ node = node.parent();
+ }
+ node.document.transaction(function() {
+ var comment = node.after({tagName: 'aside', attrs: {'class': 'comment'}});
+ comment.append({text:''});
+ var user = editor.getUser(), creator;
+ if(user) {
+ creator = user.name;
+ if(user.email) {
+ creator += ' (' + user.email + ')';
+ }
+ } else {
+ creator = 'anonymous';
+ }
+
+ var currentDate = new Date(),
+ dt = pad(currentDate.getDate()) + '-' +
+ pad((currentDate.getMonth() + 1)) + '-' +
+ pad(currentDate.getFullYear()) + ' ' +
+ pad(currentDate.getHours()) + ':' +
+ pad(currentDate.getMinutes()) + ':' +
+ pad(currentDate.getSeconds());
+
+ var metadata = comment.getMetadata();
+ metadata.add({key: 'creator', value: creator});
+ metadata.add({key: 'date', value: dt});
+ }, {
+ metadata: {
+ description: action.getState().description
+ },
+ success: callback
+ });
+ },
+ },
+ getState: function(params) {
+ var state = {
+ allowed: params.fragment && params.fragment.isValid() &&
+ params.fragment instanceof params.fragment.NodeFragment && !params.fragment.node.isRoot()
+ };
+ if(state.allowed) {
+ state.description = gettext('Insert comment after current node');
+ }
+ return state;
+ }
+};
+
+
+var createWrapTextAction = function(createParams) {
+ return {
+ name: createParams.name,
+ params: {
+ fragment: {type: 'context', name: 'fragment'},
+ },
+ getState: function(params) {
+ var state = {
+ label: this.config.label
+ },
+ parent;
+
+ if(
+ !params.fragment || !params.fragment.isValid() ||
+ !(params.fragment instanceof params.fragment.TextRangeFragment) ||
+ !params.fragment.hasSiblingBoundries()) {
+ return _.extend(state, {allowed: false});
+ }
+
+ parent = params.fragment.startNode.parent();
+ if(parent && parent.is(createParams.klass) || parent.isInside(createParams.klass)) {
+ return _.extend(state, {allowed: false});
+ }
+
+ return _.extend(state, {
+ allowed: true,
+ description: createParams.description,
+ execute: function(callback, params) {
+ params.fragment.document.transaction(function() {
+ var parent = params.fragment.startNode.parent(),
+ doc = params.fragment.document,
+ wrapper, lastTextNode;
+
+ wrapper = parent.wrapText({
+ _with: {tagName: 'span', attrs: {'class': createParams.klass}},
+ offsetStart: params.fragment.startOffset,
+ offsetEnd: params.fragment.endOffset,
+ textNodeIdx: [params.fragment.startNode.getIndex(), params.fragment.endNode.getIndex()]
+ });
+
+ lastTextNode = wrapper.getLastTextNode();
+ if(lastTextNode) {
+ return doc.createFragment(doc.CaretFragment, {node: lastTextNode, offset: lastTextNode.getText().length});
+ }
+ }, {
+ metadata: {
+ description: createParams.description
+ },
+ success: callback
+ });
+ }
+ });
+ }
+ };
+};
+
+
+var createLinkFromSelection = function(callback, params) {
+ var doc = params.fragment.document,
+ dialog = Dialog.create({
+ title: gettext('Create link'),
+ executeButtonText: gettext('Apply'),
+ cancelButtonText: gettext('Cancel'),
+ fields: [
+ {label: gettext('Link'), name: 'href', type: 'input'}
+ ]
+ }),
+ action = this;
+
+ dialog.on('execute', function(event) {
+ doc.transaction(function() {
+ var span = action.params.fragment.startNode.parent().wrapText({
+ _with: {tagName: 'span', attrs: {'class': 'link'}},
+ offsetStart: params.fragment.startOffset,
+ offsetEnd: params.fragment.endOffset,
+ textNodeIdx: [params.fragment.startNode.getIndex(), params.fragment.endNode.getIndex()]
+ }),
+ doc = params.fragment.document;
+ span.setAttr('href', event.formData.href);
+ event.success();
+ return doc.createFragment(doc.CaretFragment, {node: span.contents()[0], offset:0});
+ }, {
+ metadata: {
+ description: action.getState().description
+ },
+ success: callback
+ });
+ });
+ dialog.show();
+};
+
+var editLink = function(callback, params) {
+ var doc = params.fragment.document,
+ link = params.fragment.node.getParent('link'),
+ dialog = Dialog.create({
+ title: gettext('Edit link'),
+ executeButtonText: gettext('Apply'),
+ cancelButtonText: gettext('Cancel'),
+ fields: [
+ {label: gettext('Link'), name: 'href', type: 'input', initialValue: link.getAttr('href')}
+ ]
+ }),
+ action = this;
+
+ dialog.on('execute', function(event) {
+ doc.transaction(function() {
+ link.setAttr('href', event.formData.href);
+ event.success();
+ return params.fragment;
+ }, {
+ metadata: {
+ description: action.getState().description
+ },
+ success: callback
+ });
+ });
+ dialog.show();
+};
+
+var linkAction = {
+ name: 'link',
+ params: {
+ fragment: {type: 'context', name: 'fragment'}
+ },
+ stateDefaults: {
+ label: gettext('link')
+ },
+ getState: function(params) {
+ if(!params.fragment || !params.fragment.isValid()) {
+ return {allowed: false};
+ }
+
+ if(params.fragment instanceof params.fragment.TextRangeFragment) {
+ if(!params.fragment.hasSiblingBoundries() || params.fragment.startNode.parent().is('link')) {
+ return {allowed: false};
+ }
+ return {
+ allowed: true,
+ description: gettext('Create link from selection'),
+ execute: createLinkFromSelection
+ };
+ }
+
+ if(params.fragment instanceof params.fragment.CaretFragment) {
+ if(params.fragment.node.isInside('link')) {
+ return {allowed: true, toggled: true, execute: editLink};
+ }
+ }
+ return {allowed: false};
+ }
+};
+
+
+plugin.actions = [
+ undoRedoAction('undo'),
+ undoRedoAction('redo'),
+ commentAction,
+ createWrapTextAction({name: 'emphasis', klass: 'emp', description: gettext('Mark as emphasized')}),
+ createWrapTextAction({name: 'cite', klass: 'cite', description: gettext('Mark as citation')}),
+ linkAction
+].concat(plugin.actions, templates.actions, footnote.actions, switchTo.actions, lists.actions);
+
+
+
+plugin.config = function(config) {
+ // templates.actions[0].config(config.templates);
+ templates.actions[0].params.template.options = config.templates;
+};
+
return plugin;
});
\ No newline at end of file
--- /dev/null
+define(function() {
+
+'use strict';
+/* globals gettext */
+
+var footnoteExecute = {
+ selecting: function(params) {
+ var parent = params.fragment.startNode.parent();
+ return parent.wrapText({
+ _with: {tagName: 'aside', attrs: {'class': 'footnote'}},
+ offsetStart: params.fragment.startOffset,
+ offsetEnd: params.fragment.endOffset,
+ textNodeIdx: [params.fragment.startNode.getIndex(), params.fragment.endNode.getIndex()]
+ });
+ },
+ afterCursor: function(params) {
+ var node = params.fragment.node,
+ asideNode;
+ node.document.transaction(function() {
+ asideNode = node.divideWithElementNode({tagName: 'aside', attrs:{'class': 'footnote'}}, {offset: params.fragment.offset});
+ asideNode.append({text: ''});
+ });
+ return asideNode;
+ },
+ afterNode: function(params) {
+ var node = params.fragment.node,
+ asideNode;
+ node.document.transaction(function() {
+ asideNode = node.after({tagName: 'aside', attrs:{'class': 'footnote'}}, {offset: params.fragment.offset});
+ asideNode.append({text: ''});
+ });
+ return asideNode;
+ }
+};
+
+var footnoteAction = {
+ name: 'footnote',
+ params: {
+ fragment: {type: 'context', name: 'fragment'}
+ },
+ stateDefaults: {
+ icon: 'asterisk'
+ },
+ getState: function(params) {
+ if(!params.fragment || !params.fragment.isValid()) {
+ return {allowed: false};
+ }
+ if(params.fragment instanceof params.fragment.TextRangeFragment && params.fragment.hasSiblingBoundries()) {
+ return {
+ allowed: true,
+ description: gettext('Create footnote from selection'),
+ execute: footnoteExecute.selecting
+ };
+ }
+ if(params.fragment instanceof params.fragment.CaretFragment) {
+ return {
+ allowed: true,
+ description: gettext('Insert footnote after cursor'),
+ execute: footnoteExecute.afterCursor
+ };
+ }
+ if(params.fragment instanceof params.fragment.NodeFragment) {
+ if(params.fragment.node.isRoot()) {
+ return {
+ allowed: false,
+ description: gettext('Cannot insert footnote after root node')
+ };
+ }
+ return {
+ allowed: true,
+ description: gettext('Insert footnote after node'),
+ execute: footnoteExecute.afterNode
+ };
+ }
+ return false;
+ }
+};
+
+
+return {
+ actions: [footnoteAction],
+};
+
+});
\ No newline at end of file
--- /dev/null
+define(function() {
+
+'use strict';
+/* globals gettext, interpolate */
+
+
+var getBoundriesForAList = function(fragment) {
+ var node;
+
+ if(fragment instanceof fragment.RangeFragment && fragment.hasSiblingBoundries()) {
+ return fragment.boundriesSiblingParents();
+ }
+ if(fragment instanceof fragment.NodeFragment) {
+ node = fragment.node.getNearestElementNode();
+ return {
+ node1: node,
+ node2: node
+ };
+ }
+};
+
+var countItems = function(boundries) {
+ var ptr = boundries.node1,
+ c = 1;
+ while(ptr && !ptr.sameNode(boundries.node2)) {
+ c++;
+ ptr = ptr.next();
+ }
+ return c;
+};
+
+var toggleListAction = function(type) {
+
+ var execute = {
+ add: function(callback, params) {
+ var boundries = getBoundriesForAList(params.fragment),
+ listParams = {klass: type === 'Bullet' ? 'list' : 'list.enum'},
+ action = this;
+
+ if(boundries && boundries.node1) {
+ listParams.node1 = boundries.node1;
+ listParams.node2 = boundries.node2;
+ boundries.node1.document.transaction(function() {
+ var list = boundries.node1.document.createList(listParams),
+ item1 = list.object.getItem(0),
+ text = item1 ? item1.contents()[0] : undefined, //
+ doc = boundries.node1.document;
+
+ return doc.createFragment(doc.CaretFragment, {node: text, offset:0});
+ }, {
+ metadata: {
+ description: action.getState().description
+ },
+ success: callback
+ });
+ } else {
+ throw new Error('Invalid boundries');
+ }
+ },
+ remove: function(callback, params) {
+ /* globals Node */
+ var current = params.fragment.node,
+ action = this;
+
+ var toSearch = current.nodeType === Node.ELEMENT_NODE ? [current] : [];
+ toSearch = toSearch.concat(current.parents());
+ toSearch.some(function(node) {
+ if(node.is('list')) {
+ node.document.transaction(function() {
+ node.object.extractListItems();
+ }, {
+ metadata: {
+ description: action.getState().description
+ },
+ success: callback
+ });
+
+ return true; // break
+ }
+ }.bind(this));
+ },
+ changeType: function(callback, params) {
+ var node = params.fragment.node,
+ action = this;
+ node.document.transaction(function() {
+ node.getParent('list').setClass(type === 'Bullet' ? 'list' : 'list.enum');
+ }, {
+ metadata: {
+ description: action.getState().description
+ },
+ success: callback
+ });
+ }
+ };
+
+ var isToggled = function(params) {
+ if(params.fragment && params.fragment.node && params.fragment.node.isInside('list')) {
+ var list = params.fragment.node.getParent('list');
+ return list.getClass() === (type === 'Bullet' ? 'list' : 'list.enum');
+ }
+ return false;
+ };
+
+
+ return {
+ name: 'toggle' + type + 'List',
+ context: ['fragment'],
+ params: {
+ fragment: {type: 'context', name: 'fragment'}
+ },
+ stateDefaults: {
+ label: type === 'Bullet' ? gettext('bull. list') : gettext('num. list')
+ },
+ getState: function(params) {
+ if(!params.fragment || !params.fragment.isValid()) {
+ return false;
+ }
+
+ if(params.fragment instanceof params.fragment.CaretFragment && params.fragment.node.isInside('list')) {
+ var list = params.fragment.node.getParent('list');
+ if((list.getClass() === 'list' && type === 'Enum') || (list.getClass() === 'list.enum' && type === 'Bullet')) {
+ return {
+ allowed: true,
+ description: interpolate(gettext('Change list type to %s'), [type]),
+ execute: execute.changeType
+ };
+ }
+ return {
+ allowed: true,
+ toggled: isToggled(params),
+ description: gettext('Remove list'),
+ execute: execute.remove
+ };
+
+ }
+ var boundries = getBoundriesForAList(params.fragment);
+ if(boundries) {
+ return {
+ allowed: true,
+ description: interpolate(gettext('Make %s fragment(s) into list'), [countItems(getBoundriesForAList(params.fragment))]),
+ execute: execute.add
+ };
+ }
+ }
+ };
+};
+
+
+return {
+ actions: [toggleListAction('Bullet'), toggleListAction('Enum')]
+};
+
+});
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+/* globals gettext */
+
+var _ = require('libs/underscore');
+
+
+var createSwitchAction = function(createParams) {
+ return {
+ name: createParams.name,
+ params: {
+ fragment: {type: 'context', name: 'fragment'},
+ },
+ getState: function(params) {
+ var state = {
+ label: this.config.label
+ },
+ f = params.fragment,
+ description;
+
+
+ if(
+ !(f && f.isValid()) ||
+ !((f instanceof f.CaretFragment) || (f instanceof f.TextRangeFragment && f.getCommonParent()))
+ ) {
+ return _.extend(state, {
+ allowed: false,
+ description: 'wrong or no selection'
+ });
+ }
+
+ var node = f instanceof f.CaretFragment ? f.node.parent() : f.getCommonParent(),
+ alreadyInTarget = node.isInside(createParams.to),
+ toSwitch = node,
+ textNodePath = (f.node || f.startNode).getPath();
+
+ if(!toSwitch.is(createParams.from)) {
+ toSwitch = toSwitch.getParent(createParams.from);
+ }
+
+ description = 'Switch to ' + createParams.to.name;
+ return _.extend(state, {
+ allowed: !!toSwitch,
+ toggled: alreadyInTarget,
+ description: description,
+ execute: alreadyInTarget ? function() {} : function(callback) {
+ f.document.transaction(function() {
+ if(createParams.to.tagName) {
+ toSwitch = toSwitch.setTag(createParams.to.tagName);
+ }
+ if(!_.isUndefined(createParams.to.klass)) {
+ toSwitch.setClass(createParams.to.klass);
+ }
+ return f.document.createFragment(f.CaretFragment, {node: f.document.getNodeByPath(textNodePath), offset: f.offset});
+ }, {
+ metadata: {
+ description: description
+ },
+ success: callback
+ });
+ }
+ });
+ }
+ };
+};
+
+var headerAction = createSwitchAction({name: 'switchToHeader', from: {tagName: 'div', klass: 'p'}, to: {tagName: 'header', klass: '', name: gettext('header')}}),
+ paragraphAction = createSwitchAction({name: 'switchToParagraph', from: {tagName: 'header'}, to: {tagName: 'div', klass: 'p', name: gettext('paragraf')}});
+
+return {
+ actions: [headerAction, paragraphAction],
+ canvasActionHandler: {
+ handles: [headerAction, paragraphAction],
+ // handle: function(canvas, action, ret) {
+ // var params = {},
+ // f;
+ // if(ret && ret.node2) {
+ // f = ret.oldFragment;
+ // if(f && f instanceof f.CaretFragment) {
+ // params.caretTo = f.offset;
+ // }
+ // canvas.setCurrentElement(ret.node2, params);
+ // }
+ // }
+ }
+};
+
+});
\ No newline at end of file
--- /dev/null
+define(function() {
+
+'use strict';
+/* globals gettext, interpolate */
+
+
+var insertTemplateAction = {
+ name: 'template',
+ params: {
+ fragment: {type: 'context', name: 'fragment'},
+ template: {type: 'select', options: []},
+ ctrl: {type: 'key', key: 'ctrl'}
+ },
+ stateDefaults: {
+ label: '+',
+ icon: 'core.plus'
+ },
+ getState: function(params) {
+ var description;
+
+ if(!(params.template && params.template.id)) {
+ return {
+ allowed: false,
+ description: gettext('No template selected')
+ };
+ } else if(
+ !params.fragment || !params.fragment.isValid() ||
+ !(params.fragment instanceof params.fragment.NodeFragment) ||
+ params.fragment.node.getNearestElementNode().isRoot()
+ ) {
+ return {
+ allowed: false,
+ description: gettext('Wrong node selected')
+ };
+ }
+
+ description = interpolate(gettext('Insert template %s after %s'), [params.template.name, params.fragment.node.getNearestElementNode().getTagName()]);
+ return {
+ allowed: true,
+ description: description,
+ execute: function(callback, params) {
+ var node = params.fragment.node.getNearestElementNode();
+ node.document.transaction(function() {
+ var toAdd = node.document.createDocumentNode(params.template.content);
+ node.after(toAdd);
+ }, {
+ metadata: {
+ description: description
+ },
+ success: callback
+ });
+ }
+ };
+ }
+};
+
+
+return {
+ actions: [insertTemplateAction]
+};
+
+});
\ No newline at end of file
@import '../modules/nodeFamilyTree/nodeFamilyTree.less';
@import '../modules/metadataEditor/metadataEditor.less';
@import '../modules/diffViewer/diffViewer.less';
+@import '../modules/statusBar/statusBar.less';
--- /dev/null
+define(function(require) {
+
+'use strict';
+
+var _ = require('libs/underscore'),
+ Backbone = require('libs/backbone'),
+ logging = require('fnpjs/logging/logging');
+
+var logger = logging.getLogger('fnpjs.actions');
+
+
+var Action = function(fqName, definition, config, appObject) {
+ this.fqName = fqName;
+ this.definition = definition;
+ this.config = config;
+ this._cache = null;
+ this.appObject = appObject;
+
+ this.params = {};
+};
+
+_.extend(Action.prototype, Backbone.Events, {
+ getPluginName: function() {
+ return this.fqName.split('.')[0];
+ },
+ updateParam: function(filter, value) {
+ var changed = false;
+ _.pairs(this.definition.params).forEach(function(pair) {
+ var paramName = pair[0],
+ paramDesc = pair[1];
+ if(filter(paramDesc, paramName)) {
+ this.params[paramName] = value;
+ changed = true;
+ }
+ }.bind(this));
+ if(changed) {
+ this._cache = null;
+ this.trigger('paramsChanged');
+ }
+ },
+ updateContextParam: function(contextName, value) {
+ this.updateParam(function(paramDesc) {
+ return paramDesc.type === 'context' && paramDesc.name === contextName;
+ }, value);
+ },
+ updateKeyParam: function(keyName, toggled) {
+ this.updateParam(function(paramDesc) {
+ return paramDesc.type === 'key' && paramDesc.key === keyName;
+ }, toggled);
+ },
+ updateWidgetParam: function(name, value) {
+ this.updateParam(function(paramDesc, paramName) {
+ return !_.contains(['context', 'key'], paramDesc.type) && paramName === name;
+ }, value);
+ },
+ getState: function() {
+ var gotState;
+ if(!this._cache) {
+ try {
+ gotState = this.definition.getState.call(this, this.params);
+ } catch(e) {
+ logger.exception(e);
+ return;
+ }
+ if(typeof gotState === 'boolean') {
+ gotState = {allowed: gotState};
+ }
+ this._cache = _.extend({}, this.definition.stateDefaults || {}, gotState);
+ }
+ if(this._cache === false) {
+ this._cache = {allowed: false};
+ }
+ return this._cache;
+ },
+ execute: function(callback) {
+ var state = this.getState();
+ callback = callback || function() {};
+ if(state.allowed) {
+ return state.execute.call(this, callback, this.params, this.appObject);
+ }
+ throw new Error('Execution not allowed');
+ }
+});
+
+
+return {
+ Action: Action
+};
+
+});
-define(['libs/jquery', 'libs/underscore', 'fnpjs/logging/logging'], function($, _, logging) {
+define(['libs/jquery', 'libs/underscore', 'fnpjs/logging/logging', 'fnpjs/actions'], function($, _, logging, actions) {
'use strict';
moduleInstances = {},
eventListeners = [],
plugins = [],
- config;
+ actionDefinitions = {},
+ config,
+ actionsAppObject;
_.each(_.keys(modules || {}), function(moduleName) {
if(_.contains(app.permissions[moduleName] || [], 'handleEvents')) {
this.getConfig = function() {
return config;
};
+
+ this.createAction = function(fqName, config) {
+ var definition = actionDefinitions[fqName];
+ if(!definition) {
+ throw new Error('Invalid action: ' + fqName);
+ }
+ return new actions.Action(fqName, definition, config, actionsAppObject);
+ };
+
+ this.registerKeyHandler = function(eventName, handler) {
+ $('body').on(eventName, function(e) {
+ handler(e);
+ });
+ };
+
+ this.registerActionsAppObject = function(_actionsAppObject) {
+ actionsAppObject = _actionsAppObject;
+ };
};
this.registerPlugin = function(plugin) {
plugins.push(plugin);
+ (plugin.actions || []).forEach(function(definition) {
+ var actionFqName = plugin.name + '.' + definition.name;
+ actionDefinitions[actionFqName] = definition;
+ });
};
this.start = function(_config) {
logging.setConfig(config.logging);
}
+ _.pairs(config.plugins || {}).forEach(function(pair) {
+ var pluginName = pair[0],
+ pluginConfig = pair[1];
+
+ plugins.some(function(plugin) {
+ if(plugin.name === pluginName) {
+ if(_.isFunction(plugin.config)) {
+ plugin.config(pluginConfig);
+ }
+ return true; //break
+ }
+ });
+ });
+
app.initModules.forEach(function(moduleName) {
getModuleInstance(moduleName).start();
});
--- /dev/null
+define(function(require) {
+
+'use strict';
+
+var $ = require('libs/jquery'),
+ _ = require('libs/underscore');
+
+
+var Fragment = function(document) {
+ this.document = document;
+};
+$.extend(Fragment.prototype, {
+ isValid: function() {
+ return false;
+ }
+});
+
+
+var NodeFragment = function(document, params) {
+ Fragment.call(this, document);
+ this.node = params.node;
+};
+NodeFragment.prototype = Object.create(Fragment.prototype);
+$.extend(NodeFragment.prototype, {
+ isValid: function() {
+ return this.document.containsNode(this.node);
+ }
+});
+
+
+var CaretFragment = function(document, params) {
+ NodeFragment.call(this, document, params);
+ this.offset = params.offset;
+
+};
+CaretFragment.prototype = Object.create(NodeFragment.prototype);
+$.extend(CaretFragment.prototype, {
+ isValid: function() {
+ /* globals Node */
+ return NodeFragment.prototype.isValid.call(this) &&
+ this.node.nodeType === Node.TEXT_NODE &&
+ _.isNumber(this.offset);
+ }
+});
+
+
+
+var RangeFragment = function(document, params) {
+ Fragment.call(this, document);
+
+ if(params.node1.sameNode(params.node2)) {
+ this.startNode = this.endNode = params.node1;
+ } else {
+ /*jshint bitwise: false*/
+ /* globals Node */
+ var node1First = params.node1.nativeNode.compareDocumentPosition(params.node2.nativeNode) & Node.DOCUMENT_POSITION_FOLLOWING;
+ (node1First ? ['start', 'end'] : ['end','start']).forEach(function(prefix, idx) {
+ this[prefix + 'Node'] = params['node'+(idx+1)];
+ }.bind(this));
+ }
+};
+RangeFragment.prototype = Object.create(Fragment.prototype);
+$.extend(RangeFragment.prototype, {
+ isValid: function() {
+ return this.document.containsNode(this.startNode) && this.document.containsNode(this.endNode);
+ },
+ hasSiblingBoundries: function() {
+ return this.isValid() && this.startNode.isSiblingOf(this.endNode);
+ },
+ boundriesSiblingParents: function() {
+ return this.startNode.document.getSiblingParents({
+ node1: this.startNode,
+ node2: this.endNode
+ });
+ },
+ getCommonParent: function() {
+ var siblingParents = this.boundriesSiblingParents();
+ if(siblingParents) {
+ return siblingParents.node1.parent();
+ }
+ },
+});
+
+var TextRangeFragment = function(document, params) {
+ var orderChanged;
+
+ RangeFragment.call(this, document, params);
+
+ if(this.startNode.sameNode(this.endNode)) {
+ this.startOffset = Math.min(params.offset1, params.offset2);
+ this.endOffset = Math.max(params.offset1, params.offset2);
+ } else {
+ orderChanged = !params.node1.sameNode(this.startNode);
+ this.startOffset = orderChanged ? params.offset2 : params.offset1;
+ this.endOffset = orderChanged ? params.offset1 : params.offset2;
+ }
+};
+TextRangeFragment.prototype = Object.create(RangeFragment.prototype);
+$.extend(TextRangeFragment.prototype, {
+ isValid: function() {
+ return RangeFragment.prototype.isValid.call(this) &&
+ _.isNumber(this.startOffset) &&
+ _.isNumber(this.endOffset);
+ }
+});
+
+var FragmentTypes = {
+ Fragment: Fragment,
+ NodeFragment: NodeFragment,
+ CaretFragment: CaretFragment,
+ RangeFragment: RangeFragment,
+ TextRangeFragment: TextRangeFragment
+};
+_.values(FragmentTypes).forEach(function(Type) {
+ $.extend(Type.prototype, FragmentTypes);
+});
+
+return FragmentTypes;
+
+});
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+/* global describe, it */
+/* jshint expr:true */
+
+
+var chai = require('libs/chai'),
+ smartxml = require('./smartxml.js');
+
+
+var expect = chai.expect;
+
+var getDocumentFromXML = function(xml) {
+ return smartxml.documentFromXML(xml);
+};
+
+describe('Fragments API', function() {
+ describe('node fragment', function() {
+ it('describes a single node', function() {
+ var doc = getDocumentFromXML('<section></section');
+
+ var fragment = doc.createFragment(doc.NodeFragment, {node:doc.root});
+ expect(fragment instanceof fragment.NodeFragment).to.be.true;
+ expect(fragment.node.sameNode(doc.root)).to.be.true;
+ });
+ });
+
+ describe('caret fragment', function() {
+ it('describes place in a text', function() {
+ var doc = getDocumentFromXML('<section>Alice</section>');
+
+ var fragment = doc.createFragment(doc.CaretFragment, {node: doc.root.contents()[0], offset: 1});
+
+ expect(fragment instanceof fragment.CaretFragment).to.be.true;
+ expect(fragment instanceof fragment.NodeFragment).to.be.true;
+ expect(fragment.node.getText()).to.equal('Alice');
+ expect(fragment.offset).to.equal(1);
+ });
+ });
+
+ describe('text range fragment', function() {
+ it('describes fragment of a text node', function() {
+ var doc = getDocumentFromXML('<section>Alice</section>'),
+ textNode = doc.root.contents()[0];
+
+ var fragment = doc.createFragment(doc.TextRangeFragment, {
+ node1: textNode,
+ offset1: 4,
+ node2: textNode,
+ offset2: 1
+ });
+
+ expect(fragment instanceof fragment.TextRangeFragment).to.be.true;
+ expect(fragment instanceof fragment.RangeFragment).to.be.true;
+ expect(fragment.startNode.getText()).to.equal('Alice');
+ expect(fragment.startOffset).to.equal(1);
+ expect(fragment.endNode.getText()).to.equal('Alice');
+ expect(fragment.endOffset).to.equal(4);
+ });
+ it('describes text spanning multiple nodes', function() {
+ var doc = getDocumentFromXML('<section>Alice <span>has</span> a cat!</section>'),
+ textNode1 = doc.root.contents()[0],
+ textNode2 = doc.root.contents()[2];
+
+ var fragment = doc.createFragment(doc.TextRangeFragment, {
+ node1: textNode2,
+ offset1: 4,
+ node2: textNode1,
+ offset2: 1
+ });
+
+ expect(fragment instanceof fragment.TextRangeFragment).to.be.true;
+ expect(fragment.startNode.getText()).to.equal('Alice ');
+ expect(fragment.startOffset).to.equal(1);
+ expect(fragment.endNode.getText()).to.equal(' a cat!');
+ expect(fragment.endOffset).to.equal(4);
+ });
+ });
+});
+
+});
\ No newline at end of file
'libs/backbone',
'smartxml/events',
'smartxml/transformations',
- 'smartxml/core'
-], function($, _, Backbone, events, transformations, coreTransformations) {
+ 'smartxml/core',
+ 'smartxml/fragments'
+], function($, _, Backbone, events, transformations, coreTransformations, fragments) {
'use strict';
/* globals Node */
return this.document.root.sameNode(this);
},
+ isInDocument: function() {
+ return this.document.containsNode(this);
+ },
+
+ isSiblingOf: function(node) {
+ return node && this.parent().sameNode(node.parent());
+ },
+
sameNode: function(otherNode) {
return !!(otherNode) && this.nativeNode === otherNode.nativeNode;
},
return 0;
}
return this.parent().indexOf(this);
+ },
+
+ getNearestElementNode: function() {
+ return this.nodeType === Node.ELEMENT_NODE ? this : this.parent();
}
});
return node && (node.nativeNode === this.nativeNode || node._$.parents().index(this._$) !== -1);
},
+ getLastTextNode: function() {
+ var contents = this.contents(),
+ toret;
+
+ contents.reverse().some(function(node) {
+ if(node.nodeType === Node.TEXT_NODE) {
+ toret = node;
+ return true;
+ }
+ toret = node.getLastTextNode();
+ return !!toret;
+ });
+
+ return toret;
+ },
+
toXML: function() {
var wrapper = $('<div>');
wrapper.append(this._getXMLDOMToDump());
return this.nativeNode.data;
},
+
+ containsNode: function() {
+ return false;
+ },
+
triggerTextChangeEvent: function() {
var event = new events.ChangeEvent('nodeTextChange', {node: this});
this.document.trigger('change', event);
this.loadXML(xml);
};
-$.extend(Document.prototype, Backbone.Events, {
+$.extend(Document.prototype, Backbone.Events, fragments, {
ElementNodeFactory: ElementNode,
TextNodeFactory: TextNode,
this._undoInProgress = false;
this.redoStack.push(transaction);
+ this.trigger('operationEnd');
}
},
redo: function() {
});
this._transformationLevel--;
this.undoStack.push(transaction);
+ this.trigger('operationEnd');
+
}
},
}
if(this._currentTransaction.hasTransformations()) {
this.undoStack.push(this._currentTransaction);
+ this.trigger('operationEnd');
}
this._currentTransaction = null;
},
}
return $document[0];
}, configurable: true});
+ },
+
+ createFragment: function(Type, params) {
+ if(!Type.prototype instanceof fragments.Fragment) {
+ throw new Error('Can\'t create a fragment: `Type` is not a valid Fragment');
+ }
+ return new Type(this, params);
}
});
expect(event.meta.oldVal).to.equal('value1');
});
});
+
+ describe('Searching for the last child text node', function() {
+ [
+ '<div>xxx<div></div>last</div>',
+ '<div><div>last</div></div>',
+ '<div>xxx<div>last</div><div></div></div>'
+ ].forEach(function(xml, i) {
+ var example = 'example ' + i;
+ it('returns last child text node ' + example + ')', function() {
+ var doc = getDocumentFromXML(xml),
+ lastTextNode = doc.root.getLastTextNode();
+ expect(lastTextNode.getText()).to.equal('last', example);
+ });
+ });
+ });
});
describe('Basic TextNode properties', function() {
}
};
+extension.wlxmlClass.list.transformations = {
+ extractListItems: function() {
+ var contents = this.contents(),
+ first = contents[0],
+ last;
+ if(contents.length) {
+ last = contents[contents.length-1];
+ this.document.extractItems({
+ item1: first,
+ item2: last
+ });
+ } else {
+ this.detach();
+ }
+ }
+};
+
extension.document.methods = {
areItemsOfSameList: function(params) {
return params.node1.parent().sameNode(params.node2.parent()) && params.node2.parent().is('list');
}
};
-
-
extension.document.transformations.createList = {
impl: function(params) {
/* globals Node */
var WLXMLDocumentNodeMethods = {
- isInside: function(klass) {
- var parent = this.getParent(klass);
+ isInside: function(query) {
+ var parent = this.getParent(query);
return !!parent;
},
- getParent: function(klass) {
+ getParent: function(query) {
/* globals Node */
var me = this.nodeType === Node.ELEMENT_NODE ? [this] : [],
toret;
me.concat(this.parents()).some(function(node) {
- if(node.is(klass)) {
+ if(node.is(query)) {
toret = node;
return true;
}
return this.setAttr('class', klass);
}
},
- is: function(klass) {
- return this.getClass().substr(0, klass.length) === klass;
+ is: function(query) {
+ if(typeof query === 'string') {
+ query = {klass: query};
+ }
+ return (_.isUndefined(query.klass) || this.getClass().substr(0, query.klass.length) === query.klass) &&
+ (_.isUndefined(query.tagName) || this.getTagName() === query.tagName);
},
getMetaAttributes: function() {
var toret = new AttributesList(),
return (/\.test\.js$/).test(file);
});
+ /* globals window */
+ // This installs noop i18n functions so that tests can work with i18nized code
+ window.gettext = window.interpolate = function() {
+ return Array.prototype.slice.call(arguments, 0);
+ };
+
require({
baseUrl: '/base/src/editor',
deps: tests,