});
-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);
}
};
}
}, 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',
'./canvas/canvas',
-'./commands',
-'libs/text!./template.html'], function($, canvas3, commands, template) {
+'libs/text!./template.html'], function($, _, canvas3, template) {
'use strict';
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) {
+ (actionHandlers[action.getPluginName()] || []).forEach(function(handler) {
+ handler(canvas, action, ret);
+ });
}
};
--- /dev/null
+define(function(require) {
+
+'use strict';
+
+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();
+
+ 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() {
+ var ret = this.action.execute();
+ this.trigger('actionExecuted', this.action, ret);
+ },
+ 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 = {},
+ contextDefer = {},
+ duringClick = false,
+ 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;
- }
+ _.pairs(contextParams).forEach(function(pair) {
+ var name = pair[0],
+ value = pair[1];
+ action.updateContextParam(name, value);
+ });
- if(command === 'undo' || command === 'redo') {
- params.callback = function(disable) {
- btn.attr('disabled', !disable);
- };
+ group.append(view.dom);
+ view.on('actionExecuted', function(action, ret) {
+ sandbox.publish('actionExecuted', action, ret);
+ duringClick = false;
+ _.pairs(contextDefer).forEach(function(pair) {
+ var what = pair[0],
+ deferred = pair[1];
+ if(deferred) {
+ refreshContextParam(what);
}
+ });
+ });
+ view.on('mousedown', function() {
+ duringClick = true;
+ });
- _.extend(params, {ctrlKey: e.ctrlKey});
-
- 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;
+ if(duringClick) {
+ contextDefer[what] = true;
+ } else {
+ refreshContextParam(what);
}
};
- view.setup();
+ var refreshContextParam = function(what) {
+ addedActions.forEach(function(action) {
+ action.updateContextParam(what, contextParams[what]);
+ });
+ };
+
+ 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
clear: function() {
},
setMetadata: function(node) {
+ this.node.find('.rng-module-metadataEditor-addBtn').attr('disabled', !node);
if(!node) {
this.metaTable.html('');
return;
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) {
<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})),
+ listens = false,
currentNode;
view.on('change', 'select', function(e) {
value = target.val().replace(/-/g, '.');
currentNode['set' + attr](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(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;
}
};
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);
- }
};
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);
}
};
return {
start: function() {
+ sandbox.registerActionsAppObject({
+ getUser: function() {
+ return sandbox.getConfig().user;
+ }
+ });
sandbox.getModule('data').start();
},
handleEvent: function(moduleName, eventName, args) {
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(params) {
+ params.document[dir]();
+ },
+ },
+ 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');
+ 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(params, editor) {
+ /* globals Node */
+ var node = params.fragment.node;
+ 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});
+ });
+ },
+ },
+ 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,
+ execute: function(params) {
+ params.fragment.document.transaction(function() {
+ var parent = params.fragment.startNode.parent();
+ return 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()]
+ });
+ });
+ }
+ });
+ }
+ };
+};
+
+
+var createLinkFromSelection = function(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'}
+ ]
+ });
+
+ dialog.on('execute', function(event) {
+ doc.transaction(function() {
+ var span = 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()]
+ });
+ span.setAttr('href', event.formData.href);
+ event.success();
+ return span;
+ });
+ });
+ dialog.show();
+};
+
+var editLink = function(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')}
+ ]
+ });
+
+ dialog.on('execute', function(event) {
+ doc.transaction(function() {
+ link.setAttr('href', event.formData.href);
+ event.success();
+ });
+ });
+ 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'}),
+ createWrapTextAction({name: 'cite', klass: 'cite'}),
+ 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(params) {
+ var boundries = getBoundriesForAList(params.fragment),
+ listParams = {klass: type === 'Bullet' ? 'list' : 'list.enum'};
+ if(boundries && boundries.node1) {
+ listParams.node1 = boundries.node1;
+ listParams.node2 = boundries.node2;
+ boundries.node1.document.createList(listParams);
+ } else {
+ throw new Error('Invalid boundries');
+ }
+ },
+ remove: function(params) {
+ /* globals Node */
+ var current = params.fragment.node;
+
+ var toSearch = current.nodeType === Node.ELEMENT_NODE ? [current] : [];
+ toSearch = toSearch.concat(current.parents());
+ toSearch.some(function(node) {
+ if(node.is('list')) {
+ node.object.extractListItems();
+ return true; // break
+ }
+ });
+ },
+ changeType: function(params) {
+ params.fragment.node.getParent('list').setClass(type === 'Bullet' ? 'list' : 'list.enum');
+ }
+ };
+
+ 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;
+
+
+ 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;
+
+ if(!toSwitch.is(createParams.from)) {
+ toSwitch = toSwitch.getParent(createParams.from);
+ }
+
+ return _.extend(state, {
+ allowed: !!toSwitch,
+ toggled: alreadyInTarget,
+ description: 'Switch to ' + createParams.to.name,
+ execute: alreadyInTarget ? function() {} : function() {
+ 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 {
+ actions: [
+ createSwitchAction({name: 'switchToHeader', from: {tagName: 'div', klass: 'p'}, to: {tagName: 'header', klass: '', name: gettext('header')}}),
+ createSwitchAction({name: 'switchToParagraph', from: {tagName: 'header'}, to: {tagName: 'div', klass: 'p', name: gettext('paragraf')}})
+ ]
+};
+
+});
\ 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',
+ execute: function(params) {
+ var node = params.fragment.node.getNearestElementNode();
+ var toAdd = node.document.createDocumentNode(params.template.content);
+ node.after(toAdd);
+ }
+ },
+ getState: function(params) {
+ 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)) {
+ return {
+ allowed: false,
+ description: gettext('Wrong node selected')
+ };
+ }
+ return {
+ allowed: true,
+ description: interpolate(gettext('Insert template %s after %s'), [params.template.name, params.fragment.node.getNearestElementNode().getTagName()])
+ };
+ }
+};
+
+
+return {
+ actions: [insertTemplateAction]
+};
+
+});
\ No newline at end of file
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,