requirejs: {
compile: {
options: {
- baseUrl: '',
- mainConfigFile: 'entrypoint.js',
+ baseUrl: 'src/editor',
+ mainConfigFile: 'src/editor/entrypoint.js',
out: 'build/rng.js',
name: 'entrypoint',
include: ['libs/require']
yuicompress: true
},
files: {
- 'build/rng.css': 'styles/main.less'
+ 'build/rng.css': 'src/editor/styles/main.less'
},
},
},
jshint: {
- all: ['Gruntfile.js', 'modules/**/*.js', 'views/**/*.js', 'fnpjs/**/*.js'],
+ all: ['Gruntfile.js', 'src/**/*.js'],
options: {
jshintrc: '.jshintrc'
}
+++ /dev/null
-(function() {
- 'use strict';
-
- requirejs.config({
- baseUrl: '/static/editor',
-
- map: {
- '*':
- {
- 'libs/jquery': 'libs/jquery-1.9.1.min',
- 'libs/underscore': 'libs/underscore-min',
- 'libs/bootstrap': 'libs/bootstrap/js/bootstrap.min',
- 'libs/backbone': 'libs/backbone-min'
- }
- },
-
- shim: {
- 'libs/jquery-1.9.1.min': {
- exports: '$',
- },
- 'libs/underscore-min': {
- exports: '_'
- },
- 'libs/bootstrap/js/bootstrap.min': {
- deps: ['libs/jquery']
- },
- 'libs/backbone-min': {
- exports: 'Backbone',
- deps: ['libs/jquery', 'libs/underscore']
- }
- }
-
- });
-
- requirejs([
- 'libs/jquery',
- 'fnpjs/runner',
- 'rng',
- './modules',
- 'libs/bootstrap'
- ], function($, runner, rng, modules) {
- $(function() {
- var app = new runner.Runner(rng, modules);
- app.setBootstrappedData('data', RNG_BOOTSTRAP_DATA);
- app.start({rootSelector:'#editor_root'});
- });
- });
-
-
-})();
\ No newline at end of file
+++ /dev/null
-define(['libs/jquery', 'libs/underscore'], function($ ,_) {
- 'use strict';
-
- var Layout = function(template) {
- var layout = this;
- this.dom = $(_.template(template)());
- this.views = {};
-
- this.dom.onShow = function() {
- _.values(layout.views).forEach(function(view) {
- if(view.onShow)
- view.onShow();
- });
- };
- this.dom.onHide = function() {
- _.values(layout.views).forEach(function(view) {
- if(view.onHide)
- view.onHide();
- });
- };
-
- };
-
- Layout.prototype.setView = function(place, view) {
- this.dom.find('[fnpjs-place=' + place + ']').append(view);
- this.views[place] = view;
- if(this.dom.is(':visible') && view.onShow) {
- view.onShow();
- }
- };
-
- Layout.prototype.getAsView = function() {
- return this.dom;
- };
-
- return {Layout: Layout};
-});
\ No newline at end of file
+++ /dev/null
-define(['libs/jquery', 'libs/underscore'], function($, _) {
-
-var Runner = function(app, modules) {
-
- function getModuleInstance(moduleName) {
- var module = moduleInstances[moduleName] = (moduleInstances[moduleName] || modules[moduleName](new Sandbox(moduleName)));
- return module;
- }
-
- var bootstrappedData = {},
- options = {},
- moduleInstances = {},
- eventListeners = [];
-
- _.each(_.keys(modules || {}), function(moduleName) {
- if(_.contains(app.permissions[moduleName] || [], 'handleEvents')) {
- eventListeners.push(moduleName);
- }
- });
-
-
-
- var Sandbox = function(moduleName) {
- this.$ = $;
- this._ = _;
-
- this.getBootstrappedData = function() {
- return bootstrappedData[moduleName];
- };
-
- this.getTemplate = function(templateName) {
- return _.template($('[data-template-name="' + moduleName + '.' + templateName + '"]').html().trim());
- };
-
- this.publish = function(eventName) {
- console.log(moduleName + ': ' + eventName);
- var eventArgs = Array.prototype.slice.call(arguments, 1);
- _.each(eventListeners, function(listenerModuleName) {
- var listener = moduleInstances[listenerModuleName];
- if(listener) {
- listener.handleEvent(moduleName, eventName, eventArgs);
- }
- });
- };
-
- var permissions = app.permissions[moduleName];
-
- this.getModule = _.contains(permissions, 'getModule') ? function(requestedModuleName) {
- return getModuleInstance(requestedModuleName);
- } : undefined;
-
- this.getDOM = _.contains(permissions, 'getDOM') ? function() {
- return $(options.rootSelector);
- } : undefined;
-
- };
-
-
- this.setBootstrappedData = function(moduleName, data) {
- bootstrappedData[moduleName] = data;
- };
-
- this.start = function(_options) {
- options = _.extend({
- rootSelector: 'body'
- }, _options);
- app.initModules.forEach(function(moduleName) {
- getModuleInstance(moduleName).start();
- });
- };
-};
-
-return {
- Runner: Runner
-};
-
-});
\ No newline at end of file
+++ /dev/null
-define(['libs/jquery', './layout'], function($, layout) {
-
- var VBox = function() {};
-
- VBox.prototype = new layout.Layout('<div class="fnpjs-vbox"></div>');
- VBox.prototype.appendView = function(view) {
- var item = $('<div>').addClass('fnpjs-vbox-item').append(view);
- this.dom.append(item);
- };
-
- return {VBox: VBox};
-
-});
\ No newline at end of file
'vkbeautify.js',
{pattern: 'libs/*.js', included: false},
- {pattern: 'fnpjs/**/*.js', included: false},
- {pattern: 'modules/**/*.js', included: false},
- {pattern: 'utils/**/*.js', included: false},
- {pattern: 'views/**/*.js', included: false},
- {pattern: 'fnpjs/**/*.html', included: false},
- {pattern: 'modules/**/*.html', included: false},
- {pattern: 'views/**/*.html', included: false},
-
+ {pattern: 'src/**/*.js', included: false},
+ {pattern: 'src/**/*.html', included: false},
'tests/main.js',
];
+++ /dev/null
-define(function(require) {
- /*
- Each module must be required explicitly by apropriate 'require' function call
- in order for requirejs optimizer to work.
- */
-
- 'use strict';
-
- return {
- data: require('modules/data/data'),
- rng: require('modules/rng/rng'),
- mainBar: require('modules/mainBar/mainBar'),
- indicator: require('modules/indicator/indicator'),
-
- sourceEditor: require('modules/sourceEditor/sourceEditor'),
-
- documentCanvas: require('modules/documentCanvas/documentCanvas'),
- documentToolbar: require('modules/documentToolbar/documentToolbar'),
- nodePane: require('modules/nodePane/nodePane'),
- metadataEditor: require('modules/metadataEditor/metadataEditor'),
- nodeFamilyTree: require('modules/nodeFamilyTree/nodeFamilyTree'),
- nodeBreadCrumbs: require('modules/nodeBreadCrumbs/nodeBreadCrumbs'),
-
- documentHistory: require('modules/documentHistory/documentHistory'),
- diffViewer: require('modules/diffViewer/diffViewer')
-
- }
-});
\ No newline at end of file
+++ /dev/null
-define(['./saveDialog'], function(saveDialog) {
-
-'use strict';
-
-return function(sandbox) {
-
- var doc = sandbox.getBootstrappedData().document;
- var document_id = sandbox.getBootstrappedData().document_id;
- var document_version = sandbox.getBootstrappedData().version;
- var history = sandbox.getBootstrappedData().history;
-
-
- if(doc === '') {
- doc = '<section\n\
- xmlns="http://nowoczesnapolska.org.pl/sst#"\n\
- xmlns:xlink="http://www.w3.org/1999/xlink"\n\
- xmlns:dc="http://purl.org/dc/elements/1.1/"\n\
- xmlns:dcterms="http://purl.org/dc/terms/"\n\
- >\n\
- <metadata>\n\
- </metadata>\n\
- <div class="p"></div>\n\
- </section>';
- }
-
-
- function readCookie(name) {
- var nameEQ = escape(name) + "=";
- var ca = document.cookie.split(';');
- for (var i = 0; i < ca.length; i++) {
- var c = ca[i];
- while (c.charAt(0) == ' ') c = c.substring(1, c.length);
- if (c.indexOf(nameEQ) === 0) return unescape(c.substring(nameEQ.length, c.length));
- }
- return null;
- }
-
- $.ajaxSetup({
- crossDomain: false,
- beforeSend: function(xhr, settings) {
- if (!(/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type))) {
- xhr.setRequestHeader("X-CSRFToken", readCookie('csrftoken'));
- }
- }
- });
-
- var reloadHistory = function() {
- $.ajax({
- method: 'get',
- url: '/' + gettext('editor') + '/' + document_id + '/history',
- success: function(data) {
- history = data;
- sandbox.publish('historyItemAdded', data.slice(-1)[0]);
- },
- });
- };
-
- return {
- start: function() {
- sandbox.publish('ready');
- },
- getDocument: function() {
- return doc;
- },
- commitDocument: function(newDocument, reason) {
- doc = newDocument;
- sandbox.publish('documentChanged', doc, reason);
- },
- saveDocument: function() {
-
- var dialog = saveDialog.create();
- dialog.on('save', function(event) {
- sandbox.publish('savingStarted');
- dialog.toggleButtons(false);
- $.ajax({
- method: 'post',
- url: '/' + gettext('editor') + '/' + document_id,
- data: JSON.stringify({document:doc, description: event.data.description}),
- success: function() {
- event.success();
- sandbox.publish('savingEnded', 'success');
- reloadHistory();
- },
- error: function() {event.error(); sandbox.publish('savingEnded', 'error');}
- });
- console.log('save');
- });
- dialog.on('cancel', function() {
- });
- dialog.show();
-
-
- },
- getHistory: function() {
- return history;
- },
- fetchDiff: function(ver1, ver2) {
- $.ajax({
- method: 'get',
- url: '/' + gettext('editor') + '/' + document_id + '/diff',
- data: {from: ver1, to: ver2},
- success: function(data) {
- sandbox.publish('diffFetched', {table: data, ver1: ver1, ver2: ver2});
- },
- });
- },
- restoreVersion: function(options) {
- if(options.version && options.description) {
- sandbox.publish('restoringStarted', {version: options.version});
- $.ajax({
- method: 'post',
- dataType: 'json',
- url: '/' + gettext('editor') + '/' + document_id + '/revert',
- data: JSON.stringify(options),
- success: function(data) {
- doc = data.document;
- document_version = data.version;
- reloadHistory();
- sandbox.publish('documentReverted', data);
- },
- });
- }
- },
- getDocumentId: function() {
- return document_id;
- },
- getDocumentVersion: function() {
- return document_version;
- }
- };
-};
-
-});
\ No newline at end of file
+++ /dev/null
-@import 'saveDialog.less';
\ No newline at end of file
+++ /dev/null
-<div class="rng-module-data-saveDialog modal hide static">
- <div class="modal-header">
- <button type="button" class="close">×</button>
- <h1>Zapisz dokument</h1>
- </div>
- <div class="modal-body">
- <label>Opisz swoje zmiany</label>
- <textarea rows="5"></textarea>
- </div>
- <div class="modal-footer">
- <a href="#" class="btn btn-info btn-mini save-btn">Zapisz</a>
- <a href="#" class="btn btn-danger btn-mini cancel-btn">Anuluj</a>
- </div>
-</div>
\ No newline at end of file
+++ /dev/null
-define([
-'libs/text!./saveDialog.html',
-'libs/underscore',
-'libs/backbone',
-'libs/jquery'
-], function(saveDialogTemplate, _, Backbone, $) {
-
- var DialogView = Backbone.View.extend({
- template: _.template(saveDialogTemplate),
- events: {
- 'click .save-btn': 'onSave',
- 'click .cancel-btn': 'close',
- 'click .close': 'close'
- },
- initialize: function() {
- _.bindAll(this);
- this.actionsDisabled = false;
- },
- show: function() {
- this.setElement(this.template());
- this.$el.modal({backdrop: 'static'});
- this.$el.modal('show');
- this.$('textarea').focus();
-
- },
- onSave: function(e) {
- e.preventDefault();
- var view = this;
- this.trigger('save', {
- data: {description: view.$el.find('textarea').val()},
- success: function() { view.actionsDisabled = false; view.close(); },
- error: function() { view.actionsDisabled = false; view.close(); },
- });
- },
- close: function(e) {
- if(e)
- e.preventDefault();
- if(!this.actionsDisabled) {
- this.$el.modal('hide');
- this.$el.remove();
- }
- },
- toggleButtons: function(toggle) {
- this.$('.btn, button').toggleClass('disabled', !toggle);
- this.$('textarea').attr('disabled', !toggle);
- this.actionsDisabled = !toggle;
- }
- });
-
- return {
- create: function() {
- return new DialogView();
- }
- };
-
-});
\ No newline at end of file
+++ /dev/null
-.rng-module-data-saveDialog {
- textarea {
- padding: 3px 3px;
- margin: 5px auto;
- width: 95%;
- display: block;
- }
-
- h1, label {
- font-size: 12px;
- line-height: 12px;
-
- }
-
- h1 {
- margin: 2px 5px;
- font-weight: bold;
- }
-}
\ No newline at end of file
+++ /dev/null
-<div class="rng-module-diffViewer-diffView"></div>
\ No newline at end of file
+++ /dev/null
-define([
-'libs/jquery',
-'libs/underscore',
-'views/tabs/tabs',
-'libs/text!./diff.html'
-], function($, _, tabs, diffTemplateSrc) {
-
-'use strict';
-
-return function(sandbox) {
-
- var dom = $('<div>').addClass('rng-module-diffViewer');
- var tabsView = (new tabs.View({position: 'right'})).render();
- dom.append(tabsView.getAsView());
-
- var DiffView = function() {
- this.dom = $(diffTemplateSrc);
- };
-
- DiffView.prototype.setTable = function(table) {
- this.dom.append(table);
- };
-
-
- return {
- start: function() {sandbox.publish('ready');},
- getView: function() {return dom;},
- setDiff: function(diff) {
- var diffView = new DiffView();
- diffView.setTable(diff.table);
- var slug = diff.ver1 + '-' + diff.ver2;
- tabsView.addTab(diff.ver1 + '->' + diff.ver2, slug, diffView.dom);
- tabsView.selectTab(slug);
- }
- };
-};
-
-});
\ No newline at end of file
+++ /dev/null
-.rng-module-diffViewer {
- .nav-tabs > li > a {
- min-width: 0;
- font-size: 0.9em;
- padding: 4px 6px;
- }
-
- .tab-content {
- position: absolute;
- top:0;
- bottom:0;
- left:0;
- right:60px;
- overflow-y: scroll;
- &::-webkit-scrollbar {
- .rng-mixin-scrollbar;
- }
- &::-webkit-scrollbar-track {
- .rng-mixin-scrollbar-track;
- }
- &::-webkit-scrollbar-thumb {
- .rng-mixin-scrollbar-thumb;
- }
- &::-webkit-scrollbar-thumb:window-inactive {
- .rng-mixin-scrollbar-thumb-window-inactive;
- }
- }
-
- .diff_table {
- border-width: 1px 0 1px 1px;
- border-style: solid;
- border-color: #ddd;
- empty-cells: show;
- border-spacing: 0px;
- }
-
- .diff_table td {
- border-width: 0px 1px 1px 0px;
- border-style: dotted;
- border-color: grey;
- font-size: 10px;
- line-height: 20px;
- font-family: monospace;
- padding: 0px;
- white-space: pre-line;
- /*word-wrap:break-word;
- word-break:break-all; */
- }
-
- .diff_table th {
- border-width: 0px 1px 1px 0px;
- border-style: solid;
- border-color: #ddd;
- background: #e5ffe5;
- }
-
- .diff_table tr.change {
- background-color: #dcdcdc;
- }
-
- .diff_mark {
- display: inline-block;
- padding: 2px;
- }
-
- .diff_mark_removed {
- background-color: #ff9c94;
- }
-
- .diff_mark_added {
- background-color: #90ee90;
- }
-
- .diff_mark_changed {
- background-color: yellow;
- }
-}
\ No newline at end of file
+++ /dev/null
-define([
-'libs/jquery',
-'libs/underscore',
-'libs/backbone',
-'modules/documentCanvas/canvas/documentElement',
-'modules/documentCanvas/canvas/keyboard',
-'modules/documentCanvas/canvas/utils'
-], function($, _, Backbone, documentElement, keyboard, utils) {
-
-'use strict';
-
-var Canvas = function(wlxml, publisher) {
- this.eventBus = _.extend({}, Backbone.Events);
- this.loadWlxml(wlxml);
- this.publisher = publisher ? publisher : function() {};
-};
-
-$.extend(Canvas.prototype, {
-
- loadWlxml: function(wlxml) {
- var d = wlxml ? $($.trim(wlxml)) : null;
- if(d) {
- this.wrapper = $('<div>').addClass('canvas-wrapper').attr('contenteditable', true);
- this.wrapper.append(d);
- var canvas = this;
-
- this.wrapper.find('*').replaceWith(function() {
- var currentTag = $(this);
- if(currentTag.attr('wlxml-tag'))
- return;
-
- var meta = {}, others = {};
- for(var i = 0; i < this.attributes.length; i++) {
- var attr = this.attributes[i];
- if(attr.name.substr(0, 5) === 'meta-')
- meta[attr.name.substr(5)] = attr.value;
- else if(attr.name !== 'class')
- others[attr.name] = attr.value;
- }
-
- var element = canvas.createNodeElement({
- tag: currentTag.prop('tagName').toLowerCase(),
- klass: currentTag.attr('class'),
- meta: meta,
- others: others,
- rawChildren: currentTag.contents(),
- prepopulateOnEmpty: true
- });
-
- ['orig-before', 'orig-after', 'orig-begin', 'orig-end'].forEach(function(attr) {
- element.data(attr, '');
- });
- return element.dom();
- });
-
- var FIRST_CONTENT_INDEX = 0;
-
- // @@ TODO - refactor!
- var getNode = function(element) {
- return element.children('[document-element-content]');
- }
-
- this.wrapper.find(':not(iframe)').addBack().contents()
- .filter(function() {return this.nodeType === Node.TEXT_NODE})
- .each(function() {
-
- // TODO: use DocumentElement API
-
- var el = $(this),
- text = {original: el.text(), trimmed: $.trim(el.text())},
- elParent = el.parent(),
- hasSpanParent = elParent.attr('wlxml-tag') === 'span',
- hasSpanBefore = el.prev().length > 0 && getNode($(el.prev()[0])).attr('wlxml-tag') === 'span',
- hasSpanAfter = el.next().length > 0 && getNode($(el.next()[0])).attr('wlxml-tag') === 'span';
-
- if(el.parent().hasClass('canvas-widget') || elParent.attr('document-text-element') !== undefined)
- return true; // continue
-
- var addInfo = function(toAdd, where) {
- var parentContents = elParent.contents(),
- idx = parentContents.index(el[0]),
- prev = idx > FIRST_CONTENT_INDEX ? parentContents[idx-1] : null,
- next = idx < parentContents.length - 1 ? parentContents[idx+1] : null,
- target, key;
-
- if(where === 'above') {
- target = prev ? $(prev) : elParent.parent();
- key = prev ? 'orig-after' : 'orig-begin';
- } else if(where === 'below') {
- target = next ? $(next) : elParent.parent();
- key = next ? 'orig-before' : 'orig-end';
- } else { throw new Object;}
-
- target.data(key, toAdd);
- }
-
- text.transformed = text.trimmed;
-
- if(hasSpanParent || hasSpanBefore || hasSpanAfter) {
- var startSpace = /\s/g.test(text.original.substr(0,1)),
- endSpace = /\s/g.test(text.original.substr(-1)) && text.original.length > 1;
- text.transformed = (startSpace && (hasSpanParent || hasSpanBefore) ? ' ' : '')
- + text.trimmed
- + (endSpace && (hasSpanParent || hasSpanAfter) ? ' ' : '');
- } else {
- if(text.trimmed.length === 0 && text.original.length > 0 && elParent.contents().length === 1)
- text.transformed = ' ';
- }
-
- if(!text.transformed) {
- addInfo(text.original, 'below');
- el.remove();
- return true; // continue
- }
-
- if(text.transformed !== text.original) {
- if(!text.trimmed) {
- addInfo(text.original, 'below');
- } else {
- var startingMatch = text.original.match(/^\s+/g),
- endingMatch = text.original.match(/\s+$/g),
- startingWhiteSpace = startingMatch ? startingMatch[0] : null,
- endingWhiteSpace = endingMatch ? endingMatch[0] : null;
-
- if(endingWhiteSpace) {
- if(text.transformed[text.transformed.length - 1] === ' ' && endingWhiteSpace[0] === ' ')
- endingWhiteSpace = endingWhiteSpace.substr(1);
- addInfo(endingWhiteSpace, 'below');
- }
-
- if(startingWhiteSpace) {
- if(text.transformed[0] === ' ' && startingWhiteSpace[startingWhiteSpace.length-1] === ' ')
- startingWhiteSpace = startingWhiteSpace.substr(0, startingWhiteSpace.length -1);
- addInfo(startingWhiteSpace, 'above');
- }
- }
- }
-
- var element = documentElement.DocumentTextElement.create({text: text.transformed});
- el.replaceWith(element.dom());
- });
-
- this.d = this.wrapper.children(0);
-
- this.wrapper.on('keyup keydown keypress', function(e) {
- keyboard.handleKey(e, this);
- }.bind(this));
-
- this.wrapper.on('click', '[document-node-element], [document-text-element]', function(e) {
- e.stopPropagation();
- canvas.setCurrentElement(canvas.getDocumentElement(e.currentTarget), {caretTo: false});
- });
-
- var observer = new MutationObserver(function(mutations) {
- mutations.forEach(function(mutation) {
- if(documentElement.DocumentTextElement.isContentContainer(mutation.target)) {
- observer.disconnect();
- if(mutation.target.data === '')
- mutation.target.data = utils.unicode.ZWS;
- else if(mutation.oldValue === utils.unicode.ZWS) {
- mutation.target.data = mutation.target.data.replace(utils.unicode.ZWS, '');
- canvas._moveCaretToTextElement(canvas.getDocumentElement(mutation.target), 'end');
- }
- observer.observe(canvas.d[0], config);
- canvas.publisher('contentChanged');
- }
- });
- });
- var config = { attributes: false, childList: false, characterData: true, subtree: true, characterDataOldValue: true};
- observer.observe(this.d[0], config);
-
-
- this.wrapper.on('mouseover', '[document-node-element], [document-text-element]', function(e) {
- var el = canvas.getDocumentElement(e.currentTarget);
- if(!el)
- return;
- e.stopPropagation();
- if(el instanceof documentElement.DocumentTextElement)
- el = el.parent();
- el.toggleLabel(true);
- });
- this.wrapper.on('mouseout', '[document-node-element], [document-text-element]', function(e) {
- var el = canvas.getDocumentElement(e.currentTarget);
- if(!el)
- return;
- e.stopPropagation();
- if(el instanceof documentElement.DocumentTextElement)
- el = el.parent();
- el.toggleLabel(false);
- });
-
- this.eventBus.on('elementToggled', function(toggle, element) {
- if(!toggle) {
- canvas.setCurrentElement(element.getPreviousTextElement());
- }
- })
-
- } else {
- this.d = null;
- }
- },
-
- view: function() {
- return this.wrapper;
- },
-
- doc: function() {
- if(this.d === null)
- return null;
- return documentElement.DocumentNodeElement.fromHTMLElement(this.d.get(0), this); //{wlxmlTag: this.d.prop('tagName')};
- },
-
- createNodeElement: function(params) {
- return documentElement.DocumentNodeElement.create(params, this);
- },
-
- wrapText: function(params) {
- params = _.extend({textNodeIdx: 0}, params);
- if(typeof params.textNodeIdx === 'number')
- params.textNodeIdx = [params.textNodeIdx];
-
- var childrenInside = params.inside.children(),
- idx1 = Math.min.apply(Math, params.textNodeIdx),
- idx2 = Math.max.apply(Math, params.textNodeIdx),
- textNode1 = childrenInside[idx1],
- textNode2 = childrenInside[idx2],
- sameNode = textNode1.sameNode(textNode2),
- prefixOutside = textNode1.getText().substr(0, params.offsetStart),
- prefixInside = textNode1.getText().substr(params.offsetStart),
- suffixInside = textNode2.getText().substr(0, params.offsetEnd),
- suffixOutside = textNode2.getText().substr(params.offsetEnd)
- ;
-
- var wrapperElement = this.createNodeElement({tag: params._with.tag, klass: params._with.klass});
- textNode1.after(wrapperElement);
- textNode1.detach();
-
- if(prefixOutside.length > 0)
- wrapperElement.before({text:prefixOutside});
- if(sameNode) {
- var core = textNode1.getText().substr(params.offsetStart, params.offsetEnd - params.offsetStart);
- wrapperElement.append({text: core});
- } else {
- textNode2.detach();
- if(prefixInside.length > 0)
- wrapperElement.append({text: prefixInside});
- for(var i = idx1 + 1; i < idx2; i++) {
- wrapperElement.append(childrenInside[i]);
- }
- if(suffixInside.length > 0)
- wrapperElement.append({text: suffixInside});
- }
- if(suffixOutside.length > 0)
- wrapperElement.after({text: suffixOutside});
- return wrapperElement;
- },
-
- wrapElements: function(params) {
- if(!(params.element1.parent().sameNode(params.element2.parent())))
- return;
-
- var parent = params.element1.parent(),
- parentChildren = parent.children(),
- wrapper = this.createNodeElement({
- tag: params._with.tag,
- klass: params._with.klass}),
- idx1 = parent.childIndex(params.element1),
- idx2 = parent.childIndex(params.element2);
-
- if(idx1 > idx2) {
- var tmp = idx1;
- idx1 = idx2;
- idx2 = tmp;
- }
-
- var insertingMethod, insertingTarget;
- if(idx1 === 0) {
- insertingMethod = 'prepend';
- insertingTarget = parent;
- } else {
- insertingMethod = 'after';
- insertingTarget = parentChildren[idx1-1];
- }
-
- for(var i = idx1; i <= idx2; i++) {
- wrapper.append(parentChildren[i].detach());
- }
-
- insertingTarget[insertingMethod](wrapper);
- return wrapper;
- },
-
- getSiblingParents: function(params) {
- var parents1 = [params.element1].concat(params.element1.parents()).reverse(),
- parents2 = [params.element2].concat(params.element2.parents()).reverse(),
- noSiblingParents = null;
-
- if(parents1.length === 0 || parents2.length === 0 || !(parents1[0].sameNode(parents2[0])))
- return noSiblingParents;
-
- var i;
- for(i = 0; i < Math.min(parents1.length, parents2.length); i++) {
- if(parents1[i].sameNode(parents2[i]))
- continue;
- break;
- }
- return {element1: parents1[i], element2: parents2[i]};
- },
-
- getDocumentElement: function(from) {
- if(from instanceof HTMLElement || from instanceof Text) {
- return documentElement.DocumentElement.fromHTMLElement(from, this);
- }
- },
- getCursor: function() {
- return new Cursor(this);
- },
-
- list: {},
-
-
- getCurrentNodeElement: function() {
- return this.getDocumentElement(this.wrapper.find('.current-node-element').parent()[0]);
- },
-
- getCurrentTextElement: function() {
- return this.getDocumentElement(this.wrapper.find('.current-text-element')[0]);
- },
-
-
-
- setCurrentElement: function(element, params) {
- params = _.extend({caretTo: 'end'}, params);
- var findFirstDirectTextChild = function(e, nodeToLand) {
- var byBrowser = this.getCursor().getPosition().element;
- if(byBrowser && byBrowser.parent().sameNode(nodeToLand))
- return byBrowser;
- var children = e.children();
- for(var i = 0; i < children.length; i++) {
- if(children[i] instanceof documentElement.DocumentTextElement)
- return children[i];
- }
- return null;
- }.bind(this);
- var _markAsCurrent = function(element) {
- if(element instanceof documentElement.DocumentTextElement) {
- this.wrapper.find('.current-text-element').removeClass('current-text-element');
- element.dom().addClass('current-text-element');
- } else {
- this.wrapper.find('.current-node-element').removeClass('current-node-element')
- element._container().addClass('current-node-element');
- this.publisher('currentElementChanged', element);
- }
- }.bind(this);
-
-
- var isTextElement = element instanceof documentElement.DocumentTextElement,
- nodeElementToLand = isTextElement ? element.parent() : element,
- textElementToLand = isTextElement ? element : findFirstDirectTextChild(element, nodeElementToLand),
- currentTextElement = this.getCurrentTextElement(),
- currentNodeElement = this.getCurrentNodeElement();
-
- if(currentTextElement && !(currentTextElement.sameNode(textElementToLand)))
- this.wrapper.find('.current-text-element').removeClass('current-text-element');
-
- if(textElementToLand) {
- _markAsCurrent(textElementToLand);
- 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);
- } else {
- document.getSelection().removeAllRanges();
- }
-
- if(!(currentNodeElement && currentNodeElement.sameNode(nodeElementToLand))) {
- _markAsCurrent(nodeElementToLand);
-
- this.publisher('currentNodeElementSet', nodeElementToLand);
- }
- },
-
- _moveCaretToTextElement: function(element, where) {
- var range = document.createRange(),
- node = element.dom().contents()[0];
-
- if(typeof where !== 'number') {
- range.selectNodeContents(node);
- } else {
- range.setStart(node, where);
- }
-
- var collapseArg = true;
- if(where === 'end')
- collapseArg = false;
- range.collapse(collapseArg);
-
- var selection = document.getSelection();
-
- selection.removeAllRanges();
- selection.addRange(range);
- this.wrapper.focus(); // FF requires this for caret to be put where range colllapses, Chrome doesn't.
- },
-
- setCursorPosition: function(position) {
- if(position.element)
- this._moveCaretToTextElement(position.element, position.offset);
- },
-
- toXML: function() {
- var parent = $('<div>'),
- parts = this.doc().toXML(0);
- parent.append(parts);
- return parent.html();
- }
-});
-
-$.extend(Canvas.prototype.list, {
- create: function(params) {
- if(!(params.element1.parent().sameNode(params.element2.parent())))
- return false;
-
- var parent = params.element1.parent(),
- canvas = params.element1.canvas;
-
- if(parent.childIndex(params.element1) > parent.childIndex(params.element2)) {
- var tmp = params.element1;
- params.element1 = params.element2;
- params.element2 = tmp;
- }
-
- var elementsToWrap = [];
-
- var place = 'before';
- parent.children().some(function(element) {
- var _e = element;
- if(element.sameNode(params.element1))
- place = 'inside';
- if(place === 'inside') {
- if(element instanceof documentElement.DocumentTextElement) {
- element = element.wrapWithNodeElement({tag: 'div', klass: 'list.item'});
- if(element.children()[0].sameNode(params.element1))
- params.element1 = element;
- }
- element.setWlxmlClass('item');
- elementsToWrap.push(element);
- }
- if(_e.sameNode(params.element2))
- return true;
- });
-
- var listElement = canvas.createNodeElement({tag: 'div', klass: 'list-items' + (params.type === 'enum' ? '-enum' : '')});
- var toret;
- if(parent.is('list')) {
- var item = listElement.wrapWithNodeElement({tag: 'div', klass: 'item'});
- item.exec('toggleBullet', false);
- toret = listElement.parent();
- } else {
- toret = listElement;
- }
-
- params.element1.before(toret);
-
- elementsToWrap.forEach(function(element) {
- element.detach();
- listElement.append(element);
- });
- },
- extractItems: function(params) {
- params = _.extend({merge: true}, params);
- var list = params.element1.parent();
- if(!list.is('list') || !(list.sameNode(params.element2.parent())))
- return false;
-
- var idx1 = list.childIndex(params.element1),
- idx2 = list.childIndex(params.element2),
- precedingItems = [],
- extractedItems = [],
- succeedingItems = [],
- items = list.children(),
- listIsNested = list.parent().getWlxmlClass() === 'item',
- canvas = params.element1.canvas,
- i;
-
- if(idx1 > idx2) {
- var tmp = idx1; idx1 = idx2; idx2 = tmp;
- }
-
- items.forEach(function(item, idx) {
- if(idx < idx1)
- precedingItems.push(item);
- else if(idx >= idx1 && idx <= idx2) {
- extractedItems.push(item);
- }
- else {
- succeedingItems.push(item);
- }
- });
-
- var reference = listIsNested ? list.parent() : list;
- if(succeedingItems.length === 0) {
- var reference_orig = reference;
- extractedItems.forEach(function(item) {
- reference.after(item);
- reference = item;
- if(!listIsNested)
- item.setWlxmlClass(null);
- });
- if(precedingItems.length === 0)
- reference_orig.detach();
- } else if(precedingItems.length === 0) {
- extractedItems.forEach(function(item) {
- reference.before(item);
- if(!listIsNested)
- item.setWlxmlClass(null);
- });
- } else {
- extractedItems.forEach(function(item) {
- reference.after(item);
- if(!listIsNested)
- item.setWlxmlClass(null);
- reference = item;
- });
- var secondList = canvas.createNodeElement({tag: 'div', klass:'list-items'}),
- toAdd = secondList;
-
- if(listIsNested) {
- toAdd = secondList.wrapWithNodeElement({tag: 'div', klass:'item'});
- }
- succeedingItems.forEach(function(item) {
- secondList.append(item);
- });
-
- reference.after(toAdd);
- }
- if(!params.merge && listIsNested) {
- return this.extractItems({element1: extractedItems[0], element2: extractedItems[extractedItems.length-1]});
- }
- return true;
- },
- areItemsOfTheSameList: function(params) {
- var e1 = params.element1,
- e2 = params.element2;
- return e1.parent().sameNode(e2.parent())
- && e1.parent().is('list');
- }
-});
-
-
-var Cursor = function(canvas) {
- this.canvas = canvas;
-};
-
-$.extend(Cursor.prototype, {
- isSelecting: function() {
- var selection = window.getSelection();
- return !selection.isCollapsed;
- },
- isSelectingWithinElement: function() {
- return this.isSelecting() && this.getSelectionStart().element.sameNode(this.getSelectionEnd().element);
- },
- isSelectingSiblings: function() {
- return this.isSelecting() && this.getSelectionStart().element.parent().sameNode(this.getSelectionEnd().element.parent());
- },
- getPosition: function() {
- return this.getSelectionAnchor();
- },
- getSelectionStart: function() {
- return this.getSelectionBoundry('start');
- },
- getSelectionEnd: function() {
- return this.getSelectionBoundry('end');
- },
- getSelectionAnchor: function() {
- return this.getSelectionBoundry('anchor');
- },
- getSelectionFocus: function() {
- return this.getSelectionBoundry('focus');
- },
- getSelectionBoundry: function(which) {
- var selection = window.getSelection(),
- anchorElement = this.canvas.getDocumentElement(selection.anchorNode),
- focusElement = this.canvas.getDocumentElement(selection.focusNode);
-
- if((!anchorElement) || (anchorElement instanceof documentElement.DocumentNodeElement) || (!focusElement) || focusElement instanceof documentElement.DocumentNodeElement)
- return {};
-
- if(which === 'anchor') {
- return {
- element: anchorElement,
- offset: selection.anchorOffset,
- offsetAtBeginning: selection.anchorOffset === 0,
- offsetAtEnd: selection.anchorNode.data.length === selection.anchorOffset
- };
- }
- if(which === 'focus') {
- return {
- element: focusElement,
- offset: selection.focusOffset,
- offsetAtBeginning: selection.focusOffset === 0,
- offsetAtEnd: selection.focusNode.data.length === selection.focusOffset
- };
- }
-
- var element,
- offset;
-
- if(anchorElement.parent().sameNode(focusElement.parent())) {
- var parent = anchorElement.parent(),
- anchorFirst = parent.childIndex(anchorElement) < parent.childIndex(focusElement);
- if(anchorFirst) {
- if(which === 'start') {
- element = anchorElement;
- offset = selection.anchorOffset
- }
- else if(which === 'end') {
- element = focusElement,
- offset = selection.focusOffset
- }
- } else {
- if(which === 'start') {
- element = focusElement,
- offset = selection.focusOffset
- }
- else if(which === 'end') {
- element = anchorElement;
- offset = selection.anchorOffset
- }
- }
- } else {
- // TODO: Handle order via https://developer.mozilla.org/en-US/docs/Web/API/Node.compareDocumentPosition
- if(which === 'start') {
- element = anchorElement;
- offset = selection.anchorOffset
- } else {
- element = focusElement;
- offset = selection.focusOffset
- }
- }
-
- var nodeLen = (element.sameNode(focusElement) ? selection.focusNode : selection.anchorNode).length;
- return {
- element: element,
- offset: offset,
- offsetAtBeginning: offset === 0,
- offsetAtEnd: nodeLen === offset
- }
- }
-})
-
-return {
- fromXML: function(xml, publisher) {
- return new Canvas(xml, publisher);
- }
-};
-
-});
\ No newline at end of file
+++ /dev/null
-define([
-'libs/chai',
-'libs/sinon',
-'modules/documentCanvas/canvas/canvas',
-'modules/documentCanvas/canvas/documentElement',
-'modules/documentCanvas/canvas/utils'
-], function(chai, sinon, canvas, documentElement, utils) {
-
-'use strict';
-
-var expect = chai.expect;
-
-
-describe('Canvas', function() {
-
-
-
- describe('ZWS', function() {
- var view, section, textElement;
-
- beforeEach(function() {
- var c = canvas.fromXML('<section></section>');
-
- section = c.doc();
- textElement = section.children()[0];
- view = c.view()[0];
- document.getElementsByTagName('body')[0].appendChild(view);
- });
-
- afterEach(function() {
- view.parentNode.removeChild(view);
- });
-
- var getTextContainerNode = function(textElement) {
- return textElement.dom().contents()[0];
- }
-
- it('is set automatically on all empty DocumentTextElements', function() {
- expect(getTextContainerNode(textElement).data).to.equal(utils.unicode.ZWS);
-
- var header = section.append({tag: 'header'}),
- newText = header.append({text: ''}),
- textNode = getTextContainerNode(textElement);
-
- expect(textNode.data).to.equal(utils.unicode.ZWS);
- });
-
- it('is added automatically when whole text gets deleted', function() {
- getTextContainerNode(textElement).data = '';
-
- window.setTimeout(function() {
- expect(getTextContainerNode(textElement).data).to.equal(utils.unicode.ZWS);
- }, 0)
-
- var header = section.append({tag: 'header'}),
- newText = header.append({text: 'Alice'}),
- textNode = getTextContainerNode(newText);
-
- expect(textNode.data).to.have.length('Alice'.length);
- textNode.data = '';
-
- window.setTimeout(function() {
- expect(textNode.data).to.equal(utils.unicode.ZWS);
- }, 0)
- });
- });
-
- describe('Internal HTML representation of a DocumentNodeElement', function() {
- it('is always a div tag', function() {
- ['section', 'header', 'span', 'aside', 'figure'].forEach(function(tagName) {
- var dom = canvas.fromXML('<' + tagName + '></' + tagName + '>').doc().dom();
- expect(dom.prop('tagName')).to.equal('DIV', tagName + ' is represented as div');
- });
- });
- it('has wlxml tag put into wlxml-tag attribute of its internal container', function() {
- var dom = canvas.fromXML('<section></section>').doc().dom();
- expect(dom.children('[document-element-content]').attr('wlxml-tag')).to.equal('section');
- });
- it('has wlxml class put into wlxml-class attribute of its internal containr, dots replaced with dashes', function() {
- var dom = canvas.fromXML('<section class="some.class"></section>').doc().dom();
- expect(dom.children('[document-element-content]').attr('wlxml-class')).to.equal('some-class');
- });
- });
-
- describe('Internal HTML representation of a DocumentTextElement', function() {
- it('is text node wrapped in a div with document-text-element attribute set', function() {
- var dom = canvas.fromXML('<section>Alice</section>').doc().children()[0].dom();
- expect(dom.prop('tagName')).to.equal('DIV');
- expect(dom.attr('document-text-element')).to.equal('');
- expect(dom.contents().length).to.equal(1);
- expect(dom.contents()[0].nodeType).to.equal(Node.TEXT_NODE);
- expect($(dom.contents()[0]).text()).to.equal('Alice');
- });
- });
-
- describe('basic properties', function() {
- it('renders empty document when canvas created from empty XML', function() {
- var c = canvas.fromXML('');
- expect(c.doc()).to.equal(null);
- });
-
- it('gives access to its document root node', function() {
- var c = canvas.fromXML('<section></section>');
- expect(c.doc().getWlxmlTag()).to.equal('section');
- });
-
- describe('root element', function() {
- it('has no parent', function() {
- var c = canvas.fromXML('<section></section>');
- expect(c.doc().parent()).to.be.null;
- });
- });
-
- describe('DocumentTextElement', function() {
- it('can have its content set', function() {
- var c = canvas.fromXML('<section>Alice</section>'),
- root = c.doc(),
- text = root.children()[0];
-
- text.setText('a cat');
- expect(root.children()[0].getText()).to.equal('a cat');
- });
- });
-
- describe('DocumentNodeElement', function() {
- it('knows index of its child', function() {
- var c = canvas.fromXML('<section><div></div><header></header><span></span></section>'),
- root = c.doc(),
- child = root.children()[1];
- expect(root.childIndex(child)).to.equal(1);
- });
-
- it('knows WLXML tag it renders', function(){
- var c = canvas.fromXML('<section></section>'),
- section = c.doc();
- expect(section.getWlxmlTag()).to.equal('section', 'initial tag is section');
- section.setWlxmlTag('header');
- expect(section.getWlxmlTag()).to.equal('header', 'tag is changed to header');
- });
-
- it('knows WLXML class of a WLXML tag it renders', function(){
- var c = canvas.fromXML('<section class="some.class.A"></section>'),
- section = c.doc();
- expect(section.getWlxmlClass()).to.equal('some.class.A');
- section.setWlxmlClass('some.class.B');
- expect(section.getWlxmlClass()).to.equal('some.class.B');
- section.setWlxmlClass(null);
- expect(section.getWlxmlClass()).to.be.undefined;
- });
-
-
-
- describe('element has meta attributes', function() {
- it('can change its meta attributes', function() {
- var c = canvas.fromXML('<section><span class="uri" meta-uri="someuri"></span></section>'),
- span = c.doc().children()[0];
-
- expect(span.getWlxmlMetaAttr('uri')).to.equal('someuri');
- span.setWlxmlMetaAttr('uri', 'otheruri');
- expect(span.getWlxmlMetaAttr('uri')).to.equal('otheruri');
- });
-
- it('changes its meta attributes with class change', function() {
- var c = canvas.fromXML('<section><span class="uri" meta-uri="someuri"></span></section>'),
- span = c.doc().children()[0];
-
- expect(span.getWlxmlMetaAttr('uri')).to.equal('someuri');
- span.setWlxmlClass('author');
- expect(span.getWlxmlMetaAttr('uri')).to.be.undefined;
- });
-
- it('keeps meta attribute value on class change if a new class has this attribute', function() {
- var c = canvas.fromXML('<section><span class="uri" meta-uri="someuri"></span></section>'),
- span = c.doc().children()[0];
- span.setWlxmlClass('uri.some.subclass');
- expect(span.getWlxmlMetaAttr('uri')).to.equal('someuri');
- });
- });
- });
-
- it('returns DocumentNodeElement instance from HTMLElement', function() {
- var c = canvas.fromXML('<section></section>'),
- htmlElement = c.doc().dom().get(0),
- element = c.getDocumentElement(htmlElement);
- expect(element).to.be.instanceOf(documentElement.DocumentNodeElement);
- expect(element.sameNode(c.doc()));
- });
-
- it('returns DocumentTextElement instance from Text Node', function() {
- var c = canvas.fromXML('<section>Alice</section>'),
- aliceElement = c.doc().children()[0],
- textNode = aliceElement.dom().contents()[0],
- element = c.getDocumentElement(textNode);
-
- expect(textNode.nodeType).to.equal(Node.TEXT_NODE, 'text node selected');
- expect($(textNode).text()).to.equal('Alice');
-
- expect(element).to.be.instanceOf(documentElement.DocumentTextElement);
- expect(element.sameNode(c.doc().children()[0]));
- });
- });
-
-
-
- describe('document representation api', function() {
- describe('document root element', function() {
- var c = canvas.fromXML('<section></section>');
- it('exists', function() {
- expect(c.doc()).to.be.instanceOf(documentElement.DocumentElement);
- });
- it('is of type DocumentNodeElement', function() {
- expect(c.doc()).to.be.instanceOf(documentElement.DocumentNodeElement);
- });
- });
-
- describe('DocumentElements comparison', function() {
- it('reports dwo DocumentElements to be the same when they represent the same wlxml document element', function() {
- var c = canvas.fromXML('<section><div></div><div></div></section>'),
- first_div1 = c.doc().children()[0],
- first_div2 = c.doc().children()[0],
- second_div = c.doc().children()[1];
- expect(first_div1.sameNode(first_div1)).to.be.true;
- expect(first_div1.sameNode(first_div2)).to.be.true;
- expect(first_div1.sameNode(second_div)).to.be.false;
- });
- });
-
- describe('traversing', function() {
- it('reports element nodes', function() {
- var c = canvas.fromXML('<section><div></div></section>'),
- children = c.doc().children();
- expect(children.length).to.equal(1);
- expect(children[0]).to.be.instanceOf(documentElement.DocumentNodeElement);
-
- c = canvas.fromXML('<section><div></div><div></div></section>'),
- children = c.doc().children();
- expect(children.length).to.equal(2);
- expect(children[0]).to.be.instanceOf(documentElement.DocumentNodeElement);
- expect(children[1]).to.be.instanceOf(documentElement.DocumentNodeElement);
- });
-
- it('reports text nodes', function() {
- var c = canvas.fromXML('<section>Alice</section>'),
- children = c.doc().children();
- expect(children.length).to.equal(1);
- expect(children[0]).to.be.instanceOf(documentElement.DocumentTextElement);
- });
-
- describe('accessing parents', function() {
- it('returns DocumentNodeElement representing parent in wlxml document as DocumentNodeElement parent', function() {
- var c = canvas.fromXML('<section><div></div></section>'),
- div = c.doc().children()[0];
- expect(div.parent().sameNode(c.doc())).to.be.true;
- });
- it('returns DocumentNodeElement representing parent in wlxml document as DocumentTextElement parent', function() {
- var c = canvas.fromXML('<section>Alice</section>'),
- text = c.doc().children()[0];
- expect(text.parent().sameNode(c.doc())).to.be.true;
- });
- });
-
- describe('accessing sibling parents of two elements', function() {
- it('returns elements themself if they have direct common parent', function() {
- var c = canvas.fromXML('<section>\
- <div>\
- <div>A</div>\
- <div>B</div>\
- </div>\
- </section>'),
- section = c.doc(),
- wrappingDiv = c.doc().children()[0],
- divA = wrappingDiv.children()[0],
- divB = wrappingDiv.children()[1];
-
- var siblingParents = c.getSiblingParents({element1: divA, element2: divB});
-
- expect(siblingParents.element1.sameNode(divA)).to.equal(true, 'divA');
- expect(siblingParents.element2.sameNode(divB)).to.equal(true, 'divB');
- });
-
- it('returns sibling parents - example 1', function() {
- var c = canvas.fromXML('<section>Alice <span>has a cat</span></section>'),
- section = c.doc(),
- aliceText = section.children()[0],
- span = section.children()[1],
- spanText = span.children()[0];
-
- var siblingParents = c.getSiblingParents({element1: aliceText, element2: spanText});
-
- expect(siblingParents.element1.sameNode(aliceText)).to.equal(true, 'aliceText');
- expect(siblingParents.element2.sameNode(span)).to.equal(true, 'span');
- });
- })
-
- describe('free text handling', function() {
- it('sees free text', function() {
- var c = canvas.fromXML('<section>Alice <span>has</span> a cat</section>'),
- children = c.doc().children();
- expect(children.length).to.equal(3);
- expect(children[0]).to.be.instanceOf(documentElement.DocumentTextElement);
- expect(children[1]).to.be.instanceOf(documentElement.DocumentNodeElement);
- expect(children[2]).to.be.instanceOf(documentElement.DocumentTextElement);
- });
- });
-
- describe('empty nodes handling', function() {
- it('says empty element node from XML source has one empty DocumentTextElement', function() {
- var c = canvas.fromXML('<section></section>');
- expect(c.doc().children()).to.have.length(1);
- expect(c.doc().children()[0].getText()).to.equal('');
- });
-
- it('allows creation of an empty element node', function() {
- var c = canvas.fromXML('<section></section>'),
- section = c.doc(),
- header = section.append({tag: 'header'});
- expect(header.children()).to.have.length(0);
- });
- });
-
- describe('white characters handling', function() {
-
- it('says element node with one space has one DocumentTextElement', function() {
- var c = canvas.fromXML('<section> </section>');
- expect(c.doc().children().length).to.equal(1);
- expect(c.doc().children()[0]).to.be.instanceOf(documentElement.DocumentTextElement);
- expect(c.doc().children()[0].getText()).to.equal(' ');
- });
- it('ignores white space surrounding block elements', function() {
- var c = canvas.fromXML('<section> <div></div> </section>');
- var children = c.doc().children();
- expect(children.length).to.equal(1);
- expect(children[0]).to.be.instanceOf(documentElement.DocumentNodeElement);
- });
- it('ignores white space between block elements', function() {
- var c = canvas.fromXML('<section><div></div> <div></div></section>');
- var children = c.doc().children();
- expect(children.length === 2);
- [0,1].forEach(function(idx) {
- expect(children[idx]).to.be.instanceOf(documentElement.DocumentNodeElement);
- });
- });
-
- it('trims white space from the beginning and the end of the block elements', function() {
- var c = canvas.fromXML('<section> Alice <span>has</span> a cat </section>');
- expect(c.doc().children()[0].getText()).to.equal('Alice ');
- expect(c.doc().children()[2].getText()).to.equal(' a cat');
- });
-
- it('normalizes string of white characters to one space at the inline element boundries', function() {
- var c = canvas.fromXML('<span> Alice has a cat </span>');
- expect(c.doc().children()[0].getText()).to.equal(' Alice has a cat ');
- });
-
- it('normalizes string of white characters to one space before inline element', function() {
- var c = canvas.fromXML('<div>Alice has <span>a cat</span></div>');
- expect(c.doc().children()[0].getText()).to.equal('Alice has ');
- });
-
- it('normalizes string of white characters to one space after inline element', function() {
- var c = canvas.fromXML('<div>Alice has <span>a</span> cat</div>');
- expect(c.doc().children()[2].getText()).to.equal(' cat');
- });
- });
-
- describe('getting vertically first text element', function() {
- it('returns the first child if it\'s text element, ignores metadata', function() {
- var c = canvas.fromXML('<section><metadata><dc:author>author</dc:author></metadata>Alice<div>has</div>a cat</section>'),
- first = c.doc().getVerticallyFirstTextElement();
-
- expect(first.sameNode(c.doc().children()[1])).to.be.true;
- });
-
- it('looks recursively inside node elements if they precede text element', function() {
- var c = canvas.fromXML('\
- <section>\
- <div>\
- <div>\
- Alice\
- </div>\
- </div>\
- Some text\
- </section>'),
- textAlice = c.doc().children()[0].children()[0].children()[0],
- first = c.doc().getVerticallyFirstTextElement();
-
- expect(textAlice).to.be.instanceOf(documentElement.DocumentTextElement);
- expect(first.sameNode(textAlice)).to.be.true;
- });
- });
- });
-
- describe('manipulation api', function() {
-
- describe('Basic Element inserting', function() {
- it('can put new NodeElement at the end', function() {
- var c = canvas.fromXML('<section><div></div></section>'),
- appended = c.doc().append({tag: 'header', klass: 'some.class'}),
- children = c.doc().children();
-
- expect(children.length).to.equal(2);
- expect(children[1].sameNode(appended)).to.be.true;
- });
-
- it('can put new TextElement at the end', function() {
- var c = canvas.fromXML('<section><div><div></section>'),
- appended = c.doc().append({text: 'Alice'}),
- children = c.doc().children();
-
- expect(children.length).to.equal(2);
- expect(children[1].sameNode(appended)).to.be.true;
- expect(children[1].getText()).to.equal('Alice');
- });
-
- it('can put new NodeElement at the beginning', function() {
- var c = canvas.fromXML('<section><div></div></section>'),
- prepended = c.doc().prepend({tag: 'header', klass: 'some.class'}),
- children = c.doc().children();
-
- expect(children).to.have.length(2);
- expect(children[0].sameNode(prepended)).to.be.true;
- });
-
- it('can put new TextElement at the beginning', function() {
- var c = canvas.fromXML('<section><div></div></section>'),
- prepended = c.doc().prepend({text: 'Alice'}),
- children = c.doc().children();
-
- expect(children).to.have.length(2)
- expect(children[0].sameNode(prepended)).to.be.true;
- expect(children[0].getText()).to.equal('Alice');
- });
-
- it('can put new NodeElement after another NodeElement', function() {
- var c = canvas.fromXML('<section><div></div></section>'),
- div = c.doc().children()[0],
- added = div.after({tag: 'header', klass: 'some.class'}),
- children = c.doc().children();
- expect(children.length).to.equal(2);
- expect(children[1].sameNode(added)).to.be.true;
- });
-
- it('can put new Nodeelement before another element', function() {
- var c = canvas.fromXML('<section><div></div></section>'),
- div = c.doc().children()[0],
- added = div.before({tag: 'header', klass: 'some.class'}),
- children = c.doc().children();
- expect(children.length).to.equal(2);
- expect(children[0].sameNode(added)).to.be.true;
- });
-
- it('can put new DocumentNodeElement after DocumentTextElement', function() {
- var c = canvas.fromXML('<section>Alice</section>'),
- text = c.doc().children()[0],
- added = text.after({tag: 'p'}),
- children = c.doc().children();
-
- expect(children.length).to.equal(2);
- expect(children[0]).to.be.instanceOf(documentElement.DocumentTextElement);
- expect(children[0].getText()).to.equal('Alice');
- expect(children[1]).to.be.instanceOf(documentElement.DocumentNodeElement);
- expect(children[1].sameNode(added)).to.be.true;
- });
- it('can put new DocumentNodeElement before DocumentTextElement', function() {
- var c = canvas.fromXML('<section>Alice</section>'),
- text = c.doc().children()[0],
- added = text.before({tag: 'p'}),
- children = c.doc().children();
-
- expect(children.length).to.equal(2);
- expect(children[0]).to.be.instanceOf(documentElement.DocumentNodeElement);
- expect(children[0].sameNode(added)).to.be.true;
- expect(children[1]).to.be.instanceOf(documentElement.DocumentTextElement);
- expect(children[1].getText()).to.equal('Alice');
- });
-
- it('can divide DocumentTextElement with a new DocumentNodeElement', function() {
- var c = canvas.fromXML('<section>Alice has a cat</section>'),
- section = c.doc(),
- text = section.children()[0];
-
- var returned = text.divide({tag: 'aside', klass: 'footnote', offset: 5}),
- sectionChildren = section.children(),
- lhsText = sectionChildren[0],
- rhsText = sectionChildren[2];
-
- expect(lhsText.getText()).to.equal('Alice');
- expect(returned.sameNode(sectionChildren[1]));
- expect(rhsText.getText()).to.equal(' has a cat');
- });
-
- it('treats dividing DocumentTextElement at the very end as appending after it', function() {
- var c = canvas.fromXML('<section>Alice has a cat</section>'),
- section = c.doc(),
- text = section.children()[0];
-
- var returned = text.divide({tag: 'aside', klass: 'footnote', offset: 15}),
- sectionChildren = section.children(),
- textElement = sectionChildren[0],
- nodeElement = sectionChildren[1];
-
- expect(sectionChildren.length).to.equal(2);
- expect(textElement.getText()).to.equal('Alice has a cat');
- expect(returned.sameNode(nodeElement)).to.be.true;
- expect(nodeElement.getWlxmlTag()).to.equal('aside');
- });
-
- it('treats dividing DocumentTextElement at the very beginning as appending before it', function() {
- var c = canvas.fromXML('<section>Alice has a cat</section>'),
- section = c.doc(),
- text = section.children()[0];
-
- var returned = text.divide({tag: 'aside', klass: 'footnote', offset: 0}),
- sectionChildren = section.children(),
- nodeElement = sectionChildren[0],
- textElement = sectionChildren[1];
-
- expect(sectionChildren.length).to.equal(2);
- expect(textElement.getText()).to.equal('Alice has a cat');
- expect(returned.sameNode(nodeElement)).to.be.true;
- expect(nodeElement.getWlxmlTag()).to.equal('aside');
- });
- });
-
- describe('Removing elements', function() {
- it('merges left and right DocumentTextElement sibling of a detached DocumentNodeElement', function() {
- var c = canvas.fromXML('<section>Alice<div>has</div>a cat</section>'),
- section = c.doc(),
- div = section.children()[1];
-
- div.detach();
-
- var sectionChildren = section.children(),
- textElement = sectionChildren[0];
-
- expect(sectionChildren).to.have.length(1);
- expect(textElement.getText()).to.equal('Alicea cat');
- });
- });
-
- describe('Splitting text', function() {
-
- it('splits DocumentTextElement\'s parent into two DocumentNodeElements of the same type', function() {
- var c = canvas.fromXML('<section><header>Some header</header></section>'),
- section = c.doc(),
- text = section.children()[0].children()[0];
-
- var returnedValue = text.split({offset: 5});
- expect(section.children().length).to.equal(2, 'section has two children');
-
- var header1 = section.children()[0];
- var header2 = section.children()[1];
-
- expect(header1.getWlxmlTag()).to.equal('header', 'first section child represents wlxml header');
- expect(header1.children().length).to.equal(1, 'first header has one text child');
- expect(header1.children()[0].getText()).to.equal('Some ', 'first header has correct content');
- expect(header2.getWlxmlTag()).to.equal('header', 'second section child represents wlxml header');
- expect(header2.children().length).to.equal(1, 'second header has one text child');
- expect(header2.children()[0].getText()).to.equal('header', 'second header has correct content');
-
- expect(returnedValue.first.sameNode(header1)).to.equal(true, 'first node returnde');
- expect(returnedValue.second.sameNode(header2)).to.equal(true, 'second node returned');
- });
-
- it('leaves empty copy of DocumentNodeElement if splitting at the very beginning', function() {
- var c = canvas.fromXML('<section><header>Some header</header></section>'),
- section = c.doc(),
- text = section.children()[0].children()[0];
-
- text.split({offset: 0});
-
- var header1 = section.children()[0];
- var header2 = section.children()[1];
-
- expect(header1.children().length).to.equal(0);
- expect(header2.children()[0].getText()).to.equal('Some header');
- });
-
- it('leaves empty copy of DocumentNodeElement if splitting at the very end', function() {
- var c = canvas.fromXML('<section><header>Some header</header></section>'),
- section = c.doc(),
- text = section.children()[0].children()[0];
-
- text.split({offset: 11});
-
- var header1 = section.children()[0];
- var header2 = section.children()[1];
-
- expect(header1.children()[0].getText()).to.equal('Some header');
- expect(header2.children().length).to.equal(0);
- });
-
- it('keeps DocumentTextElement\'s parent\'s children elements intact', function() {
- var c = canvas.fromXML('\
- <section>\
- <header>\
- A <span>fancy</span> and <span>nice</span> header\
- </header>\
- </section>'),
- section = c.doc(),
- header = section.children()[0],
- textAnd = header.children()[2];
-
- textAnd.split({offset: 2});
-
- var sectionChildren = section.children();
- expect(sectionChildren.length).to.equal(2, 'Section has two children');
- expect(sectionChildren[0].getWlxmlTag()).to.equal('header', 'First section element is a wlxml header');
- expect(sectionChildren[1].getWlxmlTag()).to.equal('header', 'Second section element is a wlxml header');
-
- var firstHeaderChildren = sectionChildren[0].children();
- expect(firstHeaderChildren.length).to.equal(3, 'First header has three children');
- expect(firstHeaderChildren[0].getText()).to.equal('A ', 'First header starts with a text');
- expect(firstHeaderChildren[1].getWlxmlTag()).to.equal('span', 'First header has span in the middle');
- expect(firstHeaderChildren[2].getText()).to.equal(' a', 'First header ends with text');
-
- var secondHeaderChildren = sectionChildren[1].children();
- expect(secondHeaderChildren.length).to.equal(3, 'Second header has three children');
- expect(secondHeaderChildren[0].getText()).to.equal('nd ', 'Second header starts with text');
- expect(secondHeaderChildren[1].getWlxmlTag()).to.equal('span', 'Second header has span in the middle');
- expect(secondHeaderChildren[2].getText()).to.equal(' header', 'Second header ends with text');
- });
- });
-
- describe('wrapping', function() {
- it('wraps DocumentNodeElement', function() {
- var c = canvas.fromXML('<section><div></div></section>'),
- div = c.doc().children()[0];
-
- var returned = div.wrapWithNodeElement({tag: 'header', klass: 'some.class'}),
- parent = div.parent(),
- parent2 = c.doc().children()[0];
-
- expect(returned.sameNode(parent)).to.be.true;
- expect(returned.sameNode(parent2)).to.be.true;
- expect(returned.getWlxmlTag()).to.equal('header');
- expect(returned.getWlxmlClass()).to.equal('some.class');
- });
- it('wraps DocumentTextElement', function() {
- var c = canvas.fromXML('<section>Alice</section>'),
- text = c.doc().children()[0];
-
- var returned = text.wrapWithNodeElement({tag: 'header', klass: 'some.class'}),
- parent = text.parent(),
- parent2 = c.doc().children()[0];
-
- expect(returned.sameNode(parent)).to.be.true;
- expect(returned.sameNode(parent2)).to.be.true;
- expect(returned.getWlxmlTag()).to.equal('header');
- expect(returned.getWlxmlClass()).to.equal('some.class');
- });
-
- describe('wrapping part of DocumentTextElement', function() {
- [{start: 5, end: 12}, {start: 12, end: 5}].forEach(function(offsets) {
- it('wraps in the middle ' + offsets.start + '/' + offsets.end, function() {
- var c = canvas.fromXML('<section>Alice has a cat</section>'),
- text = c.doc().children()[0];
-
- var returned = text.wrapWithNodeElement({tag: 'header', klass: 'some.class', start: offsets.start, end: offsets.end}),
- children = c.doc().children();
-
- expect(children.length).to.equal(3);
-
- expect(children[0]).to.be.instanceOf(documentElement.DocumentTextElement);
- expect(children[0].getText()).to.equal('Alice');
-
- expect(children[1].sameNode(returned)).to.be.true;
- expect(returned.getWlxmlTag()).to.equal('header');
- expect(returned.getWlxmlClass()).to.equal('some.class');
- expect(children[1].children().length).to.equal(1);
- expect(children[1].children()[0].getText()).to.equal(' has a ');
-
- expect(children[2]).to.be.instanceOf(documentElement.DocumentTextElement);
- expect(children[2].getText()).to.equal('cat');
- });
- });
-
- it('wraps whole text inside DocumentTextElement if offsets span entire content', function() {
- var c = canvas.fromXML('<section>Alice has a cat</section>'),
- text = c.doc().children()[0];
-
- var returned = text.wrapWithNodeElement({tag: 'header', klass: 'some.class', start: 0, end: 15}),
- children = c.doc().children();
-
- expect(children.length).to.equal(1);
- expect(children[0]).to.be.instanceOf(documentElement.DocumentNodeElement);
- expect(children[0].children()[0].getText()).to.equal('Alice has a cat');
- });
- });
-
- it('wraps text spanning multiple sibling DocumentTextNodes', function() {
- var c = canvas.fromXML('<section>Alice has a <span>small</span> cat</section>'),
- section = c.doc(),
- wrapper = c.wrapText({
- inside: section,
- _with: {tag: 'span', klass: 'some.class'},
- offsetStart: 6,
- offsetEnd: 4,
- textNodeIdx: [0,2]
- });
-
- expect(section.children().length).to.equal(2);
- expect(section.children()[0]).to.be.instanceOf(documentElement.DocumentTextElement);
- expect(section.children()[0].getText()).to.equal('Alice ');
-
- var wrapper2 = section.children()[1];
- expect(wrapper2.sameNode(wrapper)).to.be.true;
-
- var wrapperChildren = wrapper.children();
- expect(wrapperChildren.length).to.equal(3);
- expect(wrapperChildren[0].getText()).to.equal('has a ');
-
- expect(wrapperChildren[1]).to.be.instanceOf(documentElement.DocumentNodeElement);
- expect(wrapperChildren[1].children().length).to.equal(1);
- expect(wrapperChildren[1].children()[0].getText()).to.equal('small');
-
- expect(wrapperChildren[2].getText()).to.equal(' cat');
- });
-
- it('wraps multiple sibling Elements', function() {
- var c = canvas.fromXML('<section>Alice<div>has</div><div>a cat</div></section>'),
- section = c.doc(),
- aliceText = section.children()[0],
- firstDiv = section.children()[1],
- lastDiv = section.children()[section.children().length -1];
-
- var returned = c.wrapElements({
- element1: aliceText,
- element2: lastDiv,
- _with: {tag: 'header'}
- });
-
- var sectionChildren = section.children(),
- header = sectionChildren[0],
- headerChildren = header.children();
-
- expect(sectionChildren).to.have.length(1);
- expect(header.sameNode(returned)).to.equal(true, 'wrapper returned');
- expect(headerChildren).to.have.length(3);
- expect(headerChildren[0].sameNode(aliceText)).to.equal(true, 'first node wrapped');
- expect(headerChildren[1].sameNode(firstDiv)).to.equal(true, 'second node wrapped');
- expect(headerChildren[2].sameNode(lastDiv)).to.equal(true, 'third node wrapped');
- });
- it('wraps multiple sibling Elements - middle case', function() {
- var c = canvas.fromXML('<section><div></div>div></div><div></div><div></div></section>'),
- section = c.doc(),
- div1 = section.children()[0],
- div2 = section.children()[1],
- div3 = section.children()[2],
- div4 = section.children()[3];
-
- var returned = c.wrapElements({
- element1: div2,
- element2: div3,
- _with: {tag: 'header'}
- });
-
- var sectionChildren = section.children(),
- header = sectionChildren[1],
- headerChildren = header.children();
-
- expect(sectionChildren).to.have.length(3);
- expect(headerChildren).to.have.length(2);
- expect(headerChildren[0].sameNode(div2)).to.equal(true, 'first node wrapped');
- expect(headerChildren[1].sameNode(div3)).to.equal(true, 'second node wrapped');
- });
- });
-
- describe('unwrapping DocumentTextElement from its parent DocumentNodeElement if it\'s its only child', function() {
- it('unwraps text element from its parent and stays between its old parent siblings', function() {
- var c = canvas.fromXML('<section><div>Alice</div><div>has</div><div>a cat</div></section>'),
- section = c.doc(),
- sectionChildren = section.children(),
- divAlice = sectionChildren[0],
- divHas = sectionChildren[1],
- textHas = divHas.children()[0],
- divCat = sectionChildren[2];
-
- var newTextContainer = textHas.unwrap(),
- sectionChildren = section.children();
-
- expect(sectionChildren[0].sameNode(divAlice)).to.equal(true, 'divAlice ok');
- expect(newTextContainer.sameNode(section)).to.equal(true, 'unwrap returns new text parent DocumentNodeElement');
- expect(sectionChildren[1].getText()).to.equal('has');
- expect(sectionChildren[2].sameNode(divCat)).to.equal(true, 'divCat ok');
-
- });
- it('unwraps and join with its old parent adjacent text elements ', function() {
- var c = canvas.fromXML('<section>Alice <span>has a</span> cat</section>'),
- section = c.doc(),
- text = section.children()[1].children()[0];
-
- var newTextContainer = text.unwrap();
-
- expect(section.children().length).to.equal(1, 'section has one child');
- expect(section.children()[0].getText()).to.equal('Alice has a cat');
- expect(newTextContainer.sameNode(c.doc())).to.equal(true, 'unwrap returns new text parent DocumentNodeElement');
- });
-
- it('unwraps text element from its parent - first child case', function() {
- var c = canvas.fromXML('<section><span class="uri">Some</span>text</section>'),
- section = c.doc(),
- span = section.children()[0];
-
- span.children()[0].unwrap();
-
- var sectionChildren = section.children();
-
- expect(sectionChildren).to.have.length(1);
- expect(sectionChildren[0].getText()).to.equal('Sometext');
- });
- });
-
- describe('unwrapping the whole content of a DocumentNodeElement', function() {
- it('removes a DocumentNodeElement but keeps its content', function() {
- var c = canvas.fromXML('<section><div>Alice has<span>a</span> cat</div></section>'),
- section = c.doc(),
- div = c.doc().children()[0],
- span = div.children()[1];
-
- var range = div.unwrapContents(),
- sectionChildren = section.children();
-
- expect(sectionChildren).to.have.length(3);
- expect(sectionChildren[0].getText()).to.equal('Alice has');
- expect(sectionChildren[1].sameNode(span)).to.equal(true, 'span ok');
- expect(sectionChildren[2].getText()).to.equal(' cat');
-
- expect(range.element1.sameNode(sectionChildren[0])).to.equal(true, 'range start ok');
- expect(range.element2.sameNode(sectionChildren[2])).to.equal(true, 'range end ok');
- });
- it('merges text elements on the boundries', function() {
- var c = canvas.fromXML('<section>Alice<div>has a <span>cat</span>!</div>!!</section>'),
- section = c.doc(),
- div = c.doc().children()[1],
- span = div.children()[1];
-
- var range = div.unwrapContents(),
- sectionChildren = section.children();
-
- expect(sectionChildren).to.have.length(3);
- expect(sectionChildren[0].getText()).to.equal('Alicehas a ');
- expect(sectionChildren[1].sameNode(span)).to.equal(true, 'span ok');
- expect(sectionChildren[2].getText()).to.equal('!!!');
-
- expect(range.element1.sameNode(sectionChildren[0])).to.equal(true, 'range start ok');
- expect(range.element2.sameNode(sectionChildren[2])).to.equal(true, 'range end ok');
- });
-
- it('merges text elements on the boundries - single child case', function() {
- var c = canvas.fromXML('<section>Alice <span>has</span> a cat</section>'),
- section = c.doc(),
- span = section.children()[1];
-
- var range = span.unwrapContents(),
- sectionChildren = section.children();
-
- expect(sectionChildren).to.have.length(1);
- expect(sectionChildren[0].getText()).to.equal('Alice has a cat');
- });
- });
-
- });
-
- describe('Lists api', function() {
- describe('creating lists', function() {
- it('allows creation of a list from existing sibling DocumentElements', function() {
- var c = canvas.fromXML('\
- <section>\
- Alice\
- <div>has</div>\
- a\
- <div>cat</div>\
- </section>'),
- section = c.doc(),
- textHas = section.children()[1],
- divA = section.children()[2]
-
- c.list.create({element1: textHas, element2: divA});
-
- expect(section.children().length).to.equal(3, 'section has three child elements');
-
- var child1 = section.children()[0],
- list = section.children()[1],
- child3 = section.children()[2];
-
- expect(child1.getText()).to.equal('Alice');
- expect(list.is('list')).to.equal(true, 'second child is a list');
- expect(list.children().length).to.equal(2, 'list contains two elements');
- list.children().forEach(function(child) {
- expect(child.getWlxmlClass()).to.equal('item', 'list childs have wlxml class of item');
- });
- expect(child3.children()[0].getText()).to.equal('cat');
- });
-
- it('allows creating nested list from existing sibling list items', function() {
- var c = canvas.fromXML('\
- <section>\
- <div class="list-items">\
- <div class="item">A</div>\
- <div class="item">B</div>\
- <div class="item">C</div>\
- <div class="item">D</div>\
- </div>\
- </section>'),
- outerList = c.doc().children()[0],
- itemB = outerList.children()[1],
- itemC = outerList.children()[2];
-
-
- c.list.create({element1: itemB, element2: itemC});
-
- var outerListItems = outerList.children(),
- innerList = outerListItems[1].children()[0],
- innerListItems = innerList.children();
-
- expect(outerListItems.length).to.equal(3, 'outer list has three items');
- expect(outerListItems[0].children()[0].getText()).to.equal('A', 'first outer item ok');
- expect(outerListItems[1].getWlxmlClass()).to.equal('item', 'inner list is wrapped by item element');
-
- expect(innerList.is('list')).to.equal(true, 'inner list created');
- expect(innerListItems.length).to.equal(2, 'inner list has two items');
- expect(innerListItems[0].children()[0].getText()).to.equal('B', 'first inner item ok');
- expect(innerListItems[1].children()[0].getText()).to.equal('C', 'second inner item ok');
-
- expect(outerListItems[2].children()[0].getText()).to.equal('D', 'last outer item ok');
-
- });
-
- });
-
- describe('extracting list items', function() {
- it('creates two lists with extracted items in the middle if extracting from the middle of the list', function() {
- var c = canvas.fromXML('\
- <section>\
- <div class="list.items">\
- <div class="item">0</div>\
- <div class="item">1</div>\
- <div class="item">2</div>\
- <div class="item">3</div>\
- </div>\
- </section>'),
- list = c.doc().children()[0],
- item1 = list.children()[1],
- item2 = list.children()[2];
-
- c.list.extractItems({element1: item1, element2: item2});
-
- var section = c.doc(),
- list1 = section.children()[0],
- oldItem1 = section.children()[1],
- oldItem2 = section.children()[2],
- list2 = section.children()[3];
-
- expect(section.children().length).to.equal(4, 'section contains four children');
-
- expect(list1.is('list')).to.equal(true, 'first section child is a list');
- expect(list1.children().length).to.equal(1, 'first list has one child');
- expect(list1.children()[0].children()[0].getText()).to.equal('0', 'first item of the first list is a first item of the original list');
-
- expect(oldItem1.children()[0].getText()).to.equal('1', 'first item got extracted');
- expect(oldItem1.getWlxmlClass() === undefined).to.equal(true, 'first extracted element has no wlxml class');
-
- expect(oldItem2.children()[0].getText()).to.equal('2', 'second item got extracted');
- expect(oldItem2.getWlxmlClass() === undefined).to.equal(true, 'second extracted element has no wlxml class');
-
- expect(list2.is('list')).to.equal(true, 'last section child is a list');
- expect(list2.children().length).to.equal(1, 'second list has one child');
- expect(list2.children()[0].children()[0].getText()).to.equal('3', 'first item of the second list is a last item of the original list');
- });
-
- it('puts extracted items above the list if starting item is the first one', function() {
- var c = canvas.fromXML('\
- <section>\
- <div class="list.items">\
- <div class="item">0</div>\
- <div class="item">1</div>\
- <div class="item">2</div>\
- </div>\
- </section>'),
- list = c.doc().children()[0],
- item1 = list.children()[0],
- item2 = list.children()[1],
- item3 = list.children()[2];
-
- c.list.extractItems({element1: item1, element2: item2});
-
- var section = c.doc(),
- oldItem1 = section.children()[0],
- oldItem2 = section.children()[1],
- newList = section.children()[2];
-
- expect(section.children().length).to.equal(3, 'section has three children');
- expect(oldItem1.children()[0].getText()).to.equal('0', 'first item extracted');
- expect(oldItem2.children()[0].getText()).to.equal('1', 'second item extracted');
- expect(newList.is('list')).to.equal(true, 'list lies below extracted item');
- expect(newList.children().length).to.equal(1, 'list has now one child');
- });
-
- it('puts extracted items below the list if ending item is the last one', function() {
- var c = canvas.fromXML('\
- <section>\
- <div class="list.items">\
- <div class="item">0</div>\
- <div class="item">1</div>\
- <div class="item">2</div>\
- </div>\
- </section>'),
- list = c.doc().children()[0],
- item1 = list.children()[0],
- item2 = list.children()[1],
- item3 = list.children()[2];
-
- c.list.extractItems({element1: item2, element2: item3});
-
- var section = c.doc(),
- oldItem1 = section.children()[1],
- oldItem2 = section.children()[2],
- newList = section.children()[0];
-
- expect(section.children().length).to.equal(3, 'section has three children');
- expect(oldItem1.children()[0].getText()).to.equal('1', 'first item extracted');
- expect(oldItem2.children()[0].getText()).to.equal('2', 'second item extracted');
- expect(newList.is('list')).to.equal(true, 'list lies above extracted item');
- expect(newList.children().length).to.equal(1, 'list has now one child');
- });
-
- it('removes list if all its items are extracted', function() {
- var c = canvas.fromXML('\
- <section>\
- <div class="list.items">\
- <div class="item">some item</div>\
- <div class="item">some item 2</div>\
- </div>\
- </section>'),
- list = c.doc().children()[0],
- item1 = list.children()[0],
- item2 = list.children()[1];
-
- c.list.extractItems({element1: item1, element2: item2});
-
- var section = c.doc(),
- list1 = section.children()[0],
- oldItem1 = section.children()[0],
- oldItem2 = section.children()[1];
-
- expect(section.children().length).to.equal(2, 'section contains two children');
- expect(oldItem1.children()[0].getText()).to.equal('some item');
- expect(oldItem2.children()[0].getText()).to.equal('some item 2');
- });
-
- it('creates two lists with extracted items in the middle if extracting from the middle of the list - nested case' , function() {
- var c = canvas.fromXML('\
- <section>\
- <div class="list.items">\
- <div class="item">0</div>\
- <div class="item">\
- <div class="list.items">\
- <div class="item">1.1</div>\
- <div class="item">1.2</div>\
- <div class="item">1.3</div>\
- </div>\
- </div>\
- <div class="item">2</div>\
- </div>\
- </section>'),
- list = c.doc().children()[0],
- nestedList = list.children()[1].children()[0],
- nestedListItem = nestedList.children()[1];
-
- c.list.extractItems({element1: nestedListItem, element2: nestedListItem});
-
- var section = c.doc(),
- list = section.children()[0],
- item1 = list.children()[0],
- item2 = list.children()[1], //
- item3 = list.children()[2],
- item4 = list.children()[3], //
- item5 = list.children()[4],
- nestedList1 = item2.children()[0],
- nestedList2 = item4.children()[0];
-
- expect(list.children().length).to.equal(5, 'top list has five items');
-
- expect(item1.children()[0].getText()).to.equal('0', 'first item ok');
-
- expect(item2.getWlxmlClass()).to.equal('item', 'first nested list is still wrapped in item element');
- expect(nestedList1.children().length).to.equal(1, 'first nested list is left with one child');
- expect(nestedList1.children()[0].children()[0].getText()).to.equal('1.1', 'first nested list item left alone');
-
- expect(item3.children()[0].getText()).to.equal('1.2', 'third item ok');
-
- expect(item4.getWlxmlClass()).to.equal('item', 'second nested list is still wrapped in item element');
- expect(nestedList2.children().length).to.equal(1, 'second nested list is left with one child');
- expect(nestedList2.children()[0].children()[0].getText()).to.equal('1.3', 'second nested list item left alone');
-
- expect(item5.children()[0].getText()).to.equal('2', 'last item ok');
- });
-
- it('puts extracted items below the list if ending item is the last one - nested case' , function() {
- var c = canvas.fromXML('\
- <section>\
- <div class="list.items">\
- <div class="item">0</div>\
- <div class="item">\
- <div class="list.items">\
- <div class="item">1.1</div>\
- <div class="item">1.2</div>\
- <div class="item">1.3</div>\
- </div>\
- </div>\
- <div class="item">2</div>\
- </div>\
- </section>'),
- list = c.doc().children()[0],
- nestedList = list.children()[1].children()[0],
- nestedListItem1 = nestedList.children()[1],
- nestedListItem2 = nestedList.children()[2];
-
- c.list.extractItems({element1: nestedListItem1, element2: nestedListItem2});
-
- var section = c.doc(),
- list = section.children()[0],
- item1 = list.children()[0],
- item2 = list.children()[1],
- item3 = list.children()[2],
- item4 = list.children()[3],
- item5 = list.children()[4];
- nestedList = item2.children()[0];
-
- expect(list.children().length).to.equal(5, 'top list has five items');
- expect(item1.children()[0].getText()).to.equal('0', 'first item ok');
- expect(item2.getWlxmlClass()).to.equal('item', 'nested list is still wrapped in item element');
- expect(nestedList.children().length).to.equal(1, 'nested list is left with one child');
- expect(nestedList.children()[0].children()[0].getText()).to.equal('1.1', 'nested list item left alone');
- expect(item3.children()[0].getText()).to.equal('1.2', 'third item ok');
- expect(item4.children()[0].getText()).to.equal('1.3', 'fourth item ok');
- expect(item5.children()[0].getText()).to.equal('2', 'fifth item ok');
- });
-
- it('puts extracted items above the list if starting item is the first one - nested case' , function() {
- var c = canvas.fromXML('\
- <section>\
- <div class="list.items">\
- <div class="item">0</div>\
- <div class="item">\
- <div class="list.items">\
- <div class="item">1.1</div>\
- <div class="item">1.2</div>\
- <div class="item">1.3</div>\
- </div>\
- </div>\
- <div class="item">2</div>\
- </div>\
- </section>'),
- list = c.doc().children()[0],
- nestedList = list.children()[1].children()[0],
- nestedListItem1 = nestedList.children()[0],
- nestedListItem2 = nestedList.children()[1];
-
- c.list.extractItems({element1: nestedListItem1, element2: nestedListItem2});
-
- var section = c.doc(),
- list = section.children()[0],
- item1 = list.children()[0],
- item2 = list.children()[1],
- item3 = list.children()[2],
- item4 = list.children()[3],
- item5 = list.children()[4];
- nestedList = item4.children()[0];
-
- expect(list.children().length).to.equal(5, 'top list has five items');
- expect(item1.children()[0].getText()).to.equal('0', 'first item ok');
- expect(item2.children()[0].getText()).to.equal('1.1', 'second item ok');
- expect(item3.children()[0].getText()).to.equal('1.2', 'third item ok');
-
- expect(item4.getWlxmlClass()).to.equal('item', 'nested list is still wrapped in item element');
- expect(nestedList.children().length).to.equal(1, 'nested list is left with one child');
- expect(nestedList.children()[0].children()[0].getText()).to.equal('1.3', 'nested list item left alone');
- expect(item5.children()[0].getText()).to.equal('2', 'fifth item ok');
- });
-
- it('removes list if all its items are extracted - nested case', function() {
- var c = canvas.fromXML('\
- <section>\
- <div class="list.items">\
- <div class="item">0</div>\
- <div class="item">\
- <div class="list.items">\
- <div class="item">1.1</div>\
- <div class="item">1.2</div>\
- </div>\
- </div>\
- <div class="item">2</div>\
- </div>\
- </section>'),
- list = c.doc().children()[0],
- nestedList = list.children()[1].children()[0],
- nestedListItem1 = nestedList.children()[0],
- nestedListItem2 = nestedList.children()[1];
-
- c.list.extractItems({element1: nestedListItem1, element2: nestedListItem2});
-
- var section = c.doc(),
- list = section.children()[0],
- item1 = list.children()[0],
- item2 = list.children()[1],
- item3 = list.children()[2],
- item4 = list.children()[3];
-
- expect(list.children().length).to.equal(4, 'top list has four items');
- expect(item1.children()[0].getText()).to.equal('0', 'first item ok');
- expect(item2.children()[0].getText()).to.equal('1.1', 'second item ok');
- expect(item3.children()[0].getText()).to.equal('1.2', 'third item ok');
- expect(item4.children()[0].getText()).to.equal('2', 'fourth item ok');
- });
-
- it('extracts items out of outer most list when merge flag is set to false', function() {
- var c = canvas.fromXML('\
- <section>\
- <div class="list.items">\
- <div class="item">0</div>\
- <div class="item">\
- <div class="list.items">\
- <div class="item">1.1</div>\
- <div class="item">1.2</div>\
- </div>\
- </div>\
- <div class="item">2</div>\
- </div>\
- </section>'),
- section = c.doc(),
- list = section.children()[0],
- nestedList = list.children()[1].children()[0],
- nestedListItem = nestedList.children()[0];
-
- var test = c.list.extractItems({element1: nestedListItem, element2: nestedListItem, merge: false});
-
- expect(test).to.equal(true, 'extraction status ok');
-
- var sectionChildren = section.children(),
- extractedItem = sectionChildren[1];
-
- expect(sectionChildren.length).to.equal(3, 'section has three children');
- expect(sectionChildren[0].is('list')).to.equal(true, 'first child is a list');
-
- expect(extractedItem.getWlxmlTag()).to.equal('div', 'extracted item is a wlxml div');
- expect(extractedItem.getWlxmlClass()).to.equal(undefined, 'extracted item has no wlxml class');
- expect(extractedItem.children()[0].getText()).to.equal('1.1', 'extracted item ok');
- expect(sectionChildren[2].is('list')).to.equal(true, 'second child is a list');
- });
- });
- });
-
- });
-
- describe('Cursor', function() {
-
- var getSelection;
-
- var findTextNode = function(inside, text) {
- var nodes = inside.find(':not(iframe)').addBack().contents().filter(function() {
- return this.nodeType === Node.TEXT_NODE && this.data === text;
- });
- if(nodes.length)
- return nodes[0];
- return null;
- }
-
- beforeEach(function() {
- getSelection = sinon.stub(window, 'getSelection');
- });
-
- afterEach(function() {
- getSelection.restore();
- });
-
- it('returns position when browser selection collapsed', function() {
- var c = canvas.fromXML('<section>Alice has a cat</section>'),
- dom = c.doc().dom(),
- text = findTextNode(dom, 'Alice has a cat');
-
- expect(text.nodeType).to.equal(Node.TEXT_NODE, 'correct node selected');
- expect($(text).text()).to.equal('Alice has a cat');
-
- getSelection.returns({
- anchorNode: text,
- focusNode: text,
- anchorOffset: 5,
- focusOffset: 5,
- isCollapsed: true
- });
- var cursor = c.getCursor(),
- position = cursor.getPosition();
-
- expect(cursor.isSelecting()).to.equal(false, 'cursor is not selecting anything');
- expect(position.element.getText()).to.equal('Alice has a cat');
- expect(position.offset).to.equal(5);
- expect(position.offsetAtEnd).to.equal(false, 'offset is not at end');
-
- getSelection.returns({
- anchorNode: text,
- focusNode: text,
- anchorOffset: 15,
- focusOffset: 15,
- isCollapsed: true
- });
-
- expect(cursor.getPosition().offsetAtEnd).to.equal(true, 'offset at end');
- });
-
- it('returns boundries of selection when browser selection not collapsed', function() {
- var c = canvas.fromXML('<section>Alice <span>has</span> a <span>big</span> cat</section>'),
- dom = c.doc().dom(),
- text = {
- alice: findTextNode(dom, 'Alice '),
- has: findTextNode(dom, 'has'),
- cat: findTextNode(dom, ' cat')
- },
- cursor = c.getCursor(),
- aliceElement = c.getDocumentElement(text.alice),
- catElement = c.getDocumentElement(text.cat);
-
-
- [
- {focus: text.alice, focusOffset: 1, anchor: text.cat, anchorOffset: 2, selectionAnchor: catElement},
- {focus: text.cat, focusOffset: 2, anchor: text.alice, anchorOffset: 1, selectionAnchor: aliceElement}
- ].forEach(function(s, idx) {
- getSelection.returns({isColapsed: false, anchorNode: s.anchor, anchorOffset: s.anchorOffset, focusNode: s.focus, focusOffset: s.focusOffset});
-
- var selectionStart = cursor.getSelectionStart(),
- selectionEnd = cursor.getSelectionEnd(),
- selectionAnchor = cursor.getSelectionAnchor();
-
- expect(cursor.isSelecting()).to.equal(true, 'cursor is selecting');
- expect(selectionStart.element.sameNode(aliceElement)).to.equal(true, '"Alice" is the start of the selection ' + idx);
- expect(selectionStart.offset).to.equal(1, '"Alice" offset ok' + idx);
- expect(selectionEnd.element.sameNode(catElement)).to.equal(true, '"Cat" is the start of the selection ' + idx);
- expect(selectionEnd.offset).to.equal(2, '"Cat" offset ok' + idx);
- expect(selectionAnchor.element.sameNode(s.selectionAnchor)).to.equal(true, 'anchor ok');
- expect(selectionAnchor.offset).to.equal(s.anchorOffset, 'anchor offset ok');
- });
- });
-
- it('recognizes when browser selection boundries lies in sibling DocumentTextElements', function() {
- var c = canvas.fromXML('<section>Alice <span>has</span> a <span>big</span> cat</section>'),
- dom = c.doc().dom(),
- text = {
- alice: findTextNode(dom, 'Alice '),
- has: findTextNode(dom, 'has'),
- a: findTextNode(dom, ' a '),
- big: findTextNode(dom, 'big'),
- cat: findTextNode(dom, ' cat'),
- },
- cursor = c.getCursor();
-
- expect($(text.alice).text()).to.equal('Alice ');
- expect($(text.has).text()).to.equal('has');
- expect($(text.a).text()).to.equal(' a ');
- expect($(text.big).text()).to.equal('big');
- expect($(text.cat).text()).to.equal(' cat');
-
- getSelection.returns({anchorNode: text.alice, focusNode: text.a});
- expect(cursor.isSelectingSiblings()).to.equal(true, '"Alice" and "a" are children');
-
- getSelection.returns({anchorNode: text.alice, focusNode: text.cat});
- expect(cursor.isSelectingSiblings()).to.equal(true, '"Alice" and "cat" are children');
-
- getSelection.returns({anchorNode: text.alice, focusNode: text.has});
- expect(cursor.isSelectingSiblings()).to.equal(false, '"Alice" and "has" are not children');
-
- getSelection.returns({anchorNode: text.has, focusNode: text.big});
- expect(cursor.isSelectingSiblings()).to.equal(false, '"has" and "big" are not children');
-
- })
- });
-
- describe('Serializing document to WLXML', function() {
- it('keeps document intact when no changes have been made', function() {
- var xmlIn = '<section>Alice<div>has</div>a <span class="uri" meta-uri="http://cat.com">cat</span>!</section>',
- c = canvas.fromXML(xmlIn),
- xmlOut = c.toXML();
-
- var parser = new DOMParser(),
- input = parser.parseFromString(xmlIn, "application/xml").childNodes[0],
- output = parser.parseFromString(xmlOut, "application/xml").childNodes[0];
-
- expect(input.isEqualNode(output)).to.be.true;
- });
-
- it('keeps arbitrary node attributes intact', function() {
- var xmlIn = '<section a="1" xmlns:dcterms="http://purl.org/dc/terms/"></section>',
- $xmlOut = $(canvas.fromXML(xmlIn).toXML());
-
- expect($xmlOut.attr('a')).to.equal('1');
- expect($xmlOut.attr('xmlns:dcterms')).to.equal('http://purl.org/dc/terms/');
- });
-
- it('doesn\' serialize meta attribute if its empty', function() {
- var c;
-
- c = canvas.fromXML('<section class="uri" meta-uri="some.uri"></section>');
- c.doc().setWlxmlMetaAttr('uri', '');
- expect($(c.toXML()).attr('meta-uri')).to.equal(undefined, 'overriding attribute with zero length string');
-
- c = canvas.fromXML('<section class="uri"></section>');
- c.doc().setWlxmlMetaAttr('uri', '');
- expect($(c.toXML()).attr('meta-uri')).to.equal(undefined, 'setting attribute to zero length string');
- });
-
- describe('output xml', function() {
- it('keeps entities intact', function() {
- var xmlIn = '<section>< ></section>',
- c = canvas.fromXML(xmlIn),
- xmlOut = c.toXML();
- expect(xmlOut).to.equal(xmlIn);
- });
- it('keeps entities intact when they form html/xml', function() {
- var xmlIn = '<section><abc></section>',
- c = canvas.fromXML(xmlIn),
- xmlOut = c.toXML();
- expect(xmlOut).to.equal(xmlIn);
- });
- });
-
- describe('formatting output xml', function() {
- /*it('keeps white spaces at the edges of input xml', function() {
- var xmlIn = ' <section></section> ',
- c = canvas.fromXML(xmlIn),
- xmlOut = c.toXML();
-
- expect(xmlOut.substr(4)).to.equal(' <', 'start');
- expect(xmlOut.substr(-2)).to.equal('> ', 'end');
- });*/
- it('keeps white space between XML nodes', function() {
- var xmlIn = '<section>\n\n\n<div></div>\n\n\n<div></div>\n\n\n</section>',
- c = canvas.fromXML(xmlIn),
- xmlOut = c.toXML();
-
- var partsIn = xmlIn.split('\n\n\n'),
- partsOut = xmlOut.split('\n\n\n');
-
- expect(partsIn).to.deep.equal(partsOut);
- });
-
- it('keeps white space between XML nodes - inline case', function() {
- var xmlIn = '<section>\n\n\n<span></span>\n\n\n<span></span>\n\n\n</section>',
- c = canvas.fromXML(xmlIn);
-
- var xmlOut = c.toXML();
-
- var partsIn = xmlIn.split('\n\n\n'),
- partsOut = xmlOut.split('\n\n\n');
-
- expect(partsIn).to.deep.equal(partsOut);
- });
-
- it('keeps white space at the beginning of text', function() {
- var xmlIn = '<section> abc<div>some div</div> abc</section>',
- c = canvas.fromXML(xmlIn),
- xmlOut = c.toXML();
-
- expect(xmlOut).to.equal(xmlIn);
- });
-
- it('nests new children block elements', function() {
- var c = canvas.fromXML('<section></section>');
-
- c.doc().append({tag: 'header'});
-
- var xmlOut = c.toXML();
- expect(xmlOut.split('\n ')[0]).to.equal('<section>', 'nesting start ok');
- expect(xmlOut.split('\n').slice(-1)[0]).to.equal('</section>', 'nesting end ok');
-
- });
-
- it('doesn\'t nest new children inline elements', function() {
- var c = canvas.fromXML('<section></section>');
-
- c.doc().append({tag: 'span'});
-
- var xmlOut = c.toXML();
- expect(xmlOut).to.equal('<section><span></span></section>');
- });
-
- it('keeps original white space at the end of text', function() {
-
- var xmlIn = '<header> Some text ended with white space \
- \
- <span class="uri">Some text</span> some text\
- \
- </header>',
- c = canvas.fromXML(xmlIn);
-
- var xmlOut = c.toXML();
- console.log(xmlOut);
- expect(xmlOut).to.equal(xmlIn);
- });
-
- it('keeps white space around text node', function() {
- var xmlIn = '<section>\
- <header>header1</header>\
- Some text surrounded by white space\
- <header>header2</header>\
- </section>',
- c = canvas.fromXML(xmlIn);
-
- var xmlOut = c.toXML();
- expect(xmlOut).to.equal(xmlIn);
- });
-
- it('keeps white space around text node - last node case', function() {
- var xmlIn = '<section>\
- <header>header</header>\
- \
- Some text surrounded by white space\
- \
- </section>',
- c = canvas.fromXML(xmlIn);
-
- var xmlOut = c.toXML();
- expect(xmlOut).to.equal(xmlIn);
- });
-
- it('keeps white space after detaching text element', function() {
- var xmlIn = '<section><header>header</header>\n\
- \n\
- text1\n\
- \n\
- </section>',
- expectedXmlOut = '<section><header>header</header>\n\
- \n\
- \n\
- \n\
- </section>',
- c = canvas.fromXML(xmlIn),
- children = c.doc().children(),
- text = children[children.length-1];
-
- expect(text.getText()).to.equal('text1');
-
- text.detach();
-
- var xmlOut = c.toXML();
- expect(xmlOut).to.equal(expectedXmlOut);
- });
-
- })
- })
-});
-
-
-});
\ No newline at end of file
+++ /dev/null
-define([
-'libs/jquery',
-'libs/underscore',
-'modules/documentCanvas/classAttributes',
-'modules/documentCanvas/canvas/utils',
-'modules/documentCanvas/canvas/widgets',
-'modules/documentCanvas/canvas/wlxmlManagers'
-], function($, _, classAttributes, utils, widgets, wlxmlManagers) {
-
-'use strict';
-
-
-// DocumentElement represents a text or an element node from WLXML document rendered inside Canvas
-var DocumentElement = function(htmlElement, canvas) {
- if(arguments.length === 0)
- return;
- this.canvas = canvas;
- this._setupDOMHandler(htmlElement);
-}
-
-var elementTypeFromParams = function(params) {
- return params.text !== undefined ? DocumentTextElement : DocumentNodeElement;
-
-};
-
-$.extend(DocumentElement, {
- create: function(params, canvas) {
- return elementTypeFromParams(params).create(params);
- },
-
- createDOM: function(params) {
- return elementTypeFromParams(params).createDOM(params);
- },
-
- fromHTMLElement: function(htmlElement, canvas) {
- var $element = $(htmlElement);
- if(htmlElement.nodeType === Node.ELEMENT_NODE && $element.attr('document-node-element') !== undefined)
- return DocumentNodeElement.fromHTMLElement(htmlElement, canvas);
- if($element.attr('document-text-element') !== undefined || (htmlElement.nodeType === Node.TEXT_NODE && $element.parent().attr('document-text-element') !== undefined))
- return DocumentTextElement.fromHTMLElement(htmlElement, canvas);
- return undefined;
- }
-});
-
-$.extend(DocumentElement.prototype, {
- _setupDOMHandler: function(htmlElement) {
- this.$element = $(htmlElement);
- },
- bound: function() {
- return $.contains(document.documentElement, this.dom()[0]);
- },
- dom: function() {
- return this.$element;
- },
- parent: function() {
- var parents = this.$element.parents('[document-node-element]');
- if(parents.length)
- return DocumentElement.fromHTMLElement(parents[0], this.canvas);
- return null;
- },
-
- parents: function() {
- var parents = [],
- parent = this.parent();
- while(parent) {
- parents.push(parent);
- parent = parent.parent();
- }
- return parents;
- },
-
- sameNode: function(other) {
- return other && (typeof other === typeof this) && other.dom()[0] === this.dom()[0];
- },
-
- wrapWithNodeElement: function(wlxmlNode) {
- var wrapper = DocumentNodeElement.create({tag: wlxmlNode.tag, klass: wlxmlNode.klass}, this);
- this.dom().replaceWith(wrapper.dom());
- wrapper.append(this);
- return wrapper;
- },
-
- markAsCurrent: function() {
- this.canvas.markAsCurrent(this);
- },
-
- getVerticallyFirstTextElement: function() {
- var toret;
- this.children().some(function(child) {
- if(!child.isVisible())
- return false; // continue
- if(child instanceof DocumentTextElement) {
- toret = child;
- return true; // break
- } else {
- toret = child.getVerticallyFirstTextElement();
- if(toret)
- return true; // break
- }
- });
- return toret;
- },
-
- getPreviousTextElement: function(includeInvisible) {
- return this.getNearestTextElement('above', includeInvisible);
- },
-
- getNextTextElement: function(includeInvisible) {
- return this.getNearestTextElement('below', includeInvisible);
- },
-
- getNearestTextElement: function(direction, includeInvisible) {
- includeInvisible = includeInvisible !== undefined ? includeInvisible : false;
- var selector = '[document-text-element]' + (includeInvisible ? '' : ':visible');
- return this.canvas.getDocumentElement(utils.nearestInDocumentOrder(selector, direction, this.dom()[0]));
- },
-
- isVisible: function() {
- return this instanceof DocumentTextElement || this.getWlxmlTag() !== 'metadata';
- },
-
- isInsideList: function() {
- return this.parents().some(function(parent) {
- return parent.is('list');
- });
- },
-
- exec: function(method) {
- var manager = this.data('_wlxmlManager');
- if(manager[method])
- return manager[method].apply(manager, Array.prototype.slice.call(arguments, 1));
- }
-});
-
-
-// DocumentNodeElement represents an element node from WLXML document rendered inside Canvas
-var DocumentNodeElement = function(htmlElement, canvas) {
- DocumentElement.call(this, htmlElement, canvas);
-};
-
-$.extend(DocumentNodeElement, {
- createDOM: function(params, canvas) {
- var dom = $('<div>')
- .attr('document-node-element', ''),
- widgetsContainer = $('<div>')
- .addClass('canvas-widgets')
- .attr('contenteditable', false),
- container = $('<div>')
- .attr('document-element-content', '');
-
- dom.append(widgetsContainer, container);
- // Make sure widgets aren't navigable with arrow keys
- widgetsContainer.find('*').add(widgetsContainer).attr('tabindex', -1);
-
- var element = this.fromHTMLElement(dom[0], canvas);
-
- element.setWlxml({tag: params.tag, klass: params.klass});
- if(params.meta) {
- _.keys(params.meta).forEach(function(key) {
- element.setWlxmlMetaAttr(key, params.meta[key]);
- });
- }
- element.data('other-attrs', params.others);
-
- if(params.rawChildren && params.rawChildren.length) {
- container.append(params.rawChildren);
- } else if(params.prepopulateOnEmpty) {
- element.append(DocumentTextElement.create({text: ''}));
- }
- return dom;
- },
-
- create: function(params, canvas) {
- return this.fromHTMLElement(this.createDOM(params, canvas)[0], canvas);
- },
-
- fromHTMLElement: function(htmlElement, canvas) {
- return new this(htmlElement, canvas);
- }
-});
-
-var manipulate = function(e, params, action) {
- var element;
- if(params instanceof DocumentElement) {
- element = params;
- } else {
- element = DocumentElement.create(params);
- }
- var target = (action === 'append' || action === 'prepend') ? e._container() : e.dom();
- target[action](element.dom());
- return element;
-};
-
-DocumentNodeElement.prototype = new DocumentElement();
-
-
-$.extend(DocumentNodeElement.prototype, {
- _container: function() {
- return this.dom().children('[document-element-content]');
- },
- detach: function() {
- var parent = this.parent();
- if(!parent)
- return;
-
- var parentChildren = parent.children(),
- myIdx = parent.childIndex(this);
-
- if(myIdx > 0 && myIdx < parentChildren.length) {
- if((parentChildren[myIdx-1] instanceof DocumentTextElement) && (parentChildren[myIdx+1] instanceof DocumentTextElement)) {
- parentChildren[myIdx-1].appendText(parentChildren[myIdx+1].getText());
- parentChildren[myIdx+1].detach();
- }
- }
- this.dom().detach();
- this.canvas = null;
- return this;
- },
- unwrapContents: function() {
- var parent = this.parent();
- if(!parent)
- return;
-
- var parentChildren = parent.children(),
- myChildren = this.children(),
- myIdx = parent.childIndex(this);
-
- if(myChildren.length === 0)
- return this.detach();
-
- var moveLeftRange, moveRightRange, leftMerged;
-
- if(myIdx > 0 && (parentChildren[myIdx-1] instanceof DocumentTextElement) && (myChildren[0] instanceof DocumentTextElement)) {
- parentChildren[myIdx-1].appendText(myChildren[0].getText());
- myChildren[0].detach();
- moveLeftRange = true;
- leftMerged = true;
- } else {
- leftMerged = false;
- }
-
- if(!(leftMerged && myChildren.length === 1)) {
- if(myIdx < parentChildren.length - 1 && (parentChildren[myIdx+1] instanceof DocumentTextElement) && (myChildren[myChildren.length-1] instanceof DocumentTextElement)) {
- parentChildren[myIdx+1].prependText(myChildren[myChildren.length-1].getText());
- myChildren[myChildren.length-1].detach();
- moveRightRange = true;
- }
- }
-
- var childrenLength = this.children().length;
- this.children().forEach(function(child) {
- this.before(child);
- }.bind(this));
-
- this.detach();
-
- return {
- element1: parent.children()[myIdx + (moveLeftRange ? -1 : 0)],
- element2: parent.children()[myIdx + childrenLength-1 + (moveRightRange ? 1 : 0)]
- };
- },
- data: function() {
- var dom = this.dom(),
- args = Array.prototype.slice.call(arguments, 0);
- if(args.length === 2 && args[1] === undefined)
- return dom.removeData(args[0]);
- return dom.data.apply(dom, arguments);
- },
- toXML: function(level) {
- var node = $('<' + this.getWlxmlTag() + '>');
-
- if(this.getWlxmlClass())
- node.attr('class', this.getWlxmlClass());
- var meta = this.getWlxmlMetaAttrs();
- meta.forEach(function(attr) {
- if(attr.value)
- node.attr('meta-' + attr.name, attr.value);
- });
- _.keys(this.data('other-attrs') || {}).forEach(function(key) {
- node.attr(key, this.data('other-attrs')[key]);
- }, this);
-
- var addFormatting = function() {
- var toret = $('<div>');
- var formattings = {};
-
- if(this.data('orig-before') !== undefined) {
- if(this.data('orig-before')) {
- toret.prepend(document.createTextNode(this.data('orig-before')));
- }
- } else if(level && this.getWlxmlTag() !== 'span') {
- toret.append('\n' + (new Array(level * 2 + 1)).join(' '));
- }
-
- toret.append(node);
-
- if(this.data('orig-after')) {
- toret.append(document.createTextNode(this.data('orig-after')));
- }
-
- /* Inside node */
- if(this.data('orig-begin')) {
- node.prepend(this.data('orig-begin'));
- formattings.begin = true;
- }
-
- if(this.data('orig-end') !== undefined) {
- if(this.data('orig-end')) {
- node.append(this.data('orig-end'));
- }
- } else if(this.getWlxmlTag() !== 'span' && children.length){
- node.append('\n' + (new Array(level * 2 + 1)).join(' '));
- }
-
- return {parts: toret.contents(), formattings: formattings};
- }.bind(this);
-
-
-
- var children = this.children(),
- childParts;
-
- var formatting = addFormatting(node);
-
- for(var i = children.length - 1; i >= 0; i--) {
- childParts = children[i].toXML(level + 1);
- if(typeof childParts === 'string')
- childParts = [document.createTextNode(childParts)];
-
- if(formatting.formattings.begin) {
- $(node.contents()[0]).after(childParts);
- } else
- node.prepend(childParts);
- }
- return formatting.parts;
- },
- append: function(params) {
- if(params.tag !== 'span')
- this.data('orig-end', undefined);
- return manipulate(this, params, 'append');
- },
- prepend: function(params) {
- return manipulate(this, params, 'prepend');
- },
- before: function(params) {
- return manipulate(this, params, 'before');
-
- },
- after: function(params) {
- return manipulate(this, params, 'after');
- },
- children: function() {
- var toret = [];
- if(this instanceof DocumentTextElement)
- return toret;
-
-
- var elementContent = this._container().contents();
- var element = this;
- elementContent.each(function(idx) {
- var childElement = DocumentElement.fromHTMLElement(this, element.canvas);
- if(childElement === undefined)
- return true;
- if(idx === 0 && elementContent.length > 1 && elementContent[1].nodeType === Node.ELEMENT_NODE && (childElement instanceof DocumentTextElement) && $.trim($(this).text()) === '')
- return true;
- if(idx > 0 && childElement instanceof DocumentTextElement) {
- if(toret[toret.length-1] instanceof DocumentNodeElement && $.trim($(this).text()) === '')
- return true;
- }
- toret.push(childElement);
- });
- return toret;
- },
- childIndex: function(child) {
- var children = this.children(),
- toret = null;
- children.forEach(function(c, idx) {
- if(c.sameNode(child)) {
- toret = idx;
- return false;
- }
- });
- return toret;
- },
- getWlxmlTag: function() {
- return this._container().attr('wlxml-tag');
- },
- setWlxmlTag: function(tag) {
- if(tag === this.getWlxmlTag())
- return;
-
- this._container().attr('wlxml-tag', tag);
- if(!this.__updatingWlxml)
- this._updateWlxmlManager();
- },
- getWlxmlClass: function() {
- var klass = this._container().attr('wlxml-class');
- if(klass)
- return klass.replace(/-/g, '.');
- return undefined;
- },
- setWlxmlClass: function(klass) {
- if(klass === this.getWlxmlClass())
- return;
-
- this.getWlxmlMetaAttrs().forEach(function(attr) {
- if(!classAttributes.hasMetaAttr(klass, attr.name))
- this.dom().removeAttr('wlxml-meta-' + attr.name);
- }, this);
-
- if(klass)
- this._container().attr('wlxml-class', klass.replace(/\./g, '-'));
- else
- this._container().removeAttr('wlxml-class');
- if(!this.__updatingWlxml)
- this._updateWlxmlManager();
- },
- setWlxml: function(params) {
- this.__updatingWlxml = true;
- if(params.tag !== undefined)
- this.setWlxmlTag(params.tag);
- if(params.klass !== undefined)
- this.setWlxmlClass(params.klass);
- this._updateWlxmlManager();
- this.__updatingWlxml = false;
- },
- _updateWlxmlManager: function() {
- var manager = wlxmlManagers.getFor(this);
- this.data('_wlxmlManager', manager);
- manager.setup();
- },
- is: function(what) {
- if(what === 'list' && _.contains(['list.items', 'list.items.enum'], this.getWlxmlClass()))
- return true;
- return false;
- },
-
- getWlxmlMetaAttr: function(attr) {
- return this.dom().attr('wlxml-meta-'+attr);
- },
-
- getWlxmlMetaAttrs: function() {
- var toret = [];
- var attrList = classAttributes.getMetaAttrsList(this.getWlxmlClass());
- attrList.all.forEach(function(attr) {
- toret.push({name: attr.name, value: this.getWlxmlMetaAttr(attr.name) || ''});
- }, this);
- return toret;
- },
-
- setWlxmlMetaAttr: function(attr, value) {
- this.dom().attr('wlxml-meta-'+attr, value);
- },
-
- toggleLabel: function(toggle) {
- var displayCss = toggle ? 'inline-block' : 'none';
- var label = this.dom().children('.canvas-widgets').find('.canvas-widget-label');
- label.css('display', displayCss);
- this.toggleHighlight(toggle);
- },
-
- toggleHighlight: function(toggle) {
- this._container().toggleClass('highlighted-element', toggle);
- },
-
- toggle: function(toggle) {
- var mng = this.data('_wlxmlManager');
- if(mng) {
- mng.toggle(toggle);
- }
- }
-});
-
-
-// DocumentNodeElement represents a text node from WLXML document rendered inside Canvas
-var DocumentTextElement = function(htmlElement, canvas) {
- DocumentElement.call(this, htmlElement, canvas);
-};
-
-$.extend(DocumentTextElement, {
- createDOM: function(params) {
- return $('<div>')
- .attr('document-text-element', '')
- .text(params.text || utils.unicode.ZWS);
- },
-
- create: function(params, canvas) {
- return this.fromHTMLElement(this.createDOM(params)[0]);
- },
-
- fromHTMLElement: function(htmlElement, canvas) {
- return new this(htmlElement, canvas);
- },
- isContentContainer: function(htmlElement) {
- return htmlElement.nodeType === Node.TEXT_NODE && $(htmlElement).parent().is('[document-text-element]');
- }
-});
-
-DocumentTextElement.prototype = new DocumentElement();
-
-$.extend(DocumentTextElement.prototype, {
- toXML: function(parent) {
- return this.getText();
- },
- _setupDOMHandler: function(htmlElement) {
- var $element = $(htmlElement);
- if(htmlElement.nodeType === Node.TEXT_NODE)
- this.$element = $element.parent();
- else
- this.$element = $element;
- },
- detach: function() {
- this.dom().detach();
- this.canvas = null;
- return this;
- },
- setText: function(text) {
- this.dom().contents()[0].data = text;
- },
- appendText: function(text) {
- this.dom().contents()[0].data += text;
- },
- prependText: function(text) {
- this.dom().contents()[0].data = text + this.dom().contents()[0].data;
- },
- getText: function() {
- return this.dom().text().replace(utils.unicode.ZWS, '');
- },
- isEmpty: function() {
- // Having at least Zero Width Space is guaranteed be Content Observer
- return this.dom().contents()[0].data === utils.unicode.ZWS;
- },
- after: function(params) {
- if(params instanceof DocumentTextElement || params.text)
- return false;
- var element;
- if(params instanceof DocumentNodeElement) {
- element = params;
- } else {
- element = DocumentNodeElement.create(params, this.canvas);
- }
- this.dom().wrap('<div>');
- this.dom().parent().after(element.dom());
- this.dom().unwrap();
- return element;
- },
- before: function(params) {
- if(params instanceof DocumentTextElement || params.text)
- return false;
- var element;
- if(params instanceof DocumentNodeElement) {
- element = params;
- } else {
- element = DocumentNodeElement.create(params, this.canvas);
- }
- this.dom().wrap('<div>');
- this.dom().parent().before(element.dom());
- this.dom().unwrap();
- return element;
- },
- wrapWithNodeElement: function(wlxmlNode) {
- if(typeof wlxmlNode.start === 'number' && typeof wlxmlNode.end === 'number') {
- return this.canvas.wrapText({
- inside: this.parent(),
- textNodeIdx: this.parent().childIndex(this),
- offsetStart: Math.min(wlxmlNode.start, wlxmlNode.end),
- offsetEnd: Math.max(wlxmlNode.start, wlxmlNode.end),
- _with: {tag: wlxmlNode.tag, klass: wlxmlNode.klass}
- });
- } else {
- return DocumentElement.prototype.wrapWithNodeElement.call(this, wlxmlNode);
- }
- },
- unwrap: function() {
- var parent = this.parent(),
- toret;
- if(parent.children().length === 1) {
- toret = parent.parent();
- var grandParent = parent.parent();
- if(grandParent) {
- var grandParentChildren = grandParent.children(),
- idx = grandParent.childIndex(parent),
- prev = idx - 1 > -1 ? grandParentChildren[idx-1] : null,
- next = idx + 1 < grandParentChildren.length ? grandParentChildren[idx+1] : null;
-
- prev = (prev instanceof DocumentTextElement) ? prev : null;
- next = (next instanceof DocumentTextElement) ? next : null;
-
- if(prev && next) {
- prev.setText(prev.getText() + this.getText() + next.getText());
- next.detach();
- } else if (prev || next) {
- var target = prev ? prev : next,
- newText = prev ? target.getText() + this.getText() : this.getText() + target.getText();
- target.setText(newText);
- } else {
- parent.after(this);
- }
- } else {
- parent.after(this);
- }
- parent.detach();
- return toret;
- }
- },
- split: function(params) {
- var parentElement = this.parent(),
- myIdx = parentElement.childIndex(this),
- myCanvas = this.canvas,
- passed = false,
- succeedingChildren = [],
- thisElement = this,
- prefix = this.getText().substr(0, params.offset),
- suffix = this.getText().substr(params.offset);
-
- parentElement.children().forEach(function(child) {
- if(passed)
- succeedingChildren.push(child);
- if(child.sameNode(thisElement))
- passed = true;
- });
-
- if(prefix.length > 0)
- this.setText(prefix);
- else
- this.detach();
-
- var newElement = DocumentNodeElement.create({tag: parentElement.getWlxmlTag(), klass: parentElement.getWlxmlClass()}, myCanvas);
- parentElement.after(newElement);
-
- if(suffix.length > 0)
- newElement.append({text: suffix});
- succeedingChildren.forEach(function(child) {
- newElement.append(child);
- });
-
- return {first: parentElement, second: newElement};
- },
- divide: function(params) {
- var myText = this.getText();
-
- if(params.offset === myText.length)
- return this.after(params);
- if(params.offset === 0)
- return this.before(params);
-
- var lhsText = myText.substr(0, params.offset),
- rhsText = myText.substr(params.offset),
- newElement = DocumentNodeElement.create({tag: params.tag, klass: params.klass}, this.canvas),
- rhsTextElement = DocumentTextElement.create({text: rhsText});
-
- this.setText(lhsText);
- this.after(newElement);
- newElement.after(rhsTextElement);
- return newElement;
- },
-
- toggleHighlight: function() {
- // do nothing for now
- }
-});
-
-return {
- DocumentElement: DocumentElement,
- DocumentNodeElement: DocumentNodeElement,
- DocumentTextElement: DocumentTextElement
-};
-
-});
\ No newline at end of file
+++ /dev/null
-define([
-'modules/documentCanvas/canvas/documentElement',
-'modules/documentCanvas/canvas/utils'
-], function(documentElement, utils) {
-
-'use strict';
-
-
-var KEYS = {
- ENTER: 13,
- ARROW_LEFT: 37,
- ARROW_UP: 38,
- ARROW_RIGHT: 39,
- ARROW_DOWN: 40,
- BACKSPACE: 8,
- DELETE: 46,
- X: 88
-};
-
-var handleKey = function(event, canvas) {
- handlers.some(function(handler) {
- if(handles(handler, event) && handler[event.type]) {
- handler[event.type](event, canvas);
- return true;
- }
- });
-};
-
-var handles = function(handler, event) {
- if(handler.key === event.which)
- return true;
- if(handler.keys && handler.keys.indexOf(event.which) !== -1)
- return true;
- return false;
-};
-
-var handlers = [];
-
-
-handlers.push({key: KEYS.ENTER,
- keydown: function(event, canvas) {
- event.preventDefault();
- var cursor = canvas.getCursor(),
- position = cursor.getPosition(),
- element = position.element;
-
- if(!cursor.isSelecting()) {
- if(event.ctrlKey) {
- var added = element.after({tag: 'block'});
- added.append({text:''});
- canvas.setCurrentElement(added, {caretTo: 'start'});
-
- } else {
-
- if(!(element.parent().parent())) {
- return false; // top level element is unsplittable
- }
-
- var elements = position.element.split({offset: position.offset}),
- newEmpty,
- goto,
- gotoOptions;
-
- if(position.offsetAtBeginning)
- newEmpty = elements.first;
- else if(position.offsetAtEnd)
- newEmpty = elements.second;
-
- if(newEmpty) {
- goto = newEmpty.append(documentElement.DocumentTextElement.create({text: ''}, this));
- gotoOptions = {};
- } else {
- goto = elements.second;
- gotoOptions = {caretTo: 'start'};
- }
-
- canvas.setCurrentElement(goto, gotoOptions);
- }
- }
- }
-});
-
-handlers.push({keys: [KEYS.ARROW_UP, KEYS.ARROW_DOWN, KEYS.ARROW_LEFT, KEYS.ARROW_RIGHT],
- keydown: function(event, canvas) {
- var position = canvas.getCursor().getPosition(),
- element = position.element;
- if(element && (element instanceof documentElement.DocumentTextElement) && element.isEmpty()) {
- var direction, caretTo;
- if(event.which === KEYS.ARROW_LEFT || event.which === KEYS.ARROW_UP) {
- direction = 'above';
- caretTo = 'end';
- } else {
- direction = 'below';
- caretTo = 'start';
- }
- var el = canvas.getDocumentElement(utils.nearestInDocumentOrder('[document-text-element]', direction, element.dom()[0]));
- canvas.setCurrentElement(el, {caretTo: caretTo});
- }
- },
- keyup: function(event, canvas) {
- var element = canvas.getCursor().getPosition().element,
- caretTo = false;
- if(!element) {
- // Chrome hack
- var direction;
- if(event.which === KEYS.ARROW_LEFT || event.which === KEYS.ARROW_UP) {
- direction = 'above';
- caretTo = 'end';
- } else {
- direction = 'below';
- caretTo = 'start';
- }
- element = canvas.getDocumentElement(utils.nearestInDocumentOrder('[document-text-element]:visible', direction, window.getSelection().focusNode));
- }
- canvas.setCurrentElement(element, {caretTo: caretTo});
- }
-});
-
-
-var selectsWholeTextElement = function(cursor) {
- if(cursor.isSelecting() && cursor.getSelectionStart().offsetAtBeginning && cursor.getSelectionEnd().offsetAtEnd)
- return true;
- return false;
-}
-
-handlers.push({key: KEYS.X,
- keydown: function(event, canvas) {
- if(event.ctrlKey && selectsWholeTextElement(canvas.getCursor()))
- event.preventDefault();
- }
-});
-
-handlers.push({keys: [KEYS.BACKSPACE, KEYS.DELETE],
- keydown: function(event, canvas) {
- var cursor = canvas.getCursor(),
- position = canvas.getCursor().getPosition(),
- element = position.element;
-
- if(cursor.isSelecting() && !cursor.isSelectingWithinElement()) {
- event.preventDefault();
- return;
- }
-
- var cursorAtOperationEdge = position.offsetAtBeginning;
- if(event.which === KEYS.DELETE) {
- cursorAtOperationEdge = position.offsetAtEnd;
- }
-
- var willDeleteWholeText = function() {
- return element.getText().length === 1 || selectsWholeTextElement(cursor);
- }
-
- if(willDeleteWholeText()) {
- event.preventDefault();
- element.setText('');
- }
- else if(element.isEmpty()) {
-
- var direction = 'above',
- caretTo = 'end';
-
- if(event.which === KEYS.DELETE) {
- direction = 'below';
- caretTo = 'start';
- }
-
- event.preventDefault();
-
- var parent = element.parent(),
- grandParent = parent ? parent.parent() : null,
- goto;
- if(parent.children().length === 1 && parent.children()[0].sameNode(element)) {
- if(grandParent && grandParent.children().length === 1) {
- goto = grandParent.append({text: ''});
- } else {
- goto = element.getNearestTextElement(direction);
- }
- parent.detach();
- } else {
- goto = element.getNearestTextElement(direction);
- element.detach();
- }
- canvas.setCurrentElement(goto, {caretTo: caretTo});
- canvas.publisher('contentChanged');
- }
- else if(cursorAtOperationEdge) {
- // todo
- event.preventDefault();
- }
- }
-});
-
-return {
- handleKey: handleKey
-};
-
-});
\ No newline at end of file
+++ /dev/null
-define([
-'libs/jquery',
-], function($) {
-
-'use strict';
-
-
-var nearestInDocumentOrder = function(selector, direction, element) {
- var parents = $(element).parents(),
- parent = parents.length ? $(parents[parents.length-1]) : element;
-
- var adj = parent.find(selector).filter(function() {
- return this.compareDocumentPosition(element) & (direction === 'above' ? Node.DOCUMENT_POSITION_FOLLOWING : Node.DOCUMENT_POSITION_PRECEDING);
- });
-
- if(adj.length) {
- return adj[direction === 'above' ? adj.length-1 : 0];
- }
- return null;
-}
-
-return {
- nearestInDocumentOrder: nearestInDocumentOrder,
- unicode: {
- ZWS: '\u200B'
- }
-};
-
-});
+++ /dev/null
-define([
-'libs/chai',
-'modules/documentCanvas/canvas/utils'
-
-], function(chai, utils) {
-
-'use strict';
-
-var expect = chai.expect;
-
-
-describe('utils.nearestInDocumentOrder', function() {
-
-
- var tests = [
- ['return null if no match found',
- '<span>\
- <span></span>\
- <div id="b">\
- <span></span>\
- </span>'
- ],
- ['returns nearest sibling if applicable',
- '<div n1>\
- <div n2></div>\
- <div n3>\
- <div id="a"></div>\
- <div id="b"></div>\
- <div id="c"></div>\
- </div>\
- <div n4></div>\
- </div>'
- ],
- ['looks inside siblings children',
- '<div>\
- <div></div>\
- <div>\
- <div></div>\
- <span>\
- <div id="a"></div>\
- </span>\
- <div id="b"></div>\
- <span>\
- <div id="c"></div>\
- </span>\
- <div></div>\
- </div>\
- <div></div>\
- </div>'
- ]
-
-
- ];
-
- tests.forEach(function(test) {
- var description = test[0],
- html = test[1];
- it(description, function() {
- var dom = $(html),
- a = dom.find('#a').length ? dom.find('#a')[0] : null,
- b = dom.find('#b')[0],
- c = dom.find('#c').length ? dom.find('#c')[0] : null;
- expect(utils.nearestInDocumentOrder('div', 'above', b)).to.equal(a, 'above');
- expect(utils.nearestInDocumentOrder('div', 'below', b)).to.equal(c, 'below');
- });
- });
-
-});
-
-});
+++ /dev/null
-define([
-'libs/jquery',
-'utils/wlxml'
-], function($, wlxmlUtils) {
-
-'use strict';
-
-return {
- labelWidget: function(tag, klass) {
- return $('<span>')
- .addClass('canvas-widget canvas-widget-label')
- .text(wlxmlUtils.wlxmlTagNames[tag] + (klass ? ' / ' + wlxmlUtils.wlxmlClassNames[klass] : ''));
- },
-
- footnoteHandler: function(clickHandler) {
- var mydom = $('<span>')
- .addClass('canvas-widget canvas-widget-footnote-handle')
- .css('display', 'inline')
- .show();
-
- mydom.click(function(e) {
- e.stopPropagation();
- clickHandler();
- });
-
- return mydom;
- },
-
- hideButton: function(clickHandler) {
- var mydom = $('<span>x</span>')
- .addClass('canvas-widget canvas-widget-hide-button');
- mydom.click(function(e) {
- e.stopPropagation();
- clickHandler();
- });
- return mydom;
- }
-
-};
-
-});
\ No newline at end of file
+++ /dev/null
-[document-node-element] {
- .canvas-widgets {
- display: inline;
- text-indent: 0;
- }
-
- .canvas-widget {
- display: none;
- }
-
- .canvas-widget-label {
- position: absolute;
- display: none;
- top: -20px;
- left:0;
- background-color: red;
- color: white;
- font-size:12px;
- font-weight: bold;
- padding: 1px 3px;
- //width:300px;
- opacity: 0.65;
- font-family: monospace;
- z-index:9999;
- white-space: nowrap;
- }
-
-
-
- .canvas-widget-footnote-handle {
- display: inline;
- outline: 0px solid transparent;
- cursor: pointer;
- counter-increment: footnote;
- vertical-align: super;
- color: blue;
- font-size: .8em;
-
- &::before, {
- content: "[" counter(footnote) "]";
- }
- }
-
- .canvas-widget-hide-button {
- @line_height: 12px;
- @padding: 2px;
- @temporary_footnote_hack: 10px;
-
- position: absolute;
- top: -(@line_height + 2 * @padding) + @temporary_footnote_hack;
- right: 0;
- line-height: @line_height;
- padding: @padding;
- font-weight: bold;
- color: white;
- background-image: linear-gradient(to bottom,#ee5f5b,#bd362f);
- border-radius: 1px;
- cursor: pointer;
- }
-}
\ No newline at end of file
+++ /dev/null
-define([
-'libs/jquery',
-'modules/documentCanvas/canvas/widgets'
-], function($, widgets) {
-
-'use strict';
-
-
-var DocumentElementWrapper = function(documentElement) {
-
- this.documentElement = documentElement;
-
- this.addWidget = function(widget) {
- documentElement.dom().children('.canvas-widgets').append(widget);
- };
-
- this.clearWidgets = function() {
- documentElement.dom().children('.canvas-widgets').empty();
- }
-
- this.setDisplayStyle = function(displayStyle) {
- documentElement.dom().css('display', displayStyle || '');
- documentElement._container().css('display', displayStyle || '');
- };
-
- this.tag = function() {
- return documentElement.getWlxmlTag();
- };
-
- this.klass = function() {
- return documentElement.getWlxmlClass();
- };
-
- this.toggle = function(toggle) {
- documentElement._container().toggle(toggle);
- }
-
- var eventBus = documentElement.canvas ? documentElement.canvas.eventBus :
- {trigger: function() {}};
- this.trigger = function() {
- eventBus.trigger.apply(eventBus, arguments);
- }
-
-}
-
-var getDisplayStyle = function(tag, klass) {
- if(tag === 'metadata')
- return 'none';
- if(tag === 'span')
- return 'inline';
- if(klass === 'item')
- return null;
- return 'block';
-}
-
-var GenericManager = function(wlxmlElement) {
- this.el = wlxmlElement;
-};
-
-$.extend(GenericManager.prototype, {
- setup: function() {
- this.el.setDisplayStyle(getDisplayStyle(this.el.tag(), this.el.klass()));
-
- this.el.clearWidgets();
- this.el.addWidget(widgets.labelWidget(this.el.tag(), this.el.klass()));
-
- },
- toggle: function(toggle) {
- this.el.toggle(toggle);
- }
-
-})
-
-var managers = {
- _m: {},
- set: function(tag, klass, manager) {
- if(!this._m[tag])
- this._m[tag] = {};
- this._m[tag][klass] = manager;
- },
- get: function(tag,klass) {
- if(this._m[tag] && this._m[tag][klass])
- return this._m[tag][klass];
- return GenericManager;
- }
-}
-
-var FootnoteManager = function(wlxmlElement) {
- this.el = wlxmlElement;
-};
-$.extend(FootnoteManager.prototype, {
- setup: function() {
- this.el.clearWidgets();
-
- var clickHandler = function() {
- this.toggle(true);
- }.bind(this);
- this.footnoteHandler = widgets.footnoteHandler(clickHandler);
- this.el.addWidget(this.footnoteHandler);
-
- var closeHandler = function() {
- this.toggle(false);
-
- }.bind(this);
- this.hideButton = widgets.hideButton(closeHandler);
- this.el.addWidget(this.hideButton);
-
- this.toggle(false, {silent: true});
- },
- toggle: function(toggle, options) {
- options = options || {};
- this.hideButton.toggle(toggle);
- this.footnoteHandler.toggle(!toggle);
-
- this.el.setDisplayStyle(toggle ? 'block' : 'inline');
- this.el.toggle(toggle);
- if(!options.silent)
- this.el.trigger('elementToggled', toggle, this.el.documentElement);
- }
-})
-managers.set('aside', 'footnote', FootnoteManager);
-
-
-var ListItemManager = function(wlxmlElement) {
- this.el = wlxmlElement;
-};
-$.extend(ListItemManager.prototype, {
- setup: function() {
- this.el.clearWidgets();
- this.el.addWidget(widgets.labelWidget(this.el.tag(), this.el.klass()));
- this.el.documentElement._container().css({display: 'list-item'});
- },
- toggleBullet: function(toggle) {
- this.el.documentElement._container().css({display : toggle ? 'list-item' : 'block'});
- }
-});
-managers.set('div', 'item', ListItemManager);
-
-return {
- getFor: function(documentElement) {
- var wlxmlElement = new DocumentElementWrapper(documentElement);
- return new (managers.get(wlxmlElement.tag(), wlxmlElement.klass()))(wlxmlElement);
-
- }
-};
-
-});
\ No newline at end of file
+++ /dev/null
-define([], function() {
-
-'use strict';
-
-var wlxmlDict = {
- 'uri': {
- 'uri': 'string'
- }
-};
-
-var hasMetaAttr = function(klass, attrName, dict) {
- dict = dict || wlxmlDict;
- if(!klass)
- return false;
-
- var parts = klass.split('.');
- var partialClass = '';
- for(var i = 0; i < parts.length; i++) {
- partialClass += (partialClass === '' ? '' : '.') + parts[i];
- if(dict[partialClass] && dict[partialClass][attrName])
- return true;
- }
- return false;
-};
-
-var getMetaAttrsList = function(klass, dict) {
- dict = dict || wlxmlDict;
- klass = klass || '';
-
- var toret = {own: [], inheritedFrom: {}, all: []};
- var parts = klass.split('.');
- var partialClass = '';
-
- var generate = function(klass) {
- var toret = [],
- desc = dict[klass];
-
- if(!desc)
- return toret;
-
- _.keys(desc).forEach(function(key) {
- toret.push({name: key, type: desc[key]});
- });
- return toret;
- };
-
- toret.own = generate(klass);
- for(var i = 0; i < parts.length; i++) {
- partialClass += (partialClass === '' ? '' : '.') + parts[i];
- var list = generate(partialClass);
- if(list.length > 0) {
- toret.inheritedFrom[partialClass] = generate(partialClass);
- toret.all = toret.all.concat(toret.inheritedFrom[partialClass]);
- }
- }
- return toret;
-};
-
-return {
- hasMetaAttr: hasMetaAttr,
- getMetaAttrsList: getMetaAttrsList
-};
-
-});
\ No newline at end of file
+++ /dev/null
-define([
-'modules/documentCanvas/canvas/documentElement'
-], function(documentElement) {
-
-'use strict';
-
-
-var gridToggled = false;
-
-var commands = {
- _cmds: {},
- register: function(name, command) {
- this._cmds[name] = command;
- },
-
- run: function(name, params, canvas) {
- return this._cmds[name](canvas, params);
- }
-};
-
-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;
-
- if(canvas.list.areItemsOfTheSameList({element1: parent1, element2: parent2})) {
- var selectionAnchor = cursor.getSelectionAnchor();
- canvas.list.extractItems({element1: parent1, element2: parent2});
- canvas.setCurrentElement(selectionAnchor.element, {caretTo: selectionAnchor.offset});
- } else if(!cursor.isSelecting()) {
- var toUnwrap = cursor.getPosition().element,
- parent = toUnwrap.unwrap();
- canvas.setCurrentElement(parent);
- }
-});
-
-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;
-
- if(canvas.list.areItemsOfTheSameList({element1: parent1, element2: parent2})) {
- canvas.list.create({element1: parent1, element2: parent2});
- }
-});
-
-commands.register('list', function(canvas, params) {
- var cursor = canvas.getCursor(),
- selectionStart = cursor.getSelectionStart(),
- selectionEnd = cursor.getSelectionEnd(),
- parent1 = selectionStart.element.parent() || undefined,
- parent2 = selectionEnd.element.parent() || undefined;
-
- var selectionFocus = cursor.getSelectionFocus();
-
- if(selectionStart.element.isInsideList() || selectionEnd.element.isInsideList()) {
- return;
- }
-
- canvas.list.create({element1: parent1, element2: parent2});
-
- canvas.setCurrentElement(selectionFocus.element, {caretTo: selectionFocus.offset});
-});
-
-commands.register('toggle-grid', function(canvas, params) {
- canvas.doc().dom().find('[wlxml-tag]').toggleClass('rng-common-hoveredNode', params.toggle);
- gridToggled = params.toggle;
-});
-
-commands.register('newNodeRequested', function(canvas, params) {
- var cursor = canvas.getCursor(),
- selectionStart = cursor.getSelectionStart(),
- selectionEnd = cursor.getSelectionEnd();
-
- if(cursor.isSelecting()) {
- if(cursor.isSelectingSiblings()) {
- if(cursor.isSelectingWithinElement()) {
- var newElement = selectionStart.element.wrapWithNodeElement({tag: params.wlxmlTag, klass: params.wlxmlClass, start: selectionStart.offset, end: selectionEnd.offset}),
- caretTo = selectionStart.offset < selectionEnd.offset ? 'start' : 'end';
- canvas.setCurrentElement(newElement.children()[0], {caretTo: caretTo});
- }
- else {
- var parent = selectionStart.element.parent(),
- caretTo = selectionStart.element.sameNode(cursor.getSelectionAnchor().element) ? 'end' : 'start';
-
- var wrapper = canvas.wrapText({
- inside: parent,
- _with: {tag: params.wlxmlTag, klass: params.wlxmlClass},
- offsetStart: selectionStart.offset,
- offsetEnd: selectionEnd.offset,
- textNodeIdx: [parent.childIndex(selectionStart.element), parent.childIndex(selectionEnd.element)]
- });
-
- canvas.setCurrentElement(wrapper.children()[caretTo === 0 ? 0 : wrapper.children().length - 1], {caretTo: caretTo});
- }
- } else {
- var siblingParents = canvas.getSiblingParents({element1: selectionStart.element, element2: selectionEnd.element})
- if(siblingParents) {
- canvas.wrapElements({
- element1: siblingParents.element1,
- element2: siblingParents.element2,
- _with: {tag: params.wlxmlTag, klass: params.wlxmlClass}
- });
- }
- }
- } else if(canvas.getCurrentNodeElement()) {
- var el = canvas.getCurrentNodeElement().wrapWithNodeElement({tag: params.wlxmlTag, klass: params.wlxmlClass});
- canvas.setCurrentElement(el);
- }
-
-
-});
-
-commands.register('footnote', function(canvas, params) {
- var cursor = canvas.getCursor(),
- position = cursor.getPosition(),
- asideElement;
-
-
- if(cursor.isSelectingWithinElement()) {
- asideElement = position.element.wrapWithNodeElement({tag: 'aside', klass: 'footnote', start: cursor.getSelectionStart().offset, end: cursor.getSelectionEnd().offset});
- } else {
- asideElement = position.element.divide({tag: 'aside', klass: 'footnote', offset: position.offset});
- asideElement.append({text: ''});
- }
-
- 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.unwrapContents();
-
- if(element) {
- var elementIsFirstChild = nodeElement.childIndex(element);
- if(element.bound()) {
- canvas.setCurrentElement(element, {caretTo: position.offset});
- } else {
- if(elementIsFirstChild) {
- canvas.setCurrentElement(range.element1, {caretTo: 'end'});
- } else {
- canvas.setCurrentElement(range.element2, {caretTo: 'end'});
- }
- }
- } else {
- canvas.setCurrentElement(range.element1, {caretTo: 'start'});
- }
-
-});
-
-
-return {
- run: function(name, params, canvas) {
- return commands.run(name, params, canvas);
- }
-};
-
-});
\ No newline at end of file
+++ /dev/null
-// Module that implements main WYSIWIG edit area
-
-define([
-'libs/underscore',
-'./canvas/canvas',
-'./commands',
-'libs/text!./template.html'], function(_, canvas3, commands, template) {
-
-'use strict';
-
-return function(sandbox) {
-
- var canvas = canvas3.fromXML('', sandbox.publish);
- var canvasWrapper = $(template);
- var shownAlready = false;
- var scrollbarPosition = 0,
- cursorPosition;
-
- canvasWrapper.onShow = function() {
- if(!shownAlready) {
- shownAlready = true;
- canvas.setCurrentElement(canvas.doc().getVerticallyFirstTextElement());
- } else {
- canvas.setCursorPosition(cursorPosition);
- this.find('#rng-module-documentCanvas-contentWrapper').scrollTop(scrollbarPosition);
- }
- };
-
- canvasWrapper.onHide = function() {
- scrollbarPosition = this.find('#rng-module-documentCanvas-contentWrapper').scrollTop();
- cursorPosition = canvas.getCursor().getPosition();
- };
-
- /* public api */
- return {
- start: function() { sandbox.publish('ready'); },
- getView: function() {
- return canvasWrapper;
- },
- setDocument: function(xml) {
- canvas.loadWlxml(xml);
- canvasWrapper.find('#rng-module-documentCanvas-content').empty().append(canvas.view());
- sandbox.publish('documentSet');
- },
- getDocument: function() {
- return canvas.toXML();
- },
- modifyCurrentNodeElement: function(attr, value) {
- var currentNodeElement = canvas.getCurrentNodeElement();
- if(attr === 'class' || attr === 'tag') {
- currentNodeElement['setWlxml'+(attr[0].toUpperCase() + attr.substring(1))](value);
- } else {
- currentNodeElement.setWlxmlMetaAttr(attr, value);
- }
- sandbox.publish('currentNodeElementChanged', currentNodeElement);
- },
- highlightElement: function(element) {
- element.toggleHighlight(true);
- },
- dimElement: function(element) {
- element.toggleHighlight(false);
- },
- jumpToElement: function(element) {
- canvas.setCurrentElement(element);
- },
- command: function(command, params) {
- commands.run(command, params, canvas);
- sandbox.publish('contentChanged');
- }
- };
-
-};
-
-});
\ No newline at end of file
+++ /dev/null
-@import 'nodes.less';
-@import 'canvas/widgets.less';
-
-#rng-module-documentCanvas {
- height: 100%;
-}
-
-#rng-module-documentCanvas-mainArea {
- height: 100%;
- margin-bottom: 20px;
-}
-
-#rng-module-documentCanvas-contentWrapper {
- border-color: #ddd;
- border-style: solid;
- border-width: 1px;
- float:left;
- width: 100%;
- height: 100%;
- overflow-y: scroll;
- padding: 0 10px;
-
- &::-webkit-scrollbar {
- .rng-mixin-scrollbar;
- }
- &::-webkit-scrollbar-track {
- .rng-mixin-scrollbar-track;
- }
- &::-webkit-scrollbar-thumb {
- .rng-mixin-scrollbar-thumb;
- }
- &::-webkit-scrollbar-thumb:window-inactive {
- .rng-mixin-scrollbar-thumb-window-inactive;
- }
-
- .canvas-wrapper {
- outline: 0px solid transparent;
- }
-
- .current-text-element {
- outline: 1px dashed black;
- }
-
- .current-node-element {
- border-color: lighten(#000, 15%);
- border-style: solid;
- border-width: 1px;
- }
-
- .highlighted-element {
- border: 1px solid red;
- }
-
- counter-reset: footnote;
-}
-
-.rng-module-documentCanvas-currentNode {
- background: #fffacd;
- border-color: grey;
- border-style:dashed;
- border-width:1px;
-}
-
-.rng-module-documentCanvas-hoveredNodeTag {
- position:absolute;
- height:20px;
- top:-20px;
- left:0;
- background: #bd362f;
- color: white;
- font-size:9px;
- font-weight: normal;
- font-style: normal;
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
- padding: 0 5px;
- text-indent: 0;
-}
-
-[document-node-element] {
- position:relative;
- border: 1px solid white;
-}
\ No newline at end of file
+++ /dev/null
-[document-text-element] {
- font-size: 14px;
- display: inline;
- margin: 0 1px;
- border: 1px solid white;
- white-space: pre-wrap;
-}
-
-[wlxml-tag] {
- float: none !important; /* temporaty workaround for Bootstrap's influence via [class*="span"] { float: left; } */
- border-color: white;
- border-style:solid;
- border-width:1px;
- min-height:20px;
- position:relative;
- text-indent: 0;
- padding: 1px 1px;
-}
-
-[wlxml-tag=span] {
- min-width: 10px;
-}
-
-[wlxml-tag=header] [document-text-element] {
- font-size: 13px;
- font-weight: bold;
- margin: 10px 0;
-}
-
-[wlxml-tag=section] {
- margin-top: 10px;
- margin-bottom: 10px;
-}
-
-[wlxml-tag=section] [wlxml-tag=section] {
- margin-left:10px;
-}
-
-[wlxml-class|="cite"] {
- font-style: italic;
-}
-
-[wlxml-class|="cite-code"] {
- font-family: monospace;
-}
-
-[wlxml-class|="cite-code-xml"] {
- color: blue;
-}
-
-[wlxml-tag=header] [wlxml-class=author] [document-text-element] {
- font-size: 14px;
-}
-
-[wlxml-tag=header] [wlxml-class=title] [document-text-element] {
- font-size:18px;
-}
-
-[wlxml-class|="uri"] {
- color: blue;
- text-decoration: underline;
-}
-
-[wlxml-class|="p"] {
- text-indent: 1.5em;
-}
-
-[wlxml-class|="emph-tech"] {
- font-style: italic;
-}
-
-[wlxml-tag=metadata] {
- display:none;
-}
-
-[wlxml-class="list-items"] {
-
- [wlxml-class="item"] {
- display: list-item;
- margin-left: 10px;
- padding-left: 5px;
- }
-}
-
-[wlxml-class="item"] {
- [wlxml-class="list-items"] {
- display: block;
- }
-}
-
-
-[wlxml-class="list-items-enum"] {
-
- counter-reset: myitem;
-
- > [wlxml-class="item"] {
- counter-increment: myitem;
- margin-left: 10px;
- padding-left: 5px;
-
- &:before {
- content: counter(myitem) '. ';
- margin-right:10px;
- padding-right:10px;
- }
- }
-}
-
-.canvas-silent-item {
- display: block !important;
- counter-increment: none !important;
- &:before {
- content: normal !important;
- }
-}
-
-[wlxml-class="table"] {
-
- display: table;
- border: 1px solid black;
-
- [wlxml-class="row"] {
- display: table-row;
- border: 1px solid black;
- }
- [wlxml-class="cell"] {
- display: table-cell;
- border: 1px solid black;
- padding: 5px;
- }
-
-}
-
-[wlxml-tag="aside"] {
- margin-top: 10px;
- margin-bottom: 10px;
-}
\ No newline at end of file
+++ /dev/null
-<div id="rng-module-documentCanvas">
- <div id="rng-module-documentCanvas-mainArea">
- <div id="rng-module-documentCanvas-contentWrapper">
- <div id="rng-module-documentCanvas-content"></div>
- </div>
- </div>
-</div>
\ No newline at end of file
+++ /dev/null
-define([
-'libs/chai',
-'modules/documentCanvas/classAttributes'
-], function(chai, classAttributes) {
-
-var stubDict = {
- 'klass': {
- 'prop': 'string'
- },
- 'klass.sub1': {
- 'prop1': 'string'
- },
- 'klass.sub1.sub2': {
- 'prop2': 'string'
- }
-};
-
-var assert = chai.assert;
-
-describe('Class attributes', function() {
- it('class has own attribute', function() {
- assert.ok(classAttributes.hasMetaAttr('klass.sub1.sub2', 'prop2', stubDict));
- });
-
- it('class has attributes from parent classes', function() {
- assert.ok(classAttributes.hasMetaAttr('klass.sub1.sub2', 'prop', stubDict));
- assert.ok(classAttributes.hasMetaAttr('klass.sub1.sub2', 'prop1', stubDict));
- });
-
- it('list of class meta attributes', function() {
- var attrList = classAttributes.getMetaAttrsList('klass.sub1.sub2', stubDict);
-
- assert.deepEqual(attrList.own, [{name: 'prop2', type: 'string'}]);
- assert.deepEqual(attrList.inheritedFrom['klass.sub1'], [{name: 'prop1', type: 'string'}]);
- assert.deepEqual(attrList.inheritedFrom.klass, [{name: 'prop', type: 'string'}]);
- assert.deepEqual(attrList.all.sort(), [
- {name: 'prop', type: 'string'},
- {name: 'prop1', type: 'string'},
- {name: 'prop2', type: 'string'}
- ].sort(), 'all values');
- });
-
- it('class without meta attrs', function() {
- var attrList = classAttributes.getMetaAttrsList('some.class', {});
- assert.deepEqual(attrList.own, [], 'empty own list');
- assert.deepEqual(attrList.inheritedFrom, {}, 'empty inherited dict');
- assert.deepEqual(attrList.all, [], 'empty all list');
- });
-
- it('empty class', function() {
- var attrList = classAttributes.getMetaAttrsList('', {});
- assert.deepEqual(attrList.own, [], 'empty own list');
- assert.deepEqual(attrList.inheritedFrom, {}, 'empty inherited dict');
- assert.deepEqual(attrList.all, [], 'empty all list');
- });
-});
-
-});
\ No newline at end of file
+++ /dev/null
-define([
-'libs/jquery',
-'libs/underscore',
-'./restoreDialog',
-'libs/text!./templates/main.html',
-'libs/text!./templates/item.html'
-], function($, _, restoreDialog, mainTemplateSrc, itemTemplateSrc) {
-
-'use strict';
-
-return function(sandbox) {
-
- var dom = $(_.template(mainTemplateSrc)());
- var domNodes = {
- itemList: dom.find('.rng-module-documentHistory-itemsList'),
- };
- var itemViews = [];
-
-
- dom.find('.btn.compare').click(function(e) {
- var selected = historyItems.getSelected();
- sandbox.publish('compare', selected[0], selected[1]);
- });
-
- dom.find('.btn.restore').click(function(e) {
- var dialog = restoreDialog.create();
- dialog.on('restore', function(event) {
- sandbox.publish('restoreVersion', {version: historyItems.getSelected()[0], description: event.data.description});
- event.success();
- });
- dialog.show();
- });
-
- dom.find('.btn.display').click(function(e) {
- sandbox.publish('displayVersion', {version: historyItems.getSelected()[0]});
- });
-
- var addHistoryItem = function(item, options) {
- historyItems.add(item);
- var view = new ItemView(item);
- itemViews.push(view);
- domNodes.itemList.prepend(view.dom);
- if(options.animate) {
- view.dom.hide().slideDown();
- }
- };
-
- var toggleItemViews = function(toggle) {
- itemViews.forEach(function(view) {
- if(!historyItems.isSelected(view.item))
- view.toggle(toggle);
- });
- };
-
- var toggleButton = function(btn, toggle) {
- dom.find('button.'+btn).toggleClass('disabled', !toggle);
- };
-
- var historyItems = {
- _itemsById: {},
- _selected: [],
- select: function(item) {
- if(this._selected.length < 2) {
- this._selected.push(item.version);
- this._updateUI();
- return true;
- }
- return false;
- },
- unselect: function(item) {
- this._selected = _.without(this._selected, item.version);
- this._updateUI();
- },
- add: function(item) {
- this._itemsById[item.version] = item;
- },
- isSelected: function(item) {
- return _.contains(this._selected, item.version);
- },
- getSelected: function() {
- return this._selected;
- },
- _updateUI: function() {
- var len = this._selected.length;
- if(len === 0) {
- toggleButton('compare', false);
- toggleButton('display', false);
- toggleButton('restore', false);
- }
- if(len === 1) {
- toggleButton('compare', false);
- toggleButton('display', true);
- toggleButton('restore', true);
- }
- if(len === 2) {
- toggleItemViews(false);
- toggleButton('compare', true);
- toggleButton('display', false);
- toggleButton('restore', false);
- } else {
- toggleItemViews(true);
- }
- }
- };
- historyItems._updateUI();
-
- var ItemView = function(item) {
- this.item = item;
- this.dom = $(this.template(item));
- this.dom.on('click', _.bind(this.onItemClicked, this));
- };
- ItemView.prototype.template = _.template(itemTemplateSrc);
- ItemView.prototype.onItemClicked = function() {
- if(historyItems.isSelected(this.item)) {
- historyItems.unselect(this.item);
- this.dimItem();
- } else if(historyItems.select(this.item)) {
- this.highlightItem();
- }
- };
- ItemView.prototype.highlightItem = function() {
- this.dom.addClass('highlighted');
- };
- ItemView.prototype.dimItem = function() {
- this.dom.removeClass('highlighted');
- };
- ItemView.prototype.toggle = function(toggle) {
- this.dom.toggleClass('disabled', !toggle);
- };
-
-
-
- return {
- start: function() { sandbox.publish('ready'); },
- addHistory: function(history, options) {
- history.forEach(function(historyItem) {
- addHistoryItem(historyItem, options || {});
- });
- },
- getView: function() {
- return dom;
- }
- };
-};
-
-});
\ No newline at end of file
+++ /dev/null
-.rng-module-documentHistory {
-
- .item {
- padding: 5px 5px;
- margin: 0 0 15px 0;
- cursor: pointer;
-
- .version {
- float: left;
- width: 30px;
- font-weight: bold;
- }
-
- .date, .author, .description {
- margin: 5px 10px 0 40px;
- }
-
- .description {
- font-size: .9em;
- }
- }
-
- .item.highlighted {
- background: #ffec63;
- }
-
- .item.disabled {
- cursor: default;
- }
-
- .toolbar {
- margin: 0 0 15px 0;
- white-space:nowrap;
- word-spacing:0;
- min-height: 22px;
- button {
- margin-right: 10px;
- }
- }
-
- .rng-module-documentHistory-itemsList {
- overflow-y: scroll;
- position: absolute;
- top: 30px;
- bottom: 0;
- left: 0;
- right: 0;
- &::-webkit-scrollbar {
- .rng-mixin-scrollbar;
- }
- &::-webkit-scrollbar-track {
- .rng-mixin-scrollbar-track;
- }
- &::-webkit-scrollbar-thumb {
- .rng-mixin-scrollbar-thumb;
- }
- &::-webkit-scrollbar-thumb:window-inactive {
- .rng-mixin-scrollbar-thumb-window-inactive;
- }
- }
-}
-
+++ /dev/null
-define([
-'libs/text!./templates/restoreDialog.html',
-'libs/underscore',
-'libs/backbone',
-'libs/jquery'
-], function(restoreDialogTemplate, _, Backbone, $) {
-
- var DialogView = Backbone.View.extend({
- template: _.template(restoreDialogTemplate),
- events: {
- 'click .restore-btn': 'onSave',
- 'click .cancel-btn': 'close',
- 'click .close': 'close'
- },
- initialize: function() {
- _.bindAll(this);
- this.actionsDisabled = false;
- },
- show: function() {
- this.setElement(this.template());
- this.$el.modal({backdrop: 'static'});
- this.$el.modal('show');
- this.$('textarea').focus();
- },
- onSave: function(e) {
- e.preventDefault();
- var view = this;
- this.trigger('restore', {
- data: {description: view.$el.find('textarea').val()},
- success: function() { view.actionsDisabled = false; view.close(); },
- error: function() { view.actionsDisabled = false; view.close(); },
- });
- },
- close: function(e) {
- if(e)
- e.preventDefault();
- if(!this.actionsDisabled) {
- this.$el.modal('hide');
- this.$el.remove();
- }
- },
- toggleButtons: function(toggle) {
- this.$('.btn, button').toggleClass('disabled', !toggle);
- this.$('textarea').attr('disabled', !toggle);
- this.actionsDisabled = !toggle;
- }
- });
-
- return {
- create: function() {
- return new DialogView();
- }
- };
-
-});
\ No newline at end of file
+++ /dev/null
-<div class="item">
- <div class="version"><%= version %></div>
- <div class="date"><%= date %></div>
- <div class="author"><%= author %></div>
- <div class="description"><%= description %></div>
-</div>
\ No newline at end of file
+++ /dev/null
-<div class="rng-module-documentHistory">
- <div class="toolbar">
- <div class="group">
- <button class="btn btn-mini compare">Porównaj</button>
- <button class="btn btn-mini restore">Przywróć</button>
- <button class="btn btn-mini display">Zobacz</button>
- </div>
- </div>
- <div style="clear:both;"></div>
- <div class="rng-module-documentHistory-itemsList"></div>
-<div>
\ No newline at end of file
+++ /dev/null
-<div class="rng-module-data-saveDialog modal hide static">
- <div class="modal-header">
- <button type="button" class="close">×</button>
- <h1>Przywróć wersję</h1>
- </div>
- <div class="modal-body">
- <label>Opisz powód przywrócenia</label>
- <textarea rows="5"></textarea>
- </div>
- <div class="modal-footer">
- <a href="#" class="btn btn-info btn-mini restore-btn">Przywróć</a>
- <a href="#" class="btn btn-danger btn-mini cancel-btn">Anuluj</a>
- </div>
-</div>
\ No newline at end of file
+++ /dev/null
-define(['libs/jquery', 'libs/underscore', 'utils/wlxml', 'libs/text!./template.html'], function($, _, wlxmlUtils, template) {
-
-'use strict';
-
-return function(sandbox) {
-
- var view = {
- node: $(_.template(template)({tagNames: wlxmlUtils.wlxmlTagNames, classNames: wlxmlUtils.wlxmlClassNames})),
- setup: function() {
- var view = this;
-
- 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(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;
- }
-
- sandbox.publish('command', command, params);
- });
- },
- getOption: function(option) {
- return this.node.find('.rng-module-documentToolbar-toolbarOption[data-option=' + option +']').val();
- }
- };
-
- view.setup();
-
- return {
- start: function() { sandbox.publish('ready'); },
- getView: function() { return view.node; },
- getOption: function(option) { return view.getOption(option); }
- };
-};
-
-});
\ No newline at end of file
+++ /dev/null
-.rng-module-documentToolbar {
- margin: 0;
- white-space:nowrap;
- word-spacing:0;
-
- select {
- line-height: 14px;
- font-size:9px;
- height: auto;
- width: 50px;
- padding: 1px;
- -webkit-appearance: button;
- -moz-appearance: button;
- appearance: button;
- margin-bottom: 0;
- }
-
- .rng-module-documentToolbar-toolbarGroup {
- border-width: 0 1px 0 0;
- border-style: solid;
- border-color: #ddd;
- padding: 0 8px 0 0;
- margin: 0 8px 0 0;
- float:left;
- }
-
-}
-
+++ /dev/null
-<div class="rng-module-documentToolbar">
- <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="new-node" data-meta="div/p" data-btn-type="cmd" class="btn btn-mini">paragraf</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/uri" data-btn-type="cmd" class="btn btn-mini">uri</i></button>
- <select class="rng-module-documentToolbar-toolbarOption" data-option="newTag-tag">
- <% var options = ['', 'section', 'header', 'div', 'span', 'aside']; %>
- <% options.forEach(function(option) { %>
- <option value="<%= option %>" <% if(option==='span') { %>selected<% } %>><%= tagNames[option] %></option>
- <% }); %>
- </select>
- <select class="rng-module-documentToolbar-toolbarOption" data-option="newTag-class">
- <% var options = ['', 'author', 'title', 'cite', 'cite.code', 'cite.code.xml', 'list.items', 'item', 'uri', 'p', 'footnote', 'todo', 'emp'] %>
- <% options.forEach(function(option) { %>
- <option value="<%= option.replace(/\./g, '-') %>" <% if(option==='cite') { %>selected<% } %>><%= classNames[option] %></option>
- <% }); %>
- </select>
- <button data-name="new-node" data-btn-type="cmd" class="btn btn-mini"><i class="icon-plus"></i></button>
- </div>
- <div class="rng-module-documentToolbar-toolbarGroup">
- <button data-name="take-away-node" data-btn-type="cmd" class="btn btn-mini"><i class="icon-remove-circle"></i></button>
- </div>
- <div class="rng-module-documentToolbar-toolbarGroup">
- <button data-name="list" data-btn-type="cmd" data-meta="bullets" class="btn btn-mini"><i class="icon-list"></i></button>
- <button data-name="unwrap-node" data-btn-type="cmd" class="btn btn-mini"><i class="icon-arrow-up"></i></button>
- <button data-name="wrap-node" data-btn-type="cmd" class="btn btn-mini"><i class="icon-arrow-down"></i></button>
- </div>
- <div class="rng-module-documentToolbar-toolbarGroup">
- <button data-name="footnote" data-btn-type="cmd" class="btn btn-mini"><i class="icon-comment"></i></button>
- </div>
- <div class="rng-module-documentToolbar-toolbarGroup">
- <button data-name="grid" data-btn-type="toggle" class="btn btn-mini"><i class="icon-th-large"></i></button>
- </div>
-
- <div style="clear: both;"></div>
-</div>
\ No newline at end of file
+++ /dev/null
-define([
-'libs/jquery',
-'libs/underscore',
-'libs/text!./template.html'
-], function($, _, template) {
-
-'use strict';
-
-return function(sandbox) {
-
- var view = {
- dom: $(_.template(template)()),
- setup: function() {
-
- }
- };
-
- return {
- start: function() { sandbox.publish('ready'); },
- getView: function() { return view.dom; },
- showMessage: function(msg) {
- view.dom.html('<span>' + msg + '</span>').show();
- },
- clearMessage: function(report) {
- view.dom.empty();
- if(report && report.message) {
- view.dom.html('<span class="success">' + report.message + '</span>').show().fadeOut(4000);
- }
- }
-
- };
-
-};
-
-});
+++ /dev/null
-.rng-module-indicator {
- span {
- font-weight: bold;
- background: #f9edbe;
- padding: 2px 5px;
- border: solid 1px #f6e39c;
- font-size:12px;
- }
-
- span.success {
- background: #cef9be;
- border-color: darken(#cef9be, 10%);
- }
-}
\ No newline at end of file
+++ /dev/null
-<div class="rng-module-indicator">
-
-</div>
\ No newline at end of file
+++ /dev/null
-define([
-'libs/jquery',
-'libs/underscore',
-'libs/text!./template.html'], function($, _, template) {
-
-'use strict';
-
-return function(sandbox) {
-
- var view = $(_.template(template)());
- view.find('button').click(function(e) {
- e.preventDefault();
- sandbox.publish('cmd.' + $(e.target).attr('data-cmd'));
- });
-
- return {
- start: function() { sandbox.publish('ready'); },
- getView: function() {return view;},
- setCommandEnabled: function(cmd, enabled) {
- view.find('[data-cmd='+cmd+']').toggleClass('disabled', !enabled);
- },
- setVersion: function(version) {
- view.find('.version').text(version);
- }
- };
-
-};
-
-});
\ No newline at end of file
+++ /dev/null
-/*#rng-skelton-menu {
- float: right;
-}*/
-
-.rng-module-mainBar {
-
- font-size: 10px;
-
- li {
- display: inline;
- border-width: 0 1px 0 0;
- border-color: #ddd;
- border-style: solid;
- margin: 0 5px 0 0;
- padding: 0 5px 0 0;
- }
-
- ul {
- list-style-type: none;
- }
-}
\ No newline at end of file
+++ /dev/null
-<div class="rng-module-mainBar">
- <ul>
- <li>wersja: <span class="version"></span></li>
- <li><button class="btn btn-mini btn-info" data-cmd="save">Zapisz</button></li>
- <li><a class="btn btn-mini btn-danger" href="/">Wyjdź</a></li>
- </ul>
-</div>
\ No newline at end of file
+++ /dev/null
-define([
-'libs/jquery',
-'libs/underscore',
-'./transformations',
-'libs/text!./templates/main.html',
-'libs/text!./templates/item.html'
-], function($, _, transformations, mainTemplate, itemTemplate) {
-
-'use strict';
-
-return function(sandbox) {
-
-
- var view = {
- node: $(_.template(mainTemplate)()),
- setup: function() {
- var view = this;
- var metaTable = this.metaTable = this.node.find('table');
-
- this.node.find('.rng-module-metadataEditor-addBtn').click(function() {
- var newRow = view._addMetaRow('', '');
- $(newRow.find('td div')[0]).focus();
- sandbox.publish('metadataChanged', view.getMetadata());
- });
-
- this.metaTable.on('click', '.rng-visualEditor-metaRemoveBtn', function(e) {
- $(e.target).closest('tr').remove();
- sandbox.publish('metadataChanged', view.getMetadata());
- });
-
- this.metaTable.on('keydown', '[contenteditable]', function(e) {
- console.log(e.which);
- if(e.which === 13) {
- if($(document.activeElement).hasClass('rng-module-metadataEditor-metaItemKey')) {
- metaTable.find('.rng-module-metadataEditor-metaItemValue').focus();
- } else {
- var input = $('<input>');
- input.appendTo('body').focus();
- view.node.find('.rng-module-metadataEditor-addBtn').focus();
- input.remove();
- }
- e.preventDefault();
- }
- });
-
-
- var onKeyUp = function(e) {
- if(e.which !== 13)
- sandbox.publish('metadataChanged', view.getMetadata());
- };
- this.metaTable.on('keyup', '[contenteditable]', _.throttle(onKeyUp, 500));
- },
- getMetadata: function() {
- var toret = {};
- this.node.find('tr').each(function() {
- var tr = $(this);
- var inputs = $(this).find('td [contenteditable]');
- var key = $(inputs[0]).text();
- var value = $(inputs[1]).text();
- toret[key] = value;
- });
- return toret;
- },
- setMetadata: function(metadata) {
- var view = this;
- this.metaTable.find('tr').remove();
- _.each(_.keys(metadata), function(key) {
- view._addMetaRow(key, metadata[key]);
- });
- },
- _addMetaRow: function(key, value) {
- var newRow = $(_.template(itemTemplate)({key: key || '', value: value || ''}));
- newRow.appendTo(this.metaTable);
- return newRow;
- }
- };
-
- view.setup();
-
- return {
- start: function() {
- sandbox.publish('ready');
- },
- setDocument: function(xml) {
- view.setMetadata(transformations.getMetadata(xml));
- sandbox.publish('metadataSet');
- },
- getMetadata: function() {
- return transformations.getXML(view.getMetadata());
- },
- getView: function() {
- return view.node;
- },
- attachMetadata: function(document) {
- var toret = $('<div>');
- toret.append($(document));
- var meta = $('<metadata></metadata>\n').append(transformations.getXML(view.getMetadata()));
-
- var metadata = toret.find('metadata');
- if(metadata.length === 0) {
- var section = toret.find('section');
- section = section.length ? $(section[0]) : null;
- if(section) {
- section.prepend(meta);
- }
- } else {
- metadata.replaceWith(meta);
- }
- return toret.html();
- }
-
- };
-};
-
-});
\ No newline at end of file
+++ /dev/null
-.rng-module-metadataEditor {
-
- table {
- margin-bottom:10px;
-
- [contenteditable] {
- cursor: pointer;
- }
-
- li:last-child {
- border-bottom: none !important;
- }
-
- tr td:nth-child(1){
- width: 20%;
- }
-
- tr td:nth-child(2) {
- width:80%;
- }
- }
-
- .rng-module-metadataEditor-addBtn {
- float:right;
- margin-right:6px;
- }
-
- .btn {
- padding:3px;
- line-height:10px;
- }
-}
\ No newline at end of file
+++ /dev/null
-<tr>
- <td><div contenteditable="true" class="rng-module-metadataEditor-metaItemKey"><%= key %></div></td>
- <td><div contenteditable="true" class="rng-module-metadataEditor-metaItemValue"><%= value %></div></td>
- <td><button class="rng-visualEditor-metaRemoveBtn btn btn-mini btn-danger">x</button></td>
-</tr>
\ No newline at end of file
+++ /dev/null
-<div class="rng-module-metadataEditor">
- <fieldset>
- <legend>Meta dane</legend>
- <table class="table table-condensed table-bordered">
- </table>
- <button class="rng-module-metadataEditor-addBtn btn btn-mini btn-info">+</button></td>
- </fieldset>
-</div>
\ No newline at end of file
+++ /dev/null
-define(['libs/jquery'], function($) {
-
- 'use strict';
-
- return {
- getMetadata: function(xml) {
- var toret = {};
- $(xml).find('metadata').children().each(function() {
- var node = $(this);
- toret[this.nodeName.split(':')[1].toLowerCase()] = node.text();
- });
- return toret;
- },
- getXML: function(metadata) {
- var meta = $('<metadata></metadata>\n');
- _.each(_.keys(metadata), function(key) {
- meta.append('\n\t<dc:'+key+'>' + metadata[key] + '</dc:'+key+'>');
- });
- meta.append('\n');
- return vkbeautify.xml(meta.html());
- }
- };
-
-});
\ No newline at end of file
+++ /dev/null
-define([
-'libs/jquery',
-'libs/underscore',
-'utils/wlxml',
-'libs/text!./template.html'], function($, _, wlxmlUtils, templateSrc) {
-
-'use strict';
-
-return function(sandbox) {
-
- var template = _.template(templateSrc);
-
- var view = {
- dom: $('<div>' + template({node:null, parents: null}) + '</div>'),
- setup: function() {
- var view = this;
- this.dom.on('mouseenter', 'a', function(e) {
- var target = $(e.target);
- sandbox.publish('elementEntered', target.data('element'));
- });
- this.dom.on('mouseleave', 'a', function(e) {
- var target = $(e.target);
- sandbox.publish('elementLeft', target.data('element'));
- });
- this.dom.on('click', 'a', function(e) {
- e.preventDefault();
- var target = $(e.target);
- sandbox.publish('elementClicked', target.data('element'));
- });
- },
-
- setNodeElement: function(nodeElement) {
- this.dom.empty();
- this.currentNodeElement = nodeElement;
- var parents = nodeElement.parents();
- this.dom.html(template({node: nodeElement, parents: parents, tagNames: wlxmlUtils.wlxmlTagNames, classNames: wlxmlUtils.wlxmlClassNames}));
-
- this.dom.find('li > a[href="#"]').each(function(idx, a) {
- $(a).data('element', parents[parents.length - 1 - idx]);
- });
- this.dom.find('a.active').data('element', nodeElement);
- },
-
- highlightNode: function(node) {
- this.dom.find('a[data-id="'+node.id+'"]').addClass('rng-common-hoveredNode');
- },
- dimNode: function(node) {
- this.dom.find('a[data-id="'+node.id+'"]').removeClass('rng-common-hoveredNode');
- }
- };
-
- view.setup();
-
- return {
- start: function() { sandbox.publish('ready'); },
- getView: function() { return view.dom; },
- setNodeElement: function(nodeElement) {
- view.setNodeElement(nodeElement);
- },
- highlightNode: function(id) { view.highlightNode(id); },
- dimNode: function(id) { view.dimNode(id); }
- };
-};
-
-});
\ No newline at end of file
+++ /dev/null
-<div class="rng-module-nodeBreadCrumbs">
- <ul class="breadcrumb">
- <% if(node && parents) { %>
- <% for(var i = parents.length - 1; i >= 0; i--) { %>
- <li><a href="#"> <%= tagNames[parents[i].getWlxmlTag()] %><% if(parents[i].getWlxmlClass()) { %>.<%= classNames[parents[i].getWlxmlClass()] %> <% } %></a><span class="divider">/</span></li>
- <% } %>
- <li class="active"><%= tagNames[node.getWlxmlTag()] %><% if(node.getWlxmlClass()) { %>.<%= classNames[node.getWlxmlClass()] %> <% } %></span></li>
- <% } %>
- </ul>
-</div>
\ No newline at end of file
+++ /dev/null
-define([
-'libs/jquery',
-'libs/underscore',
-'utils/wlxml',
-'libs/text!./template.html'
-], function($, _, wlxmlUtils, templateSrc) {
-
-'use strict';
-
-return function(sandbox) {
-
- var template = _.template(templateSrc);
-
- var view = {
- dom: $('<div>' + template({children: null, parent: null}) + '</div>'),
- setup: function() {
- var view = this;
- this.dom.on('click', 'a', function(e) {
- var target = $(e.target);
- sandbox.publish('elementClicked', target.data('element'));
- });
-
- this.dom.on('mouseenter', 'a', function(e) {
- var target = $(e.target);
- sandbox.publish('elementEntered', target.data('element'));
- });
- this.dom.on('mouseleave', 'a', function(e) {
- var target = $(e.target);
- sandbox.publish('elementLeft', target.data('element'));
- });
- },
- setElement: function(element) {
- console.log('familyTree sets node');
- var textElement = element.getText ? element : null,
- nodeElement = element.getText ? element.parent() : element, // TODO: better type detection
- nodeElementParent = nodeElement.parent(),
- parent;
-
- this.currentNodeElement = nodeElement;
-
- if(nodeElementParent) {
- parent = {
- repr: wlxmlUtils.wlxmlTagNames[nodeElementParent.getWlxmlTag()] + (nodeElementParent.getWlxmlClass() ? ' / ' + wlxmlUtils.wlxmlClassNames[nodeElementParent.getWlxmlClass()] : '')
- };
- }
-
- var nodeChildren = nodeElement.children(),
- children = [];
- nodeChildren.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 + '"';
- }
- children.push({repr: _.escape(text), bold: child.sameNode(textElement)});
- } else {
- children.push({repr: wlxmlUtils.wlxmlTagNames[child.getWlxmlTag()] + (child.getWlxmlClass() ? ' / ' + wlxmlUtils.wlxmlClassNames[child.getWlxmlClass()] : '')});
- }
- });
- this.dom.empty();
- this.dom.append($(template({parent: parent, children: children})));
-
- if(parent) {
- this.dom.find('.rng-module-nodeFamilyTree-parent').data('element', nodeElementParent)
- }
- this.dom.find('li a').each(function(idx, a) {
- $(a).data('element', nodeChildren[idx]);
- });
- },
- highlightNode: function(canvasNode) {
- this.dom.find('a[data-id="'+canvasNode.getId()+'"]').addClass('rng-common-hoveredNode');
- },
- dimNode: function(canvasNode) {
- this.dom.find('a[data-id="'+canvasNode.getId()+'"]').removeClass('rng-common-hoveredNode');
- }
- };
-
- view.setup();
-
- return {
- start: function() {
- sandbox.publish('ready');
- },
- setElement: function(element) {
- if(!(element.sameNode(view.currentNodeElement)))
- view.setElement(element);
- },
- getView: function() {
- return view.dom;
- },
- highlightNode: function(canvasNode) {
- view.highlightNode(canvasNode);
- },
- dimNode: function(canvasNode) {
- view.dimNode(canvasNode);
- }
- };
-};
-
-});
\ No newline at end of file
+++ /dev/null
-.rng-module-nodeFamilyTree {
- overflow-y: scroll;
- max-height: 150px;
- width:100%;
- margin-top:10px;
-
- table {
- width: 90%;
- margin: 0;
-
- tr {
- td:nth-child(1) {
- width: 30%;
- }
- td:nth-child(2) {
- width: 70%;
- }
- td ul {
- list-style-type: none;
- margin: 0;
- }
-
- }
- }
-
- &::-webkit-scrollbar {
- .rng-mixin-scrollbar;
- }
- &::-webkit-scrollbar-track {
- .rng-mixin-scrollbar-track;
- }
- &::-webkit-scrollbar-thumb {
- .rng-mixin-scrollbar-thumb;
- }
- &::-webkit-scrollbar-thumb:window-inactive {
- .rng-mixin-scrollbar-thumb-window-inactive;
- }
-}
\ No newline at end of file
+++ /dev/null
-<div class="rng-module-nodeFamilyTree">
- <table class="table table-bordered">
- <tr>
- <td>powyżej</td>
- <td><% if(parent) { %><a href="#" class="rng-module-nodeFamilyTree-parent"><%= parent.repr %></a><% } else { %>-<% } %></td>
- </tr>
- <tr>
- <td rowspan="0">poniżej</td>
- <td>
- <ul>
- <% if(!children || children.length === 0) { %>-<% } else { %>
- <% children.forEach(function(child) { %>
- <li><% if(child.bold) { %><strong><% } %><a href="#"><%= child.repr %></a><% if(child.bold) { %></strong><% } %></li>
- <% }); %>
- <% } %>
- </ul>
- </td>
- </tr>
-</div>
\ No newline at end of file
+++ /dev/null
-define([
-'libs/jquery',
-'libs/underscore',
-'libs/backbone',
-'libs/text!./stringField.html'
-], function($, _, Backbone, stringFieldTpl) {
-
-'use strict';
-
-var templates = {
- string: _.template(stringFieldTpl)
-};
-
-var getAttrElement = function(attr) {
- var toret = $('<div>');
- toret.append(templates.string({name: attr.name, value: attr.value}));
- return toret;
-};
-
-var MetaWidget = Backbone.View.extend({
- events: {
- 'change [metaField-name]': 'onMetaFieldChange'
- },
- initialize: function() {
- var view = this;
- this.options.attrs.forEach(function(attr) {
- view.$el.append(getAttrElement(attr));
- });
- },
- onMetaFieldChange: function(e) {
- var target = $(e.target);
- this.trigger('valueChanged', target.attr('metaField-name'), target.val());
- }
-});
-
-
-return {
- create: function(options) {
- return new MetaWidget(options);
- }
-};
-
-});
\ No newline at end of file
+++ /dev/null
-define([
-'libs/chai',
-'libs/sinon',
-'modules/nodePane/metaWidget/metaWidget'
-], function(chai, sinon, metaWidget) {
-
-'use strict';
-
-var assert = chai.assert;
-
-describe('metaWidget', function() {
- it('calls calls registered callback on value change', function() {
- var dom = $('<div>');
- var widget = metaWidget.create({
- el: dom,
- attrs: [{name: 'uri', type: 'string', value: 'test string'}],
- });
-
- var spy = sinon.spy();
- widget.on('valueChanged', spy);
- var input = dom.find('input');
-
- input.change();
- assert.ok(spy.calledOnce, 'called once');
- assert.ok(spy.calledWith('uri', 'test string'), 'called with');
-
- spy.reset();
- input.val('new val').change();
- assert.ok(spy.calledOnce, 'called once');
- assert.ok(spy.calledWith('uri', 'new val'), 'called with new val');
- });
-});
-
-
-});
\ No newline at end of file
+++ /dev/null
-<label><%= name %></label>
-<input type="text" metaField-name="<%= name %>" value="<%= value %>">
\ No newline at end of file
+++ /dev/null
-define([
-'libs/text!./template.html',
-'libs/jquery',
-'libs/underscore',
-'modules/nodePane/metaWidget/metaWidget',
-'utils/wlxml'
-], function(templateSrc, $, _, metaWidget, wlxmlUtils) {
-
-'use strict';
-
-return function(sandbox) {
-
- var view = $(_.template(templateSrc)({tagNames: wlxmlUtils.wlxmlTagNames, classNames: wlxmlUtils.wlxmlClassNames}));
-
- view.on('change', 'select', function(e) {
- var target = $(e.target);
- var attr = target.attr('class').split('-')[3] === 'tagSelect' ? 'tag' : 'class';
- sandbox.publish('nodeElementChange', attr, target.val().replace(/-/g, '.'));
- });
-
- return {
- start: function() {
- sandbox.publish('ready');
- },
- getView: function() {
- return view;
- },
- setNodeElement: function(nodeElement) {
- view.find('.rng-module-nodePane-tagSelect').val(nodeElement.getWlxmlTag());
-
- var escapedClassName = (nodeElement.getWlxmlClass() || '').replace(/\./g, '-')
- view.find('.rng-module-nodePane-classSelect').val(escapedClassName);
-
- var widget = metaWidget.create({attrs:nodeElement.getWlxmlMetaAttrs()});
- widget.on('valueChanged', function(key, value) {
- sandbox.publish('nodeElementChange', key, value);
- });
- view.find('.metaFields').empty().append(widget.el);
- }
- };
-
-};
-
-});
\ No newline at end of file
+++ /dev/null
-.rng-module-nodePane {
- label {
- width: 50px;
- display: inline-block;
- }
- select {
- width: 100px;
- }
-
- input {
- width: 80px;
- padding: 0 10px;
- }
-}
\ No newline at end of file
+++ /dev/null
-<div class="rng-module-nodePane">
- <fieldset>
- <legend><%= gettext('Current node') %></legend>
- <div>
- <label>Tag</label>
- <select class="rng-module-nodePane-tagSelect">
- <% var options = ['', 'section', 'header', 'div', 'span', 'aside']; %>
- <% options.forEach(function(option) { %>
- <option value="<%= option %>" <% if(option === '') { %>selected<% } %>><%= tagNames[option] %></option>
- <% }); %>
- </select>
- </div>
- <div>
- <label>Klasa</label>
- <select class="rng-module-nodePane-classSelect">
- <% var options = ['', 'author', 'title', 'cite', 'cite.code', 'cite.code.xml', 'list.items', 'list.items.enum', 'item', 'uri', 'p', 'footnote', 'todo', 'emp'] %>
- <% options.forEach(function(option) { %>
- <option value="<%= option.replace(/\./g, '-') %>" <% if(option === '') { %>selected<% } %>><%= classNames[option] %></option>
- <% }); %>
- </select>
- </div>
- <div class="metaFields">
- </div>
- </fieldset>
-</div>
\ No newline at end of file
+++ /dev/null
-<div class="rng-module-rng-diffLayout">
- <div fnpjs-place="left"></div>
- <div fnpjs-place="right"></div>
-</div>
\ No newline at end of file
+++ /dev/null
-.rng-module-rng-diffLayout {
- [fnpjs-place=left] {
- width: 300px;
- margin: 0 20px 0 0px;
- left: 0;
- }
- [fnpjs-place=right] {
- left: 320px;
- right: 0;
- }
-
- [fnpjs-place=left], [fnpjs-place=right] {
- position: absolute;
- top: 0;
- bottom: 0;
-
-
- }
-
-}
\ No newline at end of file
+++ /dev/null
-<div class="fnp-module-rng-editingLayout">
- <div fnpjs-place="toolbar"></div>
- <div class="rng-module-rng2-left" fnpjs-place="leftColumn"></div>
- <div class="rng-module-rng2-right" fnpjs-place="rightColumn"></div>
- <div style="clear:both;"></div>
- <div class="rng-module-rng2-statusBar" fnpjs-place="statusBar"></div>
-</div>
\ No newline at end of file
+++ /dev/null
-.rng-module-rng2-left {
- /*float: left;
- width: 600px;*/
-}
-
-.rng-module-rng2-right {
- /*float: right;
- position: relative;
- width: 258px;
- margin-left: 50px;*/
-
- border-width: 1px 1px 1px 1px;
- border-style: solid;
- border-color: #ddd;
- padding: 5px 15px;
-
- p, td, label, input, select {
- font-size: 11px;
- line-height:13px;
- }
-
- select {
- -webkit-appearance: button;
- -moz-appearance: button;
- appearance: button;
- height: auto;
- line-height: 14px;
- }
-
- legend {
- font-size:11px;
- height:30px;
- }
-
-
- .rng-view-tabs-tabBar {
- position:absolute;
- top:-1px;
- right:-50px;
- border-width: 1px 1px 1px 0px;
- border-style: solid;
- border-color: #ddd;
- padding: 5px;
- background: #ededed;
- }
-
- label + select {
- position:relative;
- top: 5px;
- }
-
-}
-
-.rng-module-rng2-statusBar {
- margin: 10px 5px;
- font-size:0.9em;
-}
-
-.fnp-module-rng-editingLayout {
-
- [fnpjs-place="statusBar"] {
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- height: 30px;
-
- }
-
- [fnpjs-place="leftColumn"], [fnpjs-place="rightColumn"] {
- position: absolute;
- top: 30px; //
- bottom: 50px; //
- }
-
- [fnpjs-place="leftColumn"] {
- left:0;
- right: 320px;
- }
-
- [fnpjs-place="rightColumn"] {
- right: 0px;
- width: 250px;
-
- }
-
-
-}
\ No newline at end of file
+++ /dev/null
-<div id="rng-module-rng-mainLayout">
- <div fnpjs-place="messages"></div>
- <div fnpjs-place="topPanel">
- </div>
- <div style="clear:both"></div>
- <div fnpjs-place="mainView">
- </div>
-</div>
\ No newline at end of file
+++ /dev/null
-#rng-module-rng-mainLayout {
- position: fixed;
- top: 5px;
- bottom: 5px;
- left: 80px;
- right: 80px;
-
- [fnpjs-place="messages"] {
- position: absolute;
- top: 5px;
- width:100%;
- text-align:center;
- }
-
- [fnpjs-place="topPanel"] {
- float: right;
- position: relative;
- z-index: 2;
- }
-
- [fnpjs-place="mainView"] {
- position: absolute;
- top: 0;
- left:0;
- right:0;
- bottom:0;
- z-index: 1;
-
- > .rng-view-tabs {
- position: relative;
- height: 100%;
-
- > .rng-view-tabs-content {
- position: absolute;
- top: 45px;
- left: 0;
- right: 0;
- bottom: 0;
- }
- }
-
- }
-}
\ No newline at end of file
+++ /dev/null
-define([
-'fnpjs/layout',
-'fnpjs/vbox',
-'views/tabs/tabs',
-'libs/text!./mainLayout.html',
-'libs/text!./editingLayout.html',
-'libs/text!./diffLayout.html',
-], function(layout, vbox, tabs, mainLayoutTemplate, visualEditingLayoutTemplate, diffLayoutTemplate) {
-
-'use strict';
-
-return function(sandbox) {
-
- function addMainTab(title, slug, view) {
- views.mainTabs.addTab(title, slug, view);
- }
-
- var dirty = {
- sourceEditor: false,
- documentCanvas: false,
- metadataEditor: false,
- };
-
- var synchronizeTab = function(slug) {
- function tabIsDirty(slug) {
- if(slug === 'editor' && (dirty.documentCanvas || dirty.metadataEditor))
- return true;
- if(slug === 'sourceEditor' && dirty.sourceEditor)
- return true;
- return false;
- }
-
- if(tabIsDirty(slug)) {
- var reason, doc;
- if(slug === 'sourceEditor') {
- doc = sandbox.getModule('sourceEditor').getDocument();
- reason = 'source_edit';
- dirty.sourceEditor = false;
- }
- if(slug === 'editor') {
- doc = dirty.documentCanvas ? sandbox.getModule('documentCanvas').getDocument() : sandbox.getModule('data').getDocument();
- if(dirty.metadataEditor) {
- doc = sandbox.getModule('metadataEditor').attachMetadata(doc);
- }
- reason = 'edit';
- dirty.documentCanvas = dirty.metadataEditor = false;
- }
- sandbox.getModule('data').commitDocument(doc, reason);
- }
- };
-
- var commands = {
- highlightDocumentElement: function(element, origin) {
- ///'nodeBreadCrumbs', 'nodeFamilyTree'
- ['documentCanvas', ].forEach(function(moduleName) {
- if(!origin || moduleName != origin)
- sandbox.getModule(moduleName).highlightElement(element);
- });
- },
- dimDocumentElement: function(element, origin) {
- //'nodeBreadCrumbs', 'nodeFamilyTree'
- ['documentCanvas'].forEach(function(moduleName) {
- if(!origin || moduleName != origin)
- sandbox.getModule(moduleName).dimElement(element);
- });
- },
- 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);
- },
- updateCurrentTextElement: function(textElement) {
- sandbox.getModule('nodeFamilyTree').setElement(textElement);
- },
- resetDocument: function(document, reason) {
- var modules = [];
- if(reason === 'source_edit')
- modules = ['documentCanvas', 'metadataEditor'];
- else if (reason === 'edit')
- modules = ['sourceEditor'];
- else if (reason === 'revert')
- modules = ['documentCanvas', 'metadataEditor', 'sourceEditor'];
-
- modules.forEach(function(moduleName) {
- sandbox.getModule(moduleName).setDocument(document);
- });
- }
- };
-
-
- var views = {
- mainLayout: new layout.Layout(mainLayoutTemplate),
- mainTabs: (new tabs.View()).render(),
- visualEditing: new layout.Layout(visualEditingLayoutTemplate),
- visualEditingSidebar: (new tabs.View({stacked: true})).render(),
- currentNodePaneLayout: new vbox.VBox(),
- diffLayout: new layout.Layout(diffLayoutTemplate)
- };
-
- views.visualEditing.setView('rightColumn', views.visualEditingSidebar.getAsView());
- addMainTab('Edytor', 'editor', views.visualEditing.getAsView());
- addMainTab(gettext('Source'), 'sourceEditor', '');
- addMainTab('Historia', 'history', views.diffLayout.getAsView());
-
- sandbox.getDOM().append(views.mainLayout.getAsView());
-
- views.visualEditingSidebar.addTab({icon: 'pencil'}, 'edit', views.currentNodePaneLayout.getAsView());
-
- views.mainTabs.on('tabSelected', function(event) {
- if(event.prevSlug) {
- synchronizeTab(event.prevSlug);
- }
- });
-
- /* Events handling */
-
- var eventHandlers = {};
-
- eventHandlers.sourceEditor = {
- ready: function() {
- addMainTab(gettext('Source'), 'sourceEditor', sandbox.getModule('sourceEditor').getView());
- sandbox.getModule('sourceEditor').setDocument(sandbox.getModule('data').getDocument());
- },
- xmlChanged: function() {
- dirty.sourceEditor = true;
- },
- documentSet: function() {
- dirty.sourceEditor = false;
- }
- };
-
- eventHandlers.data = {
- ready: function() {
- views.mainLayout.setView('mainView', views.mainTabs.getAsView());
-
- _.each(['sourceEditor', 'documentCanvas', 'documentToolbar', 'nodePane', 'metadataEditor', 'nodeFamilyTree', 'nodeBreadCrumbs', 'mainBar', 'indicator', 'documentHistory', 'diffViewer'], function(moduleName) {
- sandbox.getModule(moduleName).start();
- });
- },
- documentChanged: function(document, reason) {
- commands.resetDocument(document, reason);
- },
- savingStarted: function() {
- sandbox.getModule('mainBar').setCommandEnabled('save', false);
- sandbox.getModule('indicator').showMessage(gettext('Saving...'));
- },
- savingEnded: function(status) {
- sandbox.getModule('mainBar').setCommandEnabled('save', true);
- sandbox.getModule('indicator').clearMessage({message:'Dokument zapisany'});
- },
- restoringStarted: function(event) {
- sandbox.getModule('mainBar').setCommandEnabled('save', false);
- sandbox.getModule('indicator').showMessage(gettext('Restoring version ') + event.version + '...');
- },
- historyItemAdded: function(item) {
- sandbox.getModule('documentHistory').addHistory([item], {animate: true});
- },
- diffFetched: function(diff) {
- sandbox.getModule('diffViewer').setDiff(diff);
- },
- documentReverted: function(event) {
- commands.resetDocument(event.document, 'revert');
- sandbox.getModule('mainBar').setCommandEnabled('save', true);
- sandbox.getModule('indicator').clearMessage({message:'Wersja ' + event.reverted_version + ' przywrócona'});
- sandbox.getModule('mainBar').setVersion(event.current_version);
- }
- };
-
- eventHandlers.mainBar = {
- ready: function() {
- sandbox.getModule('mainBar').setVersion(sandbox.getModule('data').getDocumentVersion());
- views.mainLayout.setView('topPanel', sandbox.getModule('mainBar').getView());
- },
- 'cmd.save': function() {
- synchronizeTab(views.mainTabs.getCurrentSlug());
- sandbox.getModule('data').saveDocument();
- }
- };
-
- eventHandlers.indicator = {
- ready: function() {
- views.mainLayout.setView('messages', sandbox.getModule('indicator').getView());
- }
- };
-
-
-
- eventHandlers.documentCanvas = {
- ready: function() {
- sandbox.getModule('documentCanvas').setDocument(sandbox.getModule('data').getDocument());
- views.visualEditing.setView('leftColumn', sandbox.getModule('documentCanvas').getView());
- },
- documentSet: function() {
- dirty.documentCanvas = false;
- },
-
- currentTextElementSet: function(textElement) {
- commands.updateCurrentTextElement(textElement);
- },
-
- currentNodeElementSet: function(nodeElement) {
- commands.updateCurrentNodeElement(nodeElement);
- },
-
- currentNodeElementChanged: function(nodeElement) {
- commands.updateCurrentNodeElement(nodeElement);
- dirty.documentCanvas = true;
- },
-
- contentChanged: function() {
- dirty.documentCanvas = true;
- },
-
- nodeHovered: function(canvasNode) {
- commands.highlightDocumentNode(canvasNode);
- },
-
- nodeBlured: function(canvasNode) {
- commands.dimDocumentNode(canvasNode);
- }
- };
-
- eventHandlers.nodePane = {
- ready: function() {
- views.currentNodePaneLayout.appendView(sandbox.getModule('nodePane').getView());
- },
-
- nodeElementChange: function(attr, value) {
- sandbox.getModule('documentCanvas').modifyCurrentNodeElement(attr, value);
- }
- };
-
- eventHandlers.metadataEditor = {
- ready: function() {
- sandbox.getModule('metadataEditor').setDocument(sandbox.getModule('data').getDocument());
- views.visualEditingSidebar.addTab({icon: 'info-sign'}, 'metadataEditor', sandbox.getModule('metadataEditor').getView());
- },
- metadataChanged: function(metadata) {
- dirty.metadataEditor = true;
- },
- metadataSet: function() {
- dirty.metadataEditor = false;
- },
- };
-
- eventHandlers.nodeFamilyTree = {
- ready: function() {
- views.currentNodePaneLayout.appendView(sandbox.getModule('nodeFamilyTree').getView());
- },
- elementEntered: function(element) {
- commands.highlightDocumentElement(element, 'nodeFamilyTree');
- },
- elementLeft: function(element) {
- commands.dimDocumentElement(element, 'nodeFamilyTree');
- },
- elementClicked: function(element) {
- commands.jumpToDocumentElement(element);
- }
- };
-
- eventHandlers.documentToolbar = {
- ready: function() {
- views.visualEditing.setView('toolbar', sandbox.getModule('documentToolbar').getView());
- },
- command: function(cmd, params) {
- sandbox.getModule('documentCanvas').command(cmd, params);
- }
- };
-
- eventHandlers.nodeBreadCrumbs = {
- ready: function() {
- views.visualEditing.setView('statusBar', sandbox.getModule('nodeBreadCrumbs').getView());
- },
- elementEntered: function(element) {
- commands.highlightDocumentElement(element, 'nodeBreadCrumbs');
- },
- elementLeft: function(element) {
- commands.dimDocumentElement(element, 'nodeBreadCrumbs');
- },
- elementClicked: function(element) {
- commands.jumpToDocumentElement(element);
- }
- };
-
- eventHandlers.documentHistory = {
- ready: function() {
- sandbox.getModule('documentHistory').addHistory(sandbox.getModule('data').getHistory());
- views.diffLayout.setView('left', sandbox.getModule('documentHistory').getView());
- },
- compare: function(ver1, ver2) {
- sandbox.getModule('data').fetchDiff(ver1, ver2);
- },
- restoreVersion: function(event) {
- sandbox.getModule('data').restoreVersion(event);
- },
- displayVersion: function(event) {
- window.open('/' + gettext('editor') + '/' + sandbox.getModule('data').getDocumentId() + '?version=' + event.version, _.uniqueId());
- }
- };
-
- eventHandlers.diffViewer = {
- ready: function() {
- views.diffLayout.setView('right', sandbox.getModule('diffViewer').getView());
- }
- };
-
- /* api */
-
- return {
- start: function() {
- sandbox.getModule('data').start();
- },
- handleEvent: function(moduleName, eventName, args) {
- if('')
- wysiwigHandler.handleEvent(moduleName, eventName, args);
- else if(eventHandlers[moduleName] && eventHandlers[moduleName][eventName]) {
- eventHandlers[moduleName][eventName].apply(eventHandlers, args);
- }
- }
- };
-};
-
-});
\ No newline at end of file
+++ /dev/null
-@import 'mainLayout.less';
-@import 'editingLayout.less';
-@import 'diffLayout.less';
\ No newline at end of file
+++ /dev/null
-define(function() {
-
-'use strict';
-
-return function(sandbox) {
-
- var view = $(sandbox.getTemplate('main')());
-
- var editor = ace.edit(view.find('#rng-sourceEditor-editor')[0]),
- session = editor.getSession();
- editor.setTheme("ace/theme/chrome");
- session.setMode("ace/mode/xml")
- session.setUseWrapMode(true);
-
- $('textarea', view).on('keyup', function() {
- sandbox.publish('xmlChanged');
- });
-
- editor.getSession().on('change', function() {
- sandbox.publish('xmlChanged');
- });
- return {
- start: function() {
- sandbox.publish('ready');
- },
- getView: function() {
- return view;
- },
- setDocument: function(document) {
- editor.setValue(document);
- editor.gotoLine(0);
- sandbox.publish('documentSet');
- },
- getDocument: function() {
- return editor.getValue();
- }
- };
-};
-
-});
\ No newline at end of file
+++ /dev/null
-#rng-sourceEditor-editor {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
-}
\ No newline at end of file
+++ /dev/null
-define(function() {
-
-'use strict';
-
-return {
- modules: {},
- initModules: ['rng'],
- permissions: {
- 'skelton': ['getDOM'],
- 'rng': ['getModule', 'handleEvents', 'getDOM']
- }
-}
-
-});
\ No newline at end of file
--- /dev/null
+(function() {
+ 'use strict';
+
+ requirejs.config({
+ baseUrl: '/static/editor/src/editor',
+
+ paths: {
+ 'fnpjs': '../fnpjs',
+ 'libs': '../../libs'
+ },
+
+ map: {
+ '*':
+ {
+ 'libs/jquery': '../../libs/jquery-1.9.1.min',
+ 'libs/underscore': '../../libs/underscore-min',
+ 'libs/bootstrap': '../../libs/bootstrap/js/bootstrap.min',
+ 'libs/backbone': '../../libs/backbone-min',
+
+ }
+ },
+
+ shim: {
+ '../../libs/jquery-1.9.1.min': {
+ exports: '$',
+ },
+ '../../libs/underscore-min': {
+ exports: '_'
+ },
+ '../../libs/bootstrap/js/bootstrap.min': {
+ deps: ['libs/jquery']
+ },
+ '../../libs/backbone-min': {
+ exports: 'Backbone',
+ deps: ['libs/jquery', 'libs/underscore']
+ }
+ }
+
+ });
+
+ requirejs([
+ 'libs/jquery',
+ '../fnpjs/runner',
+ 'rng',
+ './modules',
+ 'libs/bootstrap'
+ ], function($, runner, rng, modules) {
+ $(function() {
+ var app = new runner.Runner(rng, modules);
+ app.setBootstrappedData('data', RNG_BOOTSTRAP_DATA);
+ app.start({rootSelector:'#editor_root'});
+ });
+ });
+
+
+})();
\ No newline at end of file
--- /dev/null
+define(function(require) {
+ /*
+ Each module must be required explicitly by apropriate 'require' function call
+ in order for requirejs optimizer to work.
+ */
+
+ 'use strict';
+
+ return {
+ data: require('modules/data/data'),
+ rng: require('modules/rng/rng'),
+ mainBar: require('modules/mainBar/mainBar'),
+ indicator: require('modules/indicator/indicator'),
+
+ sourceEditor: require('modules/sourceEditor/sourceEditor'),
+
+ documentCanvas: require('modules/documentCanvas/documentCanvas'),
+ documentToolbar: require('modules/documentToolbar/documentToolbar'),
+ nodePane: require('modules/nodePane/nodePane'),
+ metadataEditor: require('modules/metadataEditor/metadataEditor'),
+ nodeFamilyTree: require('modules/nodeFamilyTree/nodeFamilyTree'),
+ nodeBreadCrumbs: require('modules/nodeBreadCrumbs/nodeBreadCrumbs'),
+
+ documentHistory: require('modules/documentHistory/documentHistory'),
+ diffViewer: require('modules/diffViewer/diffViewer')
+
+ }
+});
\ No newline at end of file
--- /dev/null
+define(['./saveDialog'], function(saveDialog) {
+
+'use strict';
+
+return function(sandbox) {
+
+ var doc = sandbox.getBootstrappedData().document;
+ var document_id = sandbox.getBootstrappedData().document_id;
+ var document_version = sandbox.getBootstrappedData().version;
+ var history = sandbox.getBootstrappedData().history;
+
+
+ if(doc === '') {
+ doc = '<section\n\
+ xmlns="http://nowoczesnapolska.org.pl/sst#"\n\
+ xmlns:xlink="http://www.w3.org/1999/xlink"\n\
+ xmlns:dc="http://purl.org/dc/elements/1.1/"\n\
+ xmlns:dcterms="http://purl.org/dc/terms/"\n\
+ >\n\
+ <metadata>\n\
+ </metadata>\n\
+ <div class="p"></div>\n\
+ </section>';
+ }
+
+
+ function readCookie(name) {
+ var nameEQ = escape(name) + "=";
+ var ca = document.cookie.split(';');
+ for (var i = 0; i < ca.length; i++) {
+ var c = ca[i];
+ while (c.charAt(0) == ' ') c = c.substring(1, c.length);
+ if (c.indexOf(nameEQ) === 0) return unescape(c.substring(nameEQ.length, c.length));
+ }
+ return null;
+ }
+
+ $.ajaxSetup({
+ crossDomain: false,
+ beforeSend: function(xhr, settings) {
+ if (!(/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type))) {
+ xhr.setRequestHeader("X-CSRFToken", readCookie('csrftoken'));
+ }
+ }
+ });
+
+ var reloadHistory = function() {
+ $.ajax({
+ method: 'get',
+ url: '/' + gettext('editor') + '/' + document_id + '/history',
+ success: function(data) {
+ history = data;
+ sandbox.publish('historyItemAdded', data.slice(-1)[0]);
+ },
+ });
+ };
+
+ return {
+ start: function() {
+ sandbox.publish('ready');
+ },
+ getDocument: function() {
+ return doc;
+ },
+ commitDocument: function(newDocument, reason) {
+ doc = newDocument;
+ sandbox.publish('documentChanged', doc, reason);
+ },
+ saveDocument: function() {
+
+ var dialog = saveDialog.create();
+ dialog.on('save', function(event) {
+ sandbox.publish('savingStarted');
+ dialog.toggleButtons(false);
+ $.ajax({
+ method: 'post',
+ url: '/' + gettext('editor') + '/' + document_id,
+ data: JSON.stringify({document:doc, description: event.data.description}),
+ success: function() {
+ event.success();
+ sandbox.publish('savingEnded', 'success');
+ reloadHistory();
+ },
+ error: function() {event.error(); sandbox.publish('savingEnded', 'error');}
+ });
+ console.log('save');
+ });
+ dialog.on('cancel', function() {
+ });
+ dialog.show();
+
+
+ },
+ getHistory: function() {
+ return history;
+ },
+ fetchDiff: function(ver1, ver2) {
+ $.ajax({
+ method: 'get',
+ url: '/' + gettext('editor') + '/' + document_id + '/diff',
+ data: {from: ver1, to: ver2},
+ success: function(data) {
+ sandbox.publish('diffFetched', {table: data, ver1: ver1, ver2: ver2});
+ },
+ });
+ },
+ restoreVersion: function(options) {
+ if(options.version && options.description) {
+ sandbox.publish('restoringStarted', {version: options.version});
+ $.ajax({
+ method: 'post',
+ dataType: 'json',
+ url: '/' + gettext('editor') + '/' + document_id + '/revert',
+ data: JSON.stringify(options),
+ success: function(data) {
+ doc = data.document;
+ document_version = data.version;
+ reloadHistory();
+ sandbox.publish('documentReverted', data);
+ },
+ });
+ }
+ },
+ getDocumentId: function() {
+ return document_id;
+ },
+ getDocumentVersion: function() {
+ return document_version;
+ }
+ };
+};
+
+});
\ No newline at end of file
--- /dev/null
+@import 'saveDialog.less';
\ No newline at end of file
--- /dev/null
+<div class="rng-module-data-saveDialog modal hide static">
+ <div class="modal-header">
+ <button type="button" class="close">×</button>
+ <h1>Zapisz dokument</h1>
+ </div>
+ <div class="modal-body">
+ <label>Opisz swoje zmiany</label>
+ <textarea rows="5"></textarea>
+ </div>
+ <div class="modal-footer">
+ <a href="#" class="btn btn-info btn-mini save-btn">Zapisz</a>
+ <a href="#" class="btn btn-danger btn-mini cancel-btn">Anuluj</a>
+ </div>
+</div>
\ No newline at end of file
--- /dev/null
+define([
+'libs/text!./saveDialog.html',
+'libs/underscore',
+'libs/backbone',
+'libs/jquery'
+], function(saveDialogTemplate, _, Backbone, $) {
+
+ var DialogView = Backbone.View.extend({
+ template: _.template(saveDialogTemplate),
+ events: {
+ 'click .save-btn': 'onSave',
+ 'click .cancel-btn': 'close',
+ 'click .close': 'close'
+ },
+ initialize: function() {
+ _.bindAll(this);
+ this.actionsDisabled = false;
+ },
+ show: function() {
+ this.setElement(this.template());
+ this.$el.modal({backdrop: 'static'});
+ this.$el.modal('show');
+ this.$('textarea').focus();
+
+ },
+ onSave: function(e) {
+ e.preventDefault();
+ var view = this;
+ this.trigger('save', {
+ data: {description: view.$el.find('textarea').val()},
+ success: function() { view.actionsDisabled = false; view.close(); },
+ error: function() { view.actionsDisabled = false; view.close(); },
+ });
+ },
+ close: function(e) {
+ if(e)
+ e.preventDefault();
+ if(!this.actionsDisabled) {
+ this.$el.modal('hide');
+ this.$el.remove();
+ }
+ },
+ toggleButtons: function(toggle) {
+ this.$('.btn, button').toggleClass('disabled', !toggle);
+ this.$('textarea').attr('disabled', !toggle);
+ this.actionsDisabled = !toggle;
+ }
+ });
+
+ return {
+ create: function() {
+ return new DialogView();
+ }
+ };
+
+});
\ No newline at end of file
--- /dev/null
+.rng-module-data-saveDialog {
+ textarea {
+ padding: 3px 3px;
+ margin: 5px auto;
+ width: 95%;
+ display: block;
+ }
+
+ h1, label {
+ font-size: 12px;
+ line-height: 12px;
+
+ }
+
+ h1 {
+ margin: 2px 5px;
+ font-weight: bold;
+ }
+}
\ No newline at end of file
--- /dev/null
+<div class="rng-module-diffViewer-diffView"></div>
\ No newline at end of file
--- /dev/null
+define([
+'libs/jquery',
+'libs/underscore',
+'views/tabs/tabs',
+'libs/text!./diff.html'
+], function($, _, tabs, diffTemplateSrc) {
+
+'use strict';
+
+return function(sandbox) {
+
+ var dom = $('<div>').addClass('rng-module-diffViewer');
+ var tabsView = (new tabs.View({position: 'right'})).render();
+ dom.append(tabsView.getAsView());
+
+ var DiffView = function() {
+ this.dom = $(diffTemplateSrc);
+ };
+
+ DiffView.prototype.setTable = function(table) {
+ this.dom.append(table);
+ };
+
+
+ return {
+ start: function() {sandbox.publish('ready');},
+ getView: function() {return dom;},
+ setDiff: function(diff) {
+ var diffView = new DiffView();
+ diffView.setTable(diff.table);
+ var slug = diff.ver1 + '-' + diff.ver2;
+ tabsView.addTab(diff.ver1 + '->' + diff.ver2, slug, diffView.dom);
+ tabsView.selectTab(slug);
+ }
+ };
+};
+
+});
\ No newline at end of file
--- /dev/null
+.rng-module-diffViewer {
+ .nav-tabs > li > a {
+ min-width: 0;
+ font-size: 0.9em;
+ padding: 4px 6px;
+ }
+
+ .tab-content {
+ position: absolute;
+ top:0;
+ bottom:0;
+ left:0;
+ right:60px;
+ overflow-y: scroll;
+ &::-webkit-scrollbar {
+ .rng-mixin-scrollbar;
+ }
+ &::-webkit-scrollbar-track {
+ .rng-mixin-scrollbar-track;
+ }
+ &::-webkit-scrollbar-thumb {
+ .rng-mixin-scrollbar-thumb;
+ }
+ &::-webkit-scrollbar-thumb:window-inactive {
+ .rng-mixin-scrollbar-thumb-window-inactive;
+ }
+ }
+
+ .diff_table {
+ border-width: 1px 0 1px 1px;
+ border-style: solid;
+ border-color: #ddd;
+ empty-cells: show;
+ border-spacing: 0px;
+ }
+
+ .diff_table td {
+ border-width: 0px 1px 1px 0px;
+ border-style: dotted;
+ border-color: grey;
+ font-size: 10px;
+ line-height: 20px;
+ font-family: monospace;
+ padding: 0px;
+ white-space: pre-line;
+ /*word-wrap:break-word;
+ word-break:break-all; */
+ }
+
+ .diff_table th {
+ border-width: 0px 1px 1px 0px;
+ border-style: solid;
+ border-color: #ddd;
+ background: #e5ffe5;
+ }
+
+ .diff_table tr.change {
+ background-color: #dcdcdc;
+ }
+
+ .diff_mark {
+ display: inline-block;
+ padding: 2px;
+ }
+
+ .diff_mark_removed {
+ background-color: #ff9c94;
+ }
+
+ .diff_mark_added {
+ background-color: #90ee90;
+ }
+
+ .diff_mark_changed {
+ background-color: yellow;
+ }
+}
\ No newline at end of file
--- /dev/null
+define([
+'libs/jquery',
+'libs/underscore',
+'libs/backbone',
+'modules/documentCanvas/canvas/documentElement',
+'modules/documentCanvas/canvas/keyboard',
+'modules/documentCanvas/canvas/utils'
+], function($, _, Backbone, documentElement, keyboard, utils) {
+
+'use strict';
+
+var Canvas = function(wlxml, publisher) {
+ this.eventBus = _.extend({}, Backbone.Events);
+ this.loadWlxml(wlxml);
+ this.publisher = publisher ? publisher : function() {};
+};
+
+$.extend(Canvas.prototype, {
+
+ loadWlxml: function(wlxml) {
+ var d = wlxml ? $($.trim(wlxml)) : null;
+ if(d) {
+ this.wrapper = $('<div>').addClass('canvas-wrapper').attr('contenteditable', true);
+ this.wrapper.append(d);
+ var canvas = this;
+
+ this.wrapper.find('*').replaceWith(function() {
+ var currentTag = $(this);
+ if(currentTag.attr('wlxml-tag'))
+ return;
+
+ var meta = {}, others = {};
+ for(var i = 0; i < this.attributes.length; i++) {
+ var attr = this.attributes[i];
+ if(attr.name.substr(0, 5) === 'meta-')
+ meta[attr.name.substr(5)] = attr.value;
+ else if(attr.name !== 'class')
+ others[attr.name] = attr.value;
+ }
+
+ var element = canvas.createNodeElement({
+ tag: currentTag.prop('tagName').toLowerCase(),
+ klass: currentTag.attr('class'),
+ meta: meta,
+ others: others,
+ rawChildren: currentTag.contents(),
+ prepopulateOnEmpty: true
+ });
+
+ ['orig-before', 'orig-after', 'orig-begin', 'orig-end'].forEach(function(attr) {
+ element.data(attr, '');
+ });
+ return element.dom();
+ });
+
+ var FIRST_CONTENT_INDEX = 0;
+
+ // @@ TODO - refactor!
+ var getNode = function(element) {
+ return element.children('[document-element-content]');
+ }
+
+ this.wrapper.find(':not(iframe)').addBack().contents()
+ .filter(function() {return this.nodeType === Node.TEXT_NODE})
+ .each(function() {
+
+ // TODO: use DocumentElement API
+
+ var el = $(this),
+ text = {original: el.text(), trimmed: $.trim(el.text())},
+ elParent = el.parent(),
+ hasSpanParent = elParent.attr('wlxml-tag') === 'span',
+ hasSpanBefore = el.prev().length > 0 && getNode($(el.prev()[0])).attr('wlxml-tag') === 'span',
+ hasSpanAfter = el.next().length > 0 && getNode($(el.next()[0])).attr('wlxml-tag') === 'span';
+
+ if(el.parent().hasClass('canvas-widget') || elParent.attr('document-text-element') !== undefined)
+ return true; // continue
+
+ var addInfo = function(toAdd, where) {
+ var parentContents = elParent.contents(),
+ idx = parentContents.index(el[0]),
+ prev = idx > FIRST_CONTENT_INDEX ? parentContents[idx-1] : null,
+ next = idx < parentContents.length - 1 ? parentContents[idx+1] : null,
+ target, key;
+
+ if(where === 'above') {
+ target = prev ? $(prev) : elParent.parent();
+ key = prev ? 'orig-after' : 'orig-begin';
+ } else if(where === 'below') {
+ target = next ? $(next) : elParent.parent();
+ key = next ? 'orig-before' : 'orig-end';
+ } else { throw new Object;}
+
+ target.data(key, toAdd);
+ }
+
+ text.transformed = text.trimmed;
+
+ if(hasSpanParent || hasSpanBefore || hasSpanAfter) {
+ var startSpace = /\s/g.test(text.original.substr(0,1)),
+ endSpace = /\s/g.test(text.original.substr(-1)) && text.original.length > 1;
+ text.transformed = (startSpace && (hasSpanParent || hasSpanBefore) ? ' ' : '')
+ + text.trimmed
+ + (endSpace && (hasSpanParent || hasSpanAfter) ? ' ' : '');
+ } else {
+ if(text.trimmed.length === 0 && text.original.length > 0 && elParent.contents().length === 1)
+ text.transformed = ' ';
+ }
+
+ if(!text.transformed) {
+ addInfo(text.original, 'below');
+ el.remove();
+ return true; // continue
+ }
+
+ if(text.transformed !== text.original) {
+ if(!text.trimmed) {
+ addInfo(text.original, 'below');
+ } else {
+ var startingMatch = text.original.match(/^\s+/g),
+ endingMatch = text.original.match(/\s+$/g),
+ startingWhiteSpace = startingMatch ? startingMatch[0] : null,
+ endingWhiteSpace = endingMatch ? endingMatch[0] : null;
+
+ if(endingWhiteSpace) {
+ if(text.transformed[text.transformed.length - 1] === ' ' && endingWhiteSpace[0] === ' ')
+ endingWhiteSpace = endingWhiteSpace.substr(1);
+ addInfo(endingWhiteSpace, 'below');
+ }
+
+ if(startingWhiteSpace) {
+ if(text.transformed[0] === ' ' && startingWhiteSpace[startingWhiteSpace.length-1] === ' ')
+ startingWhiteSpace = startingWhiteSpace.substr(0, startingWhiteSpace.length -1);
+ addInfo(startingWhiteSpace, 'above');
+ }
+ }
+ }
+
+ var element = documentElement.DocumentTextElement.create({text: text.transformed});
+ el.replaceWith(element.dom());
+ });
+
+ this.d = this.wrapper.children(0);
+
+ this.wrapper.on('keyup keydown keypress', function(e) {
+ keyboard.handleKey(e, this);
+ }.bind(this));
+
+ this.wrapper.on('click', '[document-node-element], [document-text-element]', function(e) {
+ e.stopPropagation();
+ canvas.setCurrentElement(canvas.getDocumentElement(e.currentTarget), {caretTo: false});
+ });
+
+ var observer = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ if(documentElement.DocumentTextElement.isContentContainer(mutation.target)) {
+ observer.disconnect();
+ if(mutation.target.data === '')
+ mutation.target.data = utils.unicode.ZWS;
+ else if(mutation.oldValue === utils.unicode.ZWS) {
+ mutation.target.data = mutation.target.data.replace(utils.unicode.ZWS, '');
+ canvas._moveCaretToTextElement(canvas.getDocumentElement(mutation.target), 'end');
+ }
+ observer.observe(canvas.d[0], config);
+ canvas.publisher('contentChanged');
+ }
+ });
+ });
+ var config = { attributes: false, childList: false, characterData: true, subtree: true, characterDataOldValue: true};
+ observer.observe(this.d[0], config);
+
+
+ this.wrapper.on('mouseover', '[document-node-element], [document-text-element]', function(e) {
+ var el = canvas.getDocumentElement(e.currentTarget);
+ if(!el)
+ return;
+ e.stopPropagation();
+ if(el instanceof documentElement.DocumentTextElement)
+ el = el.parent();
+ el.toggleLabel(true);
+ });
+ this.wrapper.on('mouseout', '[document-node-element], [document-text-element]', function(e) {
+ var el = canvas.getDocumentElement(e.currentTarget);
+ if(!el)
+ return;
+ e.stopPropagation();
+ if(el instanceof documentElement.DocumentTextElement)
+ el = el.parent();
+ el.toggleLabel(false);
+ });
+
+ this.eventBus.on('elementToggled', function(toggle, element) {
+ if(!toggle) {
+ canvas.setCurrentElement(element.getPreviousTextElement());
+ }
+ })
+
+ } else {
+ this.d = null;
+ }
+ },
+
+ view: function() {
+ return this.wrapper;
+ },
+
+ doc: function() {
+ if(this.d === null)
+ return null;
+ return documentElement.DocumentNodeElement.fromHTMLElement(this.d.get(0), this); //{wlxmlTag: this.d.prop('tagName')};
+ },
+
+ createNodeElement: function(params) {
+ return documentElement.DocumentNodeElement.create(params, this);
+ },
+
+ wrapText: function(params) {
+ params = _.extend({textNodeIdx: 0}, params);
+ if(typeof params.textNodeIdx === 'number')
+ params.textNodeIdx = [params.textNodeIdx];
+
+ var childrenInside = params.inside.children(),
+ idx1 = Math.min.apply(Math, params.textNodeIdx),
+ idx2 = Math.max.apply(Math, params.textNodeIdx),
+ textNode1 = childrenInside[idx1],
+ textNode2 = childrenInside[idx2],
+ sameNode = textNode1.sameNode(textNode2),
+ prefixOutside = textNode1.getText().substr(0, params.offsetStart),
+ prefixInside = textNode1.getText().substr(params.offsetStart),
+ suffixInside = textNode2.getText().substr(0, params.offsetEnd),
+ suffixOutside = textNode2.getText().substr(params.offsetEnd)
+ ;
+
+ var wrapperElement = this.createNodeElement({tag: params._with.tag, klass: params._with.klass});
+ textNode1.after(wrapperElement);
+ textNode1.detach();
+
+ if(prefixOutside.length > 0)
+ wrapperElement.before({text:prefixOutside});
+ if(sameNode) {
+ var core = textNode1.getText().substr(params.offsetStart, params.offsetEnd - params.offsetStart);
+ wrapperElement.append({text: core});
+ } else {
+ textNode2.detach();
+ if(prefixInside.length > 0)
+ wrapperElement.append({text: prefixInside});
+ for(var i = idx1 + 1; i < idx2; i++) {
+ wrapperElement.append(childrenInside[i]);
+ }
+ if(suffixInside.length > 0)
+ wrapperElement.append({text: suffixInside});
+ }
+ if(suffixOutside.length > 0)
+ wrapperElement.after({text: suffixOutside});
+ return wrapperElement;
+ },
+
+ wrapElements: function(params) {
+ if(!(params.element1.parent().sameNode(params.element2.parent())))
+ return;
+
+ var parent = params.element1.parent(),
+ parentChildren = parent.children(),
+ wrapper = this.createNodeElement({
+ tag: params._with.tag,
+ klass: params._with.klass}),
+ idx1 = parent.childIndex(params.element1),
+ idx2 = parent.childIndex(params.element2);
+
+ if(idx1 > idx2) {
+ var tmp = idx1;
+ idx1 = idx2;
+ idx2 = tmp;
+ }
+
+ var insertingMethod, insertingTarget;
+ if(idx1 === 0) {
+ insertingMethod = 'prepend';
+ insertingTarget = parent;
+ } else {
+ insertingMethod = 'after';
+ insertingTarget = parentChildren[idx1-1];
+ }
+
+ for(var i = idx1; i <= idx2; i++) {
+ wrapper.append(parentChildren[i].detach());
+ }
+
+ insertingTarget[insertingMethod](wrapper);
+ return wrapper;
+ },
+
+ getSiblingParents: function(params) {
+ var parents1 = [params.element1].concat(params.element1.parents()).reverse(),
+ parents2 = [params.element2].concat(params.element2.parents()).reverse(),
+ noSiblingParents = null;
+
+ if(parents1.length === 0 || parents2.length === 0 || !(parents1[0].sameNode(parents2[0])))
+ return noSiblingParents;
+
+ var i;
+ for(i = 0; i < Math.min(parents1.length, parents2.length); i++) {
+ if(parents1[i].sameNode(parents2[i]))
+ continue;
+ break;
+ }
+ return {element1: parents1[i], element2: parents2[i]};
+ },
+
+ getDocumentElement: function(from) {
+ if(from instanceof HTMLElement || from instanceof Text) {
+ return documentElement.DocumentElement.fromHTMLElement(from, this);
+ }
+ },
+ getCursor: function() {
+ return new Cursor(this);
+ },
+
+ list: {},
+
+
+ getCurrentNodeElement: function() {
+ return this.getDocumentElement(this.wrapper.find('.current-node-element').parent()[0]);
+ },
+
+ getCurrentTextElement: function() {
+ return this.getDocumentElement(this.wrapper.find('.current-text-element')[0]);
+ },
+
+
+
+ setCurrentElement: function(element, params) {
+ params = _.extend({caretTo: 'end'}, params);
+ var findFirstDirectTextChild = function(e, nodeToLand) {
+ var byBrowser = this.getCursor().getPosition().element;
+ if(byBrowser && byBrowser.parent().sameNode(nodeToLand))
+ return byBrowser;
+ var children = e.children();
+ for(var i = 0; i < children.length; i++) {
+ if(children[i] instanceof documentElement.DocumentTextElement)
+ return children[i];
+ }
+ return null;
+ }.bind(this);
+ var _markAsCurrent = function(element) {
+ if(element instanceof documentElement.DocumentTextElement) {
+ this.wrapper.find('.current-text-element').removeClass('current-text-element');
+ element.dom().addClass('current-text-element');
+ } else {
+ this.wrapper.find('.current-node-element').removeClass('current-node-element')
+ element._container().addClass('current-node-element');
+ this.publisher('currentElementChanged', element);
+ }
+ }.bind(this);
+
+
+ var isTextElement = element instanceof documentElement.DocumentTextElement,
+ nodeElementToLand = isTextElement ? element.parent() : element,
+ textElementToLand = isTextElement ? element : findFirstDirectTextChild(element, nodeElementToLand),
+ currentTextElement = this.getCurrentTextElement(),
+ currentNodeElement = this.getCurrentNodeElement();
+
+ if(currentTextElement && !(currentTextElement.sameNode(textElementToLand)))
+ this.wrapper.find('.current-text-element').removeClass('current-text-element');
+
+ if(textElementToLand) {
+ _markAsCurrent(textElementToLand);
+ 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);
+ } else {
+ document.getSelection().removeAllRanges();
+ }
+
+ if(!(currentNodeElement && currentNodeElement.sameNode(nodeElementToLand))) {
+ _markAsCurrent(nodeElementToLand);
+
+ this.publisher('currentNodeElementSet', nodeElementToLand);
+ }
+ },
+
+ _moveCaretToTextElement: function(element, where) {
+ var range = document.createRange(),
+ node = element.dom().contents()[0];
+
+ if(typeof where !== 'number') {
+ range.selectNodeContents(node);
+ } else {
+ range.setStart(node, where);
+ }
+
+ var collapseArg = true;
+ if(where === 'end')
+ collapseArg = false;
+ range.collapse(collapseArg);
+
+ var selection = document.getSelection();
+
+ selection.removeAllRanges();
+ selection.addRange(range);
+ this.wrapper.focus(); // FF requires this for caret to be put where range colllapses, Chrome doesn't.
+ },
+
+ setCursorPosition: function(position) {
+ if(position.element)
+ this._moveCaretToTextElement(position.element, position.offset);
+ },
+
+ toXML: function() {
+ var parent = $('<div>'),
+ parts = this.doc().toXML(0);
+ parent.append(parts);
+ return parent.html();
+ }
+});
+
+$.extend(Canvas.prototype.list, {
+ create: function(params) {
+ if(!(params.element1.parent().sameNode(params.element2.parent())))
+ return false;
+
+ var parent = params.element1.parent(),
+ canvas = params.element1.canvas;
+
+ if(parent.childIndex(params.element1) > parent.childIndex(params.element2)) {
+ var tmp = params.element1;
+ params.element1 = params.element2;
+ params.element2 = tmp;
+ }
+
+ var elementsToWrap = [];
+
+ var place = 'before';
+ parent.children().some(function(element) {
+ var _e = element;
+ if(element.sameNode(params.element1))
+ place = 'inside';
+ if(place === 'inside') {
+ if(element instanceof documentElement.DocumentTextElement) {
+ element = element.wrapWithNodeElement({tag: 'div', klass: 'list.item'});
+ if(element.children()[0].sameNode(params.element1))
+ params.element1 = element;
+ }
+ element.setWlxmlClass('item');
+ elementsToWrap.push(element);
+ }
+ if(_e.sameNode(params.element2))
+ return true;
+ });
+
+ var listElement = canvas.createNodeElement({tag: 'div', klass: 'list-items' + (params.type === 'enum' ? '-enum' : '')});
+ var toret;
+ if(parent.is('list')) {
+ var item = listElement.wrapWithNodeElement({tag: 'div', klass: 'item'});
+ item.exec('toggleBullet', false);
+ toret = listElement.parent();
+ } else {
+ toret = listElement;
+ }
+
+ params.element1.before(toret);
+
+ elementsToWrap.forEach(function(element) {
+ element.detach();
+ listElement.append(element);
+ });
+ },
+ extractItems: function(params) {
+ params = _.extend({merge: true}, params);
+ var list = params.element1.parent();
+ if(!list.is('list') || !(list.sameNode(params.element2.parent())))
+ return false;
+
+ var idx1 = list.childIndex(params.element1),
+ idx2 = list.childIndex(params.element2),
+ precedingItems = [],
+ extractedItems = [],
+ succeedingItems = [],
+ items = list.children(),
+ listIsNested = list.parent().getWlxmlClass() === 'item',
+ canvas = params.element1.canvas,
+ i;
+
+ if(idx1 > idx2) {
+ var tmp = idx1; idx1 = idx2; idx2 = tmp;
+ }
+
+ items.forEach(function(item, idx) {
+ if(idx < idx1)
+ precedingItems.push(item);
+ else if(idx >= idx1 && idx <= idx2) {
+ extractedItems.push(item);
+ }
+ else {
+ succeedingItems.push(item);
+ }
+ });
+
+ var reference = listIsNested ? list.parent() : list;
+ if(succeedingItems.length === 0) {
+ var reference_orig = reference;
+ extractedItems.forEach(function(item) {
+ reference.after(item);
+ reference = item;
+ if(!listIsNested)
+ item.setWlxmlClass(null);
+ });
+ if(precedingItems.length === 0)
+ reference_orig.detach();
+ } else if(precedingItems.length === 0) {
+ extractedItems.forEach(function(item) {
+ reference.before(item);
+ if(!listIsNested)
+ item.setWlxmlClass(null);
+ });
+ } else {
+ extractedItems.forEach(function(item) {
+ reference.after(item);
+ if(!listIsNested)
+ item.setWlxmlClass(null);
+ reference = item;
+ });
+ var secondList = canvas.createNodeElement({tag: 'div', klass:'list-items'}),
+ toAdd = secondList;
+
+ if(listIsNested) {
+ toAdd = secondList.wrapWithNodeElement({tag: 'div', klass:'item'});
+ }
+ succeedingItems.forEach(function(item) {
+ secondList.append(item);
+ });
+
+ reference.after(toAdd);
+ }
+ if(!params.merge && listIsNested) {
+ return this.extractItems({element1: extractedItems[0], element2: extractedItems[extractedItems.length-1]});
+ }
+ return true;
+ },
+ areItemsOfTheSameList: function(params) {
+ var e1 = params.element1,
+ e2 = params.element2;
+ return e1.parent().sameNode(e2.parent())
+ && e1.parent().is('list');
+ }
+});
+
+
+var Cursor = function(canvas) {
+ this.canvas = canvas;
+};
+
+$.extend(Cursor.prototype, {
+ isSelecting: function() {
+ var selection = window.getSelection();
+ return !selection.isCollapsed;
+ },
+ isSelectingWithinElement: function() {
+ return this.isSelecting() && this.getSelectionStart().element.sameNode(this.getSelectionEnd().element);
+ },
+ isSelectingSiblings: function() {
+ return this.isSelecting() && this.getSelectionStart().element.parent().sameNode(this.getSelectionEnd().element.parent());
+ },
+ getPosition: function() {
+ return this.getSelectionAnchor();
+ },
+ getSelectionStart: function() {
+ return this.getSelectionBoundry('start');
+ },
+ getSelectionEnd: function() {
+ return this.getSelectionBoundry('end');
+ },
+ getSelectionAnchor: function() {
+ return this.getSelectionBoundry('anchor');
+ },
+ getSelectionFocus: function() {
+ return this.getSelectionBoundry('focus');
+ },
+ getSelectionBoundry: function(which) {
+ var selection = window.getSelection(),
+ anchorElement = this.canvas.getDocumentElement(selection.anchorNode),
+ focusElement = this.canvas.getDocumentElement(selection.focusNode);
+
+ if((!anchorElement) || (anchorElement instanceof documentElement.DocumentNodeElement) || (!focusElement) || focusElement instanceof documentElement.DocumentNodeElement)
+ return {};
+
+ if(which === 'anchor') {
+ return {
+ element: anchorElement,
+ offset: selection.anchorOffset,
+ offsetAtBeginning: selection.anchorOffset === 0,
+ offsetAtEnd: selection.anchorNode.data.length === selection.anchorOffset
+ };
+ }
+ if(which === 'focus') {
+ return {
+ element: focusElement,
+ offset: selection.focusOffset,
+ offsetAtBeginning: selection.focusOffset === 0,
+ offsetAtEnd: selection.focusNode.data.length === selection.focusOffset
+ };
+ }
+
+ var element,
+ offset;
+
+ if(anchorElement.parent().sameNode(focusElement.parent())) {
+ var parent = anchorElement.parent(),
+ anchorFirst = parent.childIndex(anchorElement) < parent.childIndex(focusElement);
+ if(anchorFirst) {
+ if(which === 'start') {
+ element = anchorElement;
+ offset = selection.anchorOffset
+ }
+ else if(which === 'end') {
+ element = focusElement,
+ offset = selection.focusOffset
+ }
+ } else {
+ if(which === 'start') {
+ element = focusElement,
+ offset = selection.focusOffset
+ }
+ else if(which === 'end') {
+ element = anchorElement;
+ offset = selection.anchorOffset
+ }
+ }
+ } else {
+ // TODO: Handle order via https://developer.mozilla.org/en-US/docs/Web/API/Node.compareDocumentPosition
+ if(which === 'start') {
+ element = anchorElement;
+ offset = selection.anchorOffset
+ } else {
+ element = focusElement;
+ offset = selection.focusOffset
+ }
+ }
+
+ var nodeLen = (element.sameNode(focusElement) ? selection.focusNode : selection.anchorNode).length;
+ return {
+ element: element,
+ offset: offset,
+ offsetAtBeginning: offset === 0,
+ offsetAtEnd: nodeLen === offset
+ }
+ }
+})
+
+return {
+ fromXML: function(xml, publisher) {
+ return new Canvas(xml, publisher);
+ }
+};
+
+});
\ No newline at end of file
--- /dev/null
+define([
+'libs/chai',
+'libs/sinon',
+'modules/documentCanvas/canvas/canvas',
+'modules/documentCanvas/canvas/documentElement',
+'modules/documentCanvas/canvas/utils'
+], function(chai, sinon, canvas, documentElement, utils) {
+
+'use strict';
+
+var expect = chai.expect;
+
+
+describe('Canvas', function() {
+
+
+
+ describe('ZWS', function() {
+ var view, section, textElement;
+
+ beforeEach(function() {
+ var c = canvas.fromXML('<section></section>');
+
+ section = c.doc();
+ textElement = section.children()[0];
+ view = c.view()[0];
+ document.getElementsByTagName('body')[0].appendChild(view);
+ });
+
+ afterEach(function() {
+ view.parentNode.removeChild(view);
+ });
+
+ var getTextContainerNode = function(textElement) {
+ return textElement.dom().contents()[0];
+ }
+
+ it('is set automatically on all empty DocumentTextElements', function() {
+ expect(getTextContainerNode(textElement).data).to.equal(utils.unicode.ZWS);
+
+ var header = section.append({tag: 'header'}),
+ newText = header.append({text: ''}),
+ textNode = getTextContainerNode(textElement);
+
+ expect(textNode.data).to.equal(utils.unicode.ZWS);
+ });
+
+ it('is added automatically when whole text gets deleted', function() {
+ getTextContainerNode(textElement).data = '';
+
+ window.setTimeout(function() {
+ expect(getTextContainerNode(textElement).data).to.equal(utils.unicode.ZWS);
+ }, 0)
+
+ var header = section.append({tag: 'header'}),
+ newText = header.append({text: 'Alice'}),
+ textNode = getTextContainerNode(newText);
+
+ expect(textNode.data).to.have.length('Alice'.length);
+ textNode.data = '';
+
+ window.setTimeout(function() {
+ expect(textNode.data).to.equal(utils.unicode.ZWS);
+ }, 0)
+ });
+ });
+
+ describe('Internal HTML representation of a DocumentNodeElement', function() {
+ it('is always a div tag', function() {
+ ['section', 'header', 'span', 'aside', 'figure'].forEach(function(tagName) {
+ var dom = canvas.fromXML('<' + tagName + '></' + tagName + '>').doc().dom();
+ expect(dom.prop('tagName')).to.equal('DIV', tagName + ' is represented as div');
+ });
+ });
+ it('has wlxml tag put into wlxml-tag attribute of its internal container', function() {
+ var dom = canvas.fromXML('<section></section>').doc().dom();
+ expect(dom.children('[document-element-content]').attr('wlxml-tag')).to.equal('section');
+ });
+ it('has wlxml class put into wlxml-class attribute of its internal containr, dots replaced with dashes', function() {
+ var dom = canvas.fromXML('<section class="some.class"></section>').doc().dom();
+ expect(dom.children('[document-element-content]').attr('wlxml-class')).to.equal('some-class');
+ });
+ });
+
+ describe('Internal HTML representation of a DocumentTextElement', function() {
+ it('is text node wrapped in a div with document-text-element attribute set', function() {
+ var dom = canvas.fromXML('<section>Alice</section>').doc().children()[0].dom();
+ expect(dom.prop('tagName')).to.equal('DIV');
+ expect(dom.attr('document-text-element')).to.equal('');
+ expect(dom.contents().length).to.equal(1);
+ expect(dom.contents()[0].nodeType).to.equal(Node.TEXT_NODE);
+ expect($(dom.contents()[0]).text()).to.equal('Alice');
+ });
+ });
+
+ describe('basic properties', function() {
+ it('renders empty document when canvas created from empty XML', function() {
+ var c = canvas.fromXML('');
+ expect(c.doc()).to.equal(null);
+ });
+
+ it('gives access to its document root node', function() {
+ var c = canvas.fromXML('<section></section>');
+ expect(c.doc().getWlxmlTag()).to.equal('section');
+ });
+
+ describe('root element', function() {
+ it('has no parent', function() {
+ var c = canvas.fromXML('<section></section>');
+ expect(c.doc().parent()).to.be.null;
+ });
+ });
+
+ describe('DocumentTextElement', function() {
+ it('can have its content set', function() {
+ var c = canvas.fromXML('<section>Alice</section>'),
+ root = c.doc(),
+ text = root.children()[0];
+
+ text.setText('a cat');
+ expect(root.children()[0].getText()).to.equal('a cat');
+ });
+ });
+
+ describe('DocumentNodeElement', function() {
+ it('knows index of its child', function() {
+ var c = canvas.fromXML('<section><div></div><header></header><span></span></section>'),
+ root = c.doc(),
+ child = root.children()[1];
+ expect(root.childIndex(child)).to.equal(1);
+ });
+
+ it('knows WLXML tag it renders', function(){
+ var c = canvas.fromXML('<section></section>'),
+ section = c.doc();
+ expect(section.getWlxmlTag()).to.equal('section', 'initial tag is section');
+ section.setWlxmlTag('header');
+ expect(section.getWlxmlTag()).to.equal('header', 'tag is changed to header');
+ });
+
+ it('knows WLXML class of a WLXML tag it renders', function(){
+ var c = canvas.fromXML('<section class="some.class.A"></section>'),
+ section = c.doc();
+ expect(section.getWlxmlClass()).to.equal('some.class.A');
+ section.setWlxmlClass('some.class.B');
+ expect(section.getWlxmlClass()).to.equal('some.class.B');
+ section.setWlxmlClass(null);
+ expect(section.getWlxmlClass()).to.be.undefined;
+ });
+
+
+
+ describe('element has meta attributes', function() {
+ it('can change its meta attributes', function() {
+ var c = canvas.fromXML('<section><span class="uri" meta-uri="someuri"></span></section>'),
+ span = c.doc().children()[0];
+
+ expect(span.getWlxmlMetaAttr('uri')).to.equal('someuri');
+ span.setWlxmlMetaAttr('uri', 'otheruri');
+ expect(span.getWlxmlMetaAttr('uri')).to.equal('otheruri');
+ });
+
+ it('changes its meta attributes with class change', function() {
+ var c = canvas.fromXML('<section><span class="uri" meta-uri="someuri"></span></section>'),
+ span = c.doc().children()[0];
+
+ expect(span.getWlxmlMetaAttr('uri')).to.equal('someuri');
+ span.setWlxmlClass('author');
+ expect(span.getWlxmlMetaAttr('uri')).to.be.undefined;
+ });
+
+ it('keeps meta attribute value on class change if a new class has this attribute', function() {
+ var c = canvas.fromXML('<section><span class="uri" meta-uri="someuri"></span></section>'),
+ span = c.doc().children()[0];
+ span.setWlxmlClass('uri.some.subclass');
+ expect(span.getWlxmlMetaAttr('uri')).to.equal('someuri');
+ });
+ });
+ });
+
+ it('returns DocumentNodeElement instance from HTMLElement', function() {
+ var c = canvas.fromXML('<section></section>'),
+ htmlElement = c.doc().dom().get(0),
+ element = c.getDocumentElement(htmlElement);
+ expect(element).to.be.instanceOf(documentElement.DocumentNodeElement);
+ expect(element.sameNode(c.doc()));
+ });
+
+ it('returns DocumentTextElement instance from Text Node', function() {
+ var c = canvas.fromXML('<section>Alice</section>'),
+ aliceElement = c.doc().children()[0],
+ textNode = aliceElement.dom().contents()[0],
+ element = c.getDocumentElement(textNode);
+
+ expect(textNode.nodeType).to.equal(Node.TEXT_NODE, 'text node selected');
+ expect($(textNode).text()).to.equal('Alice');
+
+ expect(element).to.be.instanceOf(documentElement.DocumentTextElement);
+ expect(element.sameNode(c.doc().children()[0]));
+ });
+ });
+
+
+
+ describe('document representation api', function() {
+ describe('document root element', function() {
+ var c = canvas.fromXML('<section></section>');
+ it('exists', function() {
+ expect(c.doc()).to.be.instanceOf(documentElement.DocumentElement);
+ });
+ it('is of type DocumentNodeElement', function() {
+ expect(c.doc()).to.be.instanceOf(documentElement.DocumentNodeElement);
+ });
+ });
+
+ describe('DocumentElements comparison', function() {
+ it('reports dwo DocumentElements to be the same when they represent the same wlxml document element', function() {
+ var c = canvas.fromXML('<section><div></div><div></div></section>'),
+ first_div1 = c.doc().children()[0],
+ first_div2 = c.doc().children()[0],
+ second_div = c.doc().children()[1];
+ expect(first_div1.sameNode(first_div1)).to.be.true;
+ expect(first_div1.sameNode(first_div2)).to.be.true;
+ expect(first_div1.sameNode(second_div)).to.be.false;
+ });
+ });
+
+ describe('traversing', function() {
+ it('reports element nodes', function() {
+ var c = canvas.fromXML('<section><div></div></section>'),
+ children = c.doc().children();
+ expect(children.length).to.equal(1);
+ expect(children[0]).to.be.instanceOf(documentElement.DocumentNodeElement);
+
+ c = canvas.fromXML('<section><div></div><div></div></section>'),
+ children = c.doc().children();
+ expect(children.length).to.equal(2);
+ expect(children[0]).to.be.instanceOf(documentElement.DocumentNodeElement);
+ expect(children[1]).to.be.instanceOf(documentElement.DocumentNodeElement);
+ });
+
+ it('reports text nodes', function() {
+ var c = canvas.fromXML('<section>Alice</section>'),
+ children = c.doc().children();
+ expect(children.length).to.equal(1);
+ expect(children[0]).to.be.instanceOf(documentElement.DocumentTextElement);
+ });
+
+ describe('accessing parents', function() {
+ it('returns DocumentNodeElement representing parent in wlxml document as DocumentNodeElement parent', function() {
+ var c = canvas.fromXML('<section><div></div></section>'),
+ div = c.doc().children()[0];
+ expect(div.parent().sameNode(c.doc())).to.be.true;
+ });
+ it('returns DocumentNodeElement representing parent in wlxml document as DocumentTextElement parent', function() {
+ var c = canvas.fromXML('<section>Alice</section>'),
+ text = c.doc().children()[0];
+ expect(text.parent().sameNode(c.doc())).to.be.true;
+ });
+ });
+
+ describe('accessing sibling parents of two elements', function() {
+ it('returns elements themself if they have direct common parent', function() {
+ var c = canvas.fromXML('<section>\
+ <div>\
+ <div>A</div>\
+ <div>B</div>\
+ </div>\
+ </section>'),
+ section = c.doc(),
+ wrappingDiv = c.doc().children()[0],
+ divA = wrappingDiv.children()[0],
+ divB = wrappingDiv.children()[1];
+
+ var siblingParents = c.getSiblingParents({element1: divA, element2: divB});
+
+ expect(siblingParents.element1.sameNode(divA)).to.equal(true, 'divA');
+ expect(siblingParents.element2.sameNode(divB)).to.equal(true, 'divB');
+ });
+
+ it('returns sibling parents - example 1', function() {
+ var c = canvas.fromXML('<section>Alice <span>has a cat</span></section>'),
+ section = c.doc(),
+ aliceText = section.children()[0],
+ span = section.children()[1],
+ spanText = span.children()[0];
+
+ var siblingParents = c.getSiblingParents({element1: aliceText, element2: spanText});
+
+ expect(siblingParents.element1.sameNode(aliceText)).to.equal(true, 'aliceText');
+ expect(siblingParents.element2.sameNode(span)).to.equal(true, 'span');
+ });
+ })
+
+ describe('free text handling', function() {
+ it('sees free text', function() {
+ var c = canvas.fromXML('<section>Alice <span>has</span> a cat</section>'),
+ children = c.doc().children();
+ expect(children.length).to.equal(3);
+ expect(children[0]).to.be.instanceOf(documentElement.DocumentTextElement);
+ expect(children[1]).to.be.instanceOf(documentElement.DocumentNodeElement);
+ expect(children[2]).to.be.instanceOf(documentElement.DocumentTextElement);
+ });
+ });
+
+ describe('empty nodes handling', function() {
+ it('says empty element node from XML source has one empty DocumentTextElement', function() {
+ var c = canvas.fromXML('<section></section>');
+ expect(c.doc().children()).to.have.length(1);
+ expect(c.doc().children()[0].getText()).to.equal('');
+ });
+
+ it('allows creation of an empty element node', function() {
+ var c = canvas.fromXML('<section></section>'),
+ section = c.doc(),
+ header = section.append({tag: 'header'});
+ expect(header.children()).to.have.length(0);
+ });
+ });
+
+ describe('white characters handling', function() {
+
+ it('says element node with one space has one DocumentTextElement', function() {
+ var c = canvas.fromXML('<section> </section>');
+ expect(c.doc().children().length).to.equal(1);
+ expect(c.doc().children()[0]).to.be.instanceOf(documentElement.DocumentTextElement);
+ expect(c.doc().children()[0].getText()).to.equal(' ');
+ });
+ it('ignores white space surrounding block elements', function() {
+ var c = canvas.fromXML('<section> <div></div> </section>');
+ var children = c.doc().children();
+ expect(children.length).to.equal(1);
+ expect(children[0]).to.be.instanceOf(documentElement.DocumentNodeElement);
+ });
+ it('ignores white space between block elements', function() {
+ var c = canvas.fromXML('<section><div></div> <div></div></section>');
+ var children = c.doc().children();
+ expect(children.length === 2);
+ [0,1].forEach(function(idx) {
+ expect(children[idx]).to.be.instanceOf(documentElement.DocumentNodeElement);
+ });
+ });
+
+ it('trims white space from the beginning and the end of the block elements', function() {
+ var c = canvas.fromXML('<section> Alice <span>has</span> a cat </section>');
+ expect(c.doc().children()[0].getText()).to.equal('Alice ');
+ expect(c.doc().children()[2].getText()).to.equal(' a cat');
+ });
+
+ it('normalizes string of white characters to one space at the inline element boundries', function() {
+ var c = canvas.fromXML('<span> Alice has a cat </span>');
+ expect(c.doc().children()[0].getText()).to.equal(' Alice has a cat ');
+ });
+
+ it('normalizes string of white characters to one space before inline element', function() {
+ var c = canvas.fromXML('<div>Alice has <span>a cat</span></div>');
+ expect(c.doc().children()[0].getText()).to.equal('Alice has ');
+ });
+
+ it('normalizes string of white characters to one space after inline element', function() {
+ var c = canvas.fromXML('<div>Alice has <span>a</span> cat</div>');
+ expect(c.doc().children()[2].getText()).to.equal(' cat');
+ });
+ });
+
+ describe('getting vertically first text element', function() {
+ it('returns the first child if it\'s text element, ignores metadata', function() {
+ var c = canvas.fromXML('<section><metadata><dc:author>author</dc:author></metadata>Alice<div>has</div>a cat</section>'),
+ first = c.doc().getVerticallyFirstTextElement();
+
+ expect(first.sameNode(c.doc().children()[1])).to.be.true;
+ });
+
+ it('looks recursively inside node elements if they precede text element', function() {
+ var c = canvas.fromXML('\
+ <section>\
+ <div>\
+ <div>\
+ Alice\
+ </div>\
+ </div>\
+ Some text\
+ </section>'),
+ textAlice = c.doc().children()[0].children()[0].children()[0],
+ first = c.doc().getVerticallyFirstTextElement();
+
+ expect(textAlice).to.be.instanceOf(documentElement.DocumentTextElement);
+ expect(first.sameNode(textAlice)).to.be.true;
+ });
+ });
+ });
+
+ describe('manipulation api', function() {
+
+ describe('Basic Element inserting', function() {
+ it('can put new NodeElement at the end', function() {
+ var c = canvas.fromXML('<section><div></div></section>'),
+ appended = c.doc().append({tag: 'header', klass: 'some.class'}),
+ children = c.doc().children();
+
+ expect(children.length).to.equal(2);
+ expect(children[1].sameNode(appended)).to.be.true;
+ });
+
+ it('can put new TextElement at the end', function() {
+ var c = canvas.fromXML('<section><div><div></section>'),
+ appended = c.doc().append({text: 'Alice'}),
+ children = c.doc().children();
+
+ expect(children.length).to.equal(2);
+ expect(children[1].sameNode(appended)).to.be.true;
+ expect(children[1].getText()).to.equal('Alice');
+ });
+
+ it('can put new NodeElement at the beginning', function() {
+ var c = canvas.fromXML('<section><div></div></section>'),
+ prepended = c.doc().prepend({tag: 'header', klass: 'some.class'}),
+ children = c.doc().children();
+
+ expect(children).to.have.length(2);
+ expect(children[0].sameNode(prepended)).to.be.true;
+ });
+
+ it('can put new TextElement at the beginning', function() {
+ var c = canvas.fromXML('<section><div></div></section>'),
+ prepended = c.doc().prepend({text: 'Alice'}),
+ children = c.doc().children();
+
+ expect(children).to.have.length(2)
+ expect(children[0].sameNode(prepended)).to.be.true;
+ expect(children[0].getText()).to.equal('Alice');
+ });
+
+ it('can put new NodeElement after another NodeElement', function() {
+ var c = canvas.fromXML('<section><div></div></section>'),
+ div = c.doc().children()[0],
+ added = div.after({tag: 'header', klass: 'some.class'}),
+ children = c.doc().children();
+ expect(children.length).to.equal(2);
+ expect(children[1].sameNode(added)).to.be.true;
+ });
+
+ it('can put new Nodeelement before another element', function() {
+ var c = canvas.fromXML('<section><div></div></section>'),
+ div = c.doc().children()[0],
+ added = div.before({tag: 'header', klass: 'some.class'}),
+ children = c.doc().children();
+ expect(children.length).to.equal(2);
+ expect(children[0].sameNode(added)).to.be.true;
+ });
+
+ it('can put new DocumentNodeElement after DocumentTextElement', function() {
+ var c = canvas.fromXML('<section>Alice</section>'),
+ text = c.doc().children()[0],
+ added = text.after({tag: 'p'}),
+ children = c.doc().children();
+
+ expect(children.length).to.equal(2);
+ expect(children[0]).to.be.instanceOf(documentElement.DocumentTextElement);
+ expect(children[0].getText()).to.equal('Alice');
+ expect(children[1]).to.be.instanceOf(documentElement.DocumentNodeElement);
+ expect(children[1].sameNode(added)).to.be.true;
+ });
+ it('can put new DocumentNodeElement before DocumentTextElement', function() {
+ var c = canvas.fromXML('<section>Alice</section>'),
+ text = c.doc().children()[0],
+ added = text.before({tag: 'p'}),
+ children = c.doc().children();
+
+ expect(children.length).to.equal(2);
+ expect(children[0]).to.be.instanceOf(documentElement.DocumentNodeElement);
+ expect(children[0].sameNode(added)).to.be.true;
+ expect(children[1]).to.be.instanceOf(documentElement.DocumentTextElement);
+ expect(children[1].getText()).to.equal('Alice');
+ });
+
+ it('can divide DocumentTextElement with a new DocumentNodeElement', function() {
+ var c = canvas.fromXML('<section>Alice has a cat</section>'),
+ section = c.doc(),
+ text = section.children()[0];
+
+ var returned = text.divide({tag: 'aside', klass: 'footnote', offset: 5}),
+ sectionChildren = section.children(),
+ lhsText = sectionChildren[0],
+ rhsText = sectionChildren[2];
+
+ expect(lhsText.getText()).to.equal('Alice');
+ expect(returned.sameNode(sectionChildren[1]));
+ expect(rhsText.getText()).to.equal(' has a cat');
+ });
+
+ it('treats dividing DocumentTextElement at the very end as appending after it', function() {
+ var c = canvas.fromXML('<section>Alice has a cat</section>'),
+ section = c.doc(),
+ text = section.children()[0];
+
+ var returned = text.divide({tag: 'aside', klass: 'footnote', offset: 15}),
+ sectionChildren = section.children(),
+ textElement = sectionChildren[0],
+ nodeElement = sectionChildren[1];
+
+ expect(sectionChildren.length).to.equal(2);
+ expect(textElement.getText()).to.equal('Alice has a cat');
+ expect(returned.sameNode(nodeElement)).to.be.true;
+ expect(nodeElement.getWlxmlTag()).to.equal('aside');
+ });
+
+ it('treats dividing DocumentTextElement at the very beginning as appending before it', function() {
+ var c = canvas.fromXML('<section>Alice has a cat</section>'),
+ section = c.doc(),
+ text = section.children()[0];
+
+ var returned = text.divide({tag: 'aside', klass: 'footnote', offset: 0}),
+ sectionChildren = section.children(),
+ nodeElement = sectionChildren[0],
+ textElement = sectionChildren[1];
+
+ expect(sectionChildren.length).to.equal(2);
+ expect(textElement.getText()).to.equal('Alice has a cat');
+ expect(returned.sameNode(nodeElement)).to.be.true;
+ expect(nodeElement.getWlxmlTag()).to.equal('aside');
+ });
+ });
+
+ describe('Removing elements', function() {
+ it('merges left and right DocumentTextElement sibling of a detached DocumentNodeElement', function() {
+ var c = canvas.fromXML('<section>Alice<div>has</div>a cat</section>'),
+ section = c.doc(),
+ div = section.children()[1];
+
+ div.detach();
+
+ var sectionChildren = section.children(),
+ textElement = sectionChildren[0];
+
+ expect(sectionChildren).to.have.length(1);
+ expect(textElement.getText()).to.equal('Alicea cat');
+ });
+ });
+
+ describe('Splitting text', function() {
+
+ it('splits DocumentTextElement\'s parent into two DocumentNodeElements of the same type', function() {
+ var c = canvas.fromXML('<section><header>Some header</header></section>'),
+ section = c.doc(),
+ text = section.children()[0].children()[0];
+
+ var returnedValue = text.split({offset: 5});
+ expect(section.children().length).to.equal(2, 'section has two children');
+
+ var header1 = section.children()[0];
+ var header2 = section.children()[1];
+
+ expect(header1.getWlxmlTag()).to.equal('header', 'first section child represents wlxml header');
+ expect(header1.children().length).to.equal(1, 'first header has one text child');
+ expect(header1.children()[0].getText()).to.equal('Some ', 'first header has correct content');
+ expect(header2.getWlxmlTag()).to.equal('header', 'second section child represents wlxml header');
+ expect(header2.children().length).to.equal(1, 'second header has one text child');
+ expect(header2.children()[0].getText()).to.equal('header', 'second header has correct content');
+
+ expect(returnedValue.first.sameNode(header1)).to.equal(true, 'first node returnde');
+ expect(returnedValue.second.sameNode(header2)).to.equal(true, 'second node returned');
+ });
+
+ it('leaves empty copy of DocumentNodeElement if splitting at the very beginning', function() {
+ var c = canvas.fromXML('<section><header>Some header</header></section>'),
+ section = c.doc(),
+ text = section.children()[0].children()[0];
+
+ text.split({offset: 0});
+
+ var header1 = section.children()[0];
+ var header2 = section.children()[1];
+
+ expect(header1.children().length).to.equal(0);
+ expect(header2.children()[0].getText()).to.equal('Some header');
+ });
+
+ it('leaves empty copy of DocumentNodeElement if splitting at the very end', function() {
+ var c = canvas.fromXML('<section><header>Some header</header></section>'),
+ section = c.doc(),
+ text = section.children()[0].children()[0];
+
+ text.split({offset: 11});
+
+ var header1 = section.children()[0];
+ var header2 = section.children()[1];
+
+ expect(header1.children()[0].getText()).to.equal('Some header');
+ expect(header2.children().length).to.equal(0);
+ });
+
+ it('keeps DocumentTextElement\'s parent\'s children elements intact', function() {
+ var c = canvas.fromXML('\
+ <section>\
+ <header>\
+ A <span>fancy</span> and <span>nice</span> header\
+ </header>\
+ </section>'),
+ section = c.doc(),
+ header = section.children()[0],
+ textAnd = header.children()[2];
+
+ textAnd.split({offset: 2});
+
+ var sectionChildren = section.children();
+ expect(sectionChildren.length).to.equal(2, 'Section has two children');
+ expect(sectionChildren[0].getWlxmlTag()).to.equal('header', 'First section element is a wlxml header');
+ expect(sectionChildren[1].getWlxmlTag()).to.equal('header', 'Second section element is a wlxml header');
+
+ var firstHeaderChildren = sectionChildren[0].children();
+ expect(firstHeaderChildren.length).to.equal(3, 'First header has three children');
+ expect(firstHeaderChildren[0].getText()).to.equal('A ', 'First header starts with a text');
+ expect(firstHeaderChildren[1].getWlxmlTag()).to.equal('span', 'First header has span in the middle');
+ expect(firstHeaderChildren[2].getText()).to.equal(' a', 'First header ends with text');
+
+ var secondHeaderChildren = sectionChildren[1].children();
+ expect(secondHeaderChildren.length).to.equal(3, 'Second header has three children');
+ expect(secondHeaderChildren[0].getText()).to.equal('nd ', 'Second header starts with text');
+ expect(secondHeaderChildren[1].getWlxmlTag()).to.equal('span', 'Second header has span in the middle');
+ expect(secondHeaderChildren[2].getText()).to.equal(' header', 'Second header ends with text');
+ });
+ });
+
+ describe('wrapping', function() {
+ it('wraps DocumentNodeElement', function() {
+ var c = canvas.fromXML('<section><div></div></section>'),
+ div = c.doc().children()[0];
+
+ var returned = div.wrapWithNodeElement({tag: 'header', klass: 'some.class'}),
+ parent = div.parent(),
+ parent2 = c.doc().children()[0];
+
+ expect(returned.sameNode(parent)).to.be.true;
+ expect(returned.sameNode(parent2)).to.be.true;
+ expect(returned.getWlxmlTag()).to.equal('header');
+ expect(returned.getWlxmlClass()).to.equal('some.class');
+ });
+ it('wraps DocumentTextElement', function() {
+ var c = canvas.fromXML('<section>Alice</section>'),
+ text = c.doc().children()[0];
+
+ var returned = text.wrapWithNodeElement({tag: 'header', klass: 'some.class'}),
+ parent = text.parent(),
+ parent2 = c.doc().children()[0];
+
+ expect(returned.sameNode(parent)).to.be.true;
+ expect(returned.sameNode(parent2)).to.be.true;
+ expect(returned.getWlxmlTag()).to.equal('header');
+ expect(returned.getWlxmlClass()).to.equal('some.class');
+ });
+
+ describe('wrapping part of DocumentTextElement', function() {
+ [{start: 5, end: 12}, {start: 12, end: 5}].forEach(function(offsets) {
+ it('wraps in the middle ' + offsets.start + '/' + offsets.end, function() {
+ var c = canvas.fromXML('<section>Alice has a cat</section>'),
+ text = c.doc().children()[0];
+
+ var returned = text.wrapWithNodeElement({tag: 'header', klass: 'some.class', start: offsets.start, end: offsets.end}),
+ children = c.doc().children();
+
+ expect(children.length).to.equal(3);
+
+ expect(children[0]).to.be.instanceOf(documentElement.DocumentTextElement);
+ expect(children[0].getText()).to.equal('Alice');
+
+ expect(children[1].sameNode(returned)).to.be.true;
+ expect(returned.getWlxmlTag()).to.equal('header');
+ expect(returned.getWlxmlClass()).to.equal('some.class');
+ expect(children[1].children().length).to.equal(1);
+ expect(children[1].children()[0].getText()).to.equal(' has a ');
+
+ expect(children[2]).to.be.instanceOf(documentElement.DocumentTextElement);
+ expect(children[2].getText()).to.equal('cat');
+ });
+ });
+
+ it('wraps whole text inside DocumentTextElement if offsets span entire content', function() {
+ var c = canvas.fromXML('<section>Alice has a cat</section>'),
+ text = c.doc().children()[0];
+
+ var returned = text.wrapWithNodeElement({tag: 'header', klass: 'some.class', start: 0, end: 15}),
+ children = c.doc().children();
+
+ expect(children.length).to.equal(1);
+ expect(children[0]).to.be.instanceOf(documentElement.DocumentNodeElement);
+ expect(children[0].children()[0].getText()).to.equal('Alice has a cat');
+ });
+ });
+
+ it('wraps text spanning multiple sibling DocumentTextNodes', function() {
+ var c = canvas.fromXML('<section>Alice has a <span>small</span> cat</section>'),
+ section = c.doc(),
+ wrapper = c.wrapText({
+ inside: section,
+ _with: {tag: 'span', klass: 'some.class'},
+ offsetStart: 6,
+ offsetEnd: 4,
+ textNodeIdx: [0,2]
+ });
+
+ expect(section.children().length).to.equal(2);
+ expect(section.children()[0]).to.be.instanceOf(documentElement.DocumentTextElement);
+ expect(section.children()[0].getText()).to.equal('Alice ');
+
+ var wrapper2 = section.children()[1];
+ expect(wrapper2.sameNode(wrapper)).to.be.true;
+
+ var wrapperChildren = wrapper.children();
+ expect(wrapperChildren.length).to.equal(3);
+ expect(wrapperChildren[0].getText()).to.equal('has a ');
+
+ expect(wrapperChildren[1]).to.be.instanceOf(documentElement.DocumentNodeElement);
+ expect(wrapperChildren[1].children().length).to.equal(1);
+ expect(wrapperChildren[1].children()[0].getText()).to.equal('small');
+
+ expect(wrapperChildren[2].getText()).to.equal(' cat');
+ });
+
+ it('wraps multiple sibling Elements', function() {
+ var c = canvas.fromXML('<section>Alice<div>has</div><div>a cat</div></section>'),
+ section = c.doc(),
+ aliceText = section.children()[0],
+ firstDiv = section.children()[1],
+ lastDiv = section.children()[section.children().length -1];
+
+ var returned = c.wrapElements({
+ element1: aliceText,
+ element2: lastDiv,
+ _with: {tag: 'header'}
+ });
+
+ var sectionChildren = section.children(),
+ header = sectionChildren[0],
+ headerChildren = header.children();
+
+ expect(sectionChildren).to.have.length(1);
+ expect(header.sameNode(returned)).to.equal(true, 'wrapper returned');
+ expect(headerChildren).to.have.length(3);
+ expect(headerChildren[0].sameNode(aliceText)).to.equal(true, 'first node wrapped');
+ expect(headerChildren[1].sameNode(firstDiv)).to.equal(true, 'second node wrapped');
+ expect(headerChildren[2].sameNode(lastDiv)).to.equal(true, 'third node wrapped');
+ });
+ it('wraps multiple sibling Elements - middle case', function() {
+ var c = canvas.fromXML('<section><div></div>div></div><div></div><div></div></section>'),
+ section = c.doc(),
+ div1 = section.children()[0],
+ div2 = section.children()[1],
+ div3 = section.children()[2],
+ div4 = section.children()[3];
+
+ var returned = c.wrapElements({
+ element1: div2,
+ element2: div3,
+ _with: {tag: 'header'}
+ });
+
+ var sectionChildren = section.children(),
+ header = sectionChildren[1],
+ headerChildren = header.children();
+
+ expect(sectionChildren).to.have.length(3);
+ expect(headerChildren).to.have.length(2);
+ expect(headerChildren[0].sameNode(div2)).to.equal(true, 'first node wrapped');
+ expect(headerChildren[1].sameNode(div3)).to.equal(true, 'second node wrapped');
+ });
+ });
+
+ describe('unwrapping DocumentTextElement from its parent DocumentNodeElement if it\'s its only child', function() {
+ it('unwraps text element from its parent and stays between its old parent siblings', function() {
+ var c = canvas.fromXML('<section><div>Alice</div><div>has</div><div>a cat</div></section>'),
+ section = c.doc(),
+ sectionChildren = section.children(),
+ divAlice = sectionChildren[0],
+ divHas = sectionChildren[1],
+ textHas = divHas.children()[0],
+ divCat = sectionChildren[2];
+
+ var newTextContainer = textHas.unwrap(),
+ sectionChildren = section.children();
+
+ expect(sectionChildren[0].sameNode(divAlice)).to.equal(true, 'divAlice ok');
+ expect(newTextContainer.sameNode(section)).to.equal(true, 'unwrap returns new text parent DocumentNodeElement');
+ expect(sectionChildren[1].getText()).to.equal('has');
+ expect(sectionChildren[2].sameNode(divCat)).to.equal(true, 'divCat ok');
+
+ });
+ it('unwraps and join with its old parent adjacent text elements ', function() {
+ var c = canvas.fromXML('<section>Alice <span>has a</span> cat</section>'),
+ section = c.doc(),
+ text = section.children()[1].children()[0];
+
+ var newTextContainer = text.unwrap();
+
+ expect(section.children().length).to.equal(1, 'section has one child');
+ expect(section.children()[0].getText()).to.equal('Alice has a cat');
+ expect(newTextContainer.sameNode(c.doc())).to.equal(true, 'unwrap returns new text parent DocumentNodeElement');
+ });
+
+ it('unwraps text element from its parent - first child case', function() {
+ var c = canvas.fromXML('<section><span class="uri">Some</span>text</section>'),
+ section = c.doc(),
+ span = section.children()[0];
+
+ span.children()[0].unwrap();
+
+ var sectionChildren = section.children();
+
+ expect(sectionChildren).to.have.length(1);
+ expect(sectionChildren[0].getText()).to.equal('Sometext');
+ });
+ });
+
+ describe('unwrapping the whole content of a DocumentNodeElement', function() {
+ it('removes a DocumentNodeElement but keeps its content', function() {
+ var c = canvas.fromXML('<section><div>Alice has<span>a</span> cat</div></section>'),
+ section = c.doc(),
+ div = c.doc().children()[0],
+ span = div.children()[1];
+
+ var range = div.unwrapContents(),
+ sectionChildren = section.children();
+
+ expect(sectionChildren).to.have.length(3);
+ expect(sectionChildren[0].getText()).to.equal('Alice has');
+ expect(sectionChildren[1].sameNode(span)).to.equal(true, 'span ok');
+ expect(sectionChildren[2].getText()).to.equal(' cat');
+
+ expect(range.element1.sameNode(sectionChildren[0])).to.equal(true, 'range start ok');
+ expect(range.element2.sameNode(sectionChildren[2])).to.equal(true, 'range end ok');
+ });
+ it('merges text elements on the boundries', function() {
+ var c = canvas.fromXML('<section>Alice<div>has a <span>cat</span>!</div>!!</section>'),
+ section = c.doc(),
+ div = c.doc().children()[1],
+ span = div.children()[1];
+
+ var range = div.unwrapContents(),
+ sectionChildren = section.children();
+
+ expect(sectionChildren).to.have.length(3);
+ expect(sectionChildren[0].getText()).to.equal('Alicehas a ');
+ expect(sectionChildren[1].sameNode(span)).to.equal(true, 'span ok');
+ expect(sectionChildren[2].getText()).to.equal('!!!');
+
+ expect(range.element1.sameNode(sectionChildren[0])).to.equal(true, 'range start ok');
+ expect(range.element2.sameNode(sectionChildren[2])).to.equal(true, 'range end ok');
+ });
+
+ it('merges text elements on the boundries - single child case', function() {
+ var c = canvas.fromXML('<section>Alice <span>has</span> a cat</section>'),
+ section = c.doc(),
+ span = section.children()[1];
+
+ var range = span.unwrapContents(),
+ sectionChildren = section.children();
+
+ expect(sectionChildren).to.have.length(1);
+ expect(sectionChildren[0].getText()).to.equal('Alice has a cat');
+ });
+ });
+
+ });
+
+ describe('Lists api', function() {
+ describe('creating lists', function() {
+ it('allows creation of a list from existing sibling DocumentElements', function() {
+ var c = canvas.fromXML('\
+ <section>\
+ Alice\
+ <div>has</div>\
+ a\
+ <div>cat</div>\
+ </section>'),
+ section = c.doc(),
+ textHas = section.children()[1],
+ divA = section.children()[2]
+
+ c.list.create({element1: textHas, element2: divA});
+
+ expect(section.children().length).to.equal(3, 'section has three child elements');
+
+ var child1 = section.children()[0],
+ list = section.children()[1],
+ child3 = section.children()[2];
+
+ expect(child1.getText()).to.equal('Alice');
+ expect(list.is('list')).to.equal(true, 'second child is a list');
+ expect(list.children().length).to.equal(2, 'list contains two elements');
+ list.children().forEach(function(child) {
+ expect(child.getWlxmlClass()).to.equal('item', 'list childs have wlxml class of item');
+ });
+ expect(child3.children()[0].getText()).to.equal('cat');
+ });
+
+ it('allows creating nested list from existing sibling list items', function() {
+ var c = canvas.fromXML('\
+ <section>\
+ <div class="list-items">\
+ <div class="item">A</div>\
+ <div class="item">B</div>\
+ <div class="item">C</div>\
+ <div class="item">D</div>\
+ </div>\
+ </section>'),
+ outerList = c.doc().children()[0],
+ itemB = outerList.children()[1],
+ itemC = outerList.children()[2];
+
+
+ c.list.create({element1: itemB, element2: itemC});
+
+ var outerListItems = outerList.children(),
+ innerList = outerListItems[1].children()[0],
+ innerListItems = innerList.children();
+
+ expect(outerListItems.length).to.equal(3, 'outer list has three items');
+ expect(outerListItems[0].children()[0].getText()).to.equal('A', 'first outer item ok');
+ expect(outerListItems[1].getWlxmlClass()).to.equal('item', 'inner list is wrapped by item element');
+
+ expect(innerList.is('list')).to.equal(true, 'inner list created');
+ expect(innerListItems.length).to.equal(2, 'inner list has two items');
+ expect(innerListItems[0].children()[0].getText()).to.equal('B', 'first inner item ok');
+ expect(innerListItems[1].children()[0].getText()).to.equal('C', 'second inner item ok');
+
+ expect(outerListItems[2].children()[0].getText()).to.equal('D', 'last outer item ok');
+
+ });
+
+ });
+
+ describe('extracting list items', function() {
+ it('creates two lists with extracted items in the middle if extracting from the middle of the list', function() {
+ var c = canvas.fromXML('\
+ <section>\
+ <div class="list.items">\
+ <div class="item">0</div>\
+ <div class="item">1</div>\
+ <div class="item">2</div>\
+ <div class="item">3</div>\
+ </div>\
+ </section>'),
+ list = c.doc().children()[0],
+ item1 = list.children()[1],
+ item2 = list.children()[2];
+
+ c.list.extractItems({element1: item1, element2: item2});
+
+ var section = c.doc(),
+ list1 = section.children()[0],
+ oldItem1 = section.children()[1],
+ oldItem2 = section.children()[2],
+ list2 = section.children()[3];
+
+ expect(section.children().length).to.equal(4, 'section contains four children');
+
+ expect(list1.is('list')).to.equal(true, 'first section child is a list');
+ expect(list1.children().length).to.equal(1, 'first list has one child');
+ expect(list1.children()[0].children()[0].getText()).to.equal('0', 'first item of the first list is a first item of the original list');
+
+ expect(oldItem1.children()[0].getText()).to.equal('1', 'first item got extracted');
+ expect(oldItem1.getWlxmlClass() === undefined).to.equal(true, 'first extracted element has no wlxml class');
+
+ expect(oldItem2.children()[0].getText()).to.equal('2', 'second item got extracted');
+ expect(oldItem2.getWlxmlClass() === undefined).to.equal(true, 'second extracted element has no wlxml class');
+
+ expect(list2.is('list')).to.equal(true, 'last section child is a list');
+ expect(list2.children().length).to.equal(1, 'second list has one child');
+ expect(list2.children()[0].children()[0].getText()).to.equal('3', 'first item of the second list is a last item of the original list');
+ });
+
+ it('puts extracted items above the list if starting item is the first one', function() {
+ var c = canvas.fromXML('\
+ <section>\
+ <div class="list.items">\
+ <div class="item">0</div>\
+ <div class="item">1</div>\
+ <div class="item">2</div>\
+ </div>\
+ </section>'),
+ list = c.doc().children()[0],
+ item1 = list.children()[0],
+ item2 = list.children()[1],
+ item3 = list.children()[2];
+
+ c.list.extractItems({element1: item1, element2: item2});
+
+ var section = c.doc(),
+ oldItem1 = section.children()[0],
+ oldItem2 = section.children()[1],
+ newList = section.children()[2];
+
+ expect(section.children().length).to.equal(3, 'section has three children');
+ expect(oldItem1.children()[0].getText()).to.equal('0', 'first item extracted');
+ expect(oldItem2.children()[0].getText()).to.equal('1', 'second item extracted');
+ expect(newList.is('list')).to.equal(true, 'list lies below extracted item');
+ expect(newList.children().length).to.equal(1, 'list has now one child');
+ });
+
+ it('puts extracted items below the list if ending item is the last one', function() {
+ var c = canvas.fromXML('\
+ <section>\
+ <div class="list.items">\
+ <div class="item">0</div>\
+ <div class="item">1</div>\
+ <div class="item">2</div>\
+ </div>\
+ </section>'),
+ list = c.doc().children()[0],
+ item1 = list.children()[0],
+ item2 = list.children()[1],
+ item3 = list.children()[2];
+
+ c.list.extractItems({element1: item2, element2: item3});
+
+ var section = c.doc(),
+ oldItem1 = section.children()[1],
+ oldItem2 = section.children()[2],
+ newList = section.children()[0];
+
+ expect(section.children().length).to.equal(3, 'section has three children');
+ expect(oldItem1.children()[0].getText()).to.equal('1', 'first item extracted');
+ expect(oldItem2.children()[0].getText()).to.equal('2', 'second item extracted');
+ expect(newList.is('list')).to.equal(true, 'list lies above extracted item');
+ expect(newList.children().length).to.equal(1, 'list has now one child');
+ });
+
+ it('removes list if all its items are extracted', function() {
+ var c = canvas.fromXML('\
+ <section>\
+ <div class="list.items">\
+ <div class="item">some item</div>\
+ <div class="item">some item 2</div>\
+ </div>\
+ </section>'),
+ list = c.doc().children()[0],
+ item1 = list.children()[0],
+ item2 = list.children()[1];
+
+ c.list.extractItems({element1: item1, element2: item2});
+
+ var section = c.doc(),
+ list1 = section.children()[0],
+ oldItem1 = section.children()[0],
+ oldItem2 = section.children()[1];
+
+ expect(section.children().length).to.equal(2, 'section contains two children');
+ expect(oldItem1.children()[0].getText()).to.equal('some item');
+ expect(oldItem2.children()[0].getText()).to.equal('some item 2');
+ });
+
+ it('creates two lists with extracted items in the middle if extracting from the middle of the list - nested case' , function() {
+ var c = canvas.fromXML('\
+ <section>\
+ <div class="list.items">\
+ <div class="item">0</div>\
+ <div class="item">\
+ <div class="list.items">\
+ <div class="item">1.1</div>\
+ <div class="item">1.2</div>\
+ <div class="item">1.3</div>\
+ </div>\
+ </div>\
+ <div class="item">2</div>\
+ </div>\
+ </section>'),
+ list = c.doc().children()[0],
+ nestedList = list.children()[1].children()[0],
+ nestedListItem = nestedList.children()[1];
+
+ c.list.extractItems({element1: nestedListItem, element2: nestedListItem});
+
+ var section = c.doc(),
+ list = section.children()[0],
+ item1 = list.children()[0],
+ item2 = list.children()[1], //
+ item3 = list.children()[2],
+ item4 = list.children()[3], //
+ item5 = list.children()[4],
+ nestedList1 = item2.children()[0],
+ nestedList2 = item4.children()[0];
+
+ expect(list.children().length).to.equal(5, 'top list has five items');
+
+ expect(item1.children()[0].getText()).to.equal('0', 'first item ok');
+
+ expect(item2.getWlxmlClass()).to.equal('item', 'first nested list is still wrapped in item element');
+ expect(nestedList1.children().length).to.equal(1, 'first nested list is left with one child');
+ expect(nestedList1.children()[0].children()[0].getText()).to.equal('1.1', 'first nested list item left alone');
+
+ expect(item3.children()[0].getText()).to.equal('1.2', 'third item ok');
+
+ expect(item4.getWlxmlClass()).to.equal('item', 'second nested list is still wrapped in item element');
+ expect(nestedList2.children().length).to.equal(1, 'second nested list is left with one child');
+ expect(nestedList2.children()[0].children()[0].getText()).to.equal('1.3', 'second nested list item left alone');
+
+ expect(item5.children()[0].getText()).to.equal('2', 'last item ok');
+ });
+
+ it('puts extracted items below the list if ending item is the last one - nested case' , function() {
+ var c = canvas.fromXML('\
+ <section>\
+ <div class="list.items">\
+ <div class="item">0</div>\
+ <div class="item">\
+ <div class="list.items">\
+ <div class="item">1.1</div>\
+ <div class="item">1.2</div>\
+ <div class="item">1.3</div>\
+ </div>\
+ </div>\
+ <div class="item">2</div>\
+ </div>\
+ </section>'),
+ list = c.doc().children()[0],
+ nestedList = list.children()[1].children()[0],
+ nestedListItem1 = nestedList.children()[1],
+ nestedListItem2 = nestedList.children()[2];
+
+ c.list.extractItems({element1: nestedListItem1, element2: nestedListItem2});
+
+ var section = c.doc(),
+ list = section.children()[0],
+ item1 = list.children()[0],
+ item2 = list.children()[1],
+ item3 = list.children()[2],
+ item4 = list.children()[3],
+ item5 = list.children()[4];
+ nestedList = item2.children()[0];
+
+ expect(list.children().length).to.equal(5, 'top list has five items');
+ expect(item1.children()[0].getText()).to.equal('0', 'first item ok');
+ expect(item2.getWlxmlClass()).to.equal('item', 'nested list is still wrapped in item element');
+ expect(nestedList.children().length).to.equal(1, 'nested list is left with one child');
+ expect(nestedList.children()[0].children()[0].getText()).to.equal('1.1', 'nested list item left alone');
+ expect(item3.children()[0].getText()).to.equal('1.2', 'third item ok');
+ expect(item4.children()[0].getText()).to.equal('1.3', 'fourth item ok');
+ expect(item5.children()[0].getText()).to.equal('2', 'fifth item ok');
+ });
+
+ it('puts extracted items above the list if starting item is the first one - nested case' , function() {
+ var c = canvas.fromXML('\
+ <section>\
+ <div class="list.items">\
+ <div class="item">0</div>\
+ <div class="item">\
+ <div class="list.items">\
+ <div class="item">1.1</div>\
+ <div class="item">1.2</div>\
+ <div class="item">1.3</div>\
+ </div>\
+ </div>\
+ <div class="item">2</div>\
+ </div>\
+ </section>'),
+ list = c.doc().children()[0],
+ nestedList = list.children()[1].children()[0],
+ nestedListItem1 = nestedList.children()[0],
+ nestedListItem2 = nestedList.children()[1];
+
+ c.list.extractItems({element1: nestedListItem1, element2: nestedListItem2});
+
+ var section = c.doc(),
+ list = section.children()[0],
+ item1 = list.children()[0],
+ item2 = list.children()[1],
+ item3 = list.children()[2],
+ item4 = list.children()[3],
+ item5 = list.children()[4];
+ nestedList = item4.children()[0];
+
+ expect(list.children().length).to.equal(5, 'top list has five items');
+ expect(item1.children()[0].getText()).to.equal('0', 'first item ok');
+ expect(item2.children()[0].getText()).to.equal('1.1', 'second item ok');
+ expect(item3.children()[0].getText()).to.equal('1.2', 'third item ok');
+
+ expect(item4.getWlxmlClass()).to.equal('item', 'nested list is still wrapped in item element');
+ expect(nestedList.children().length).to.equal(1, 'nested list is left with one child');
+ expect(nestedList.children()[0].children()[0].getText()).to.equal('1.3', 'nested list item left alone');
+ expect(item5.children()[0].getText()).to.equal('2', 'fifth item ok');
+ });
+
+ it('removes list if all its items are extracted - nested case', function() {
+ var c = canvas.fromXML('\
+ <section>\
+ <div class="list.items">\
+ <div class="item">0</div>\
+ <div class="item">\
+ <div class="list.items">\
+ <div class="item">1.1</div>\
+ <div class="item">1.2</div>\
+ </div>\
+ </div>\
+ <div class="item">2</div>\
+ </div>\
+ </section>'),
+ list = c.doc().children()[0],
+ nestedList = list.children()[1].children()[0],
+ nestedListItem1 = nestedList.children()[0],
+ nestedListItem2 = nestedList.children()[1];
+
+ c.list.extractItems({element1: nestedListItem1, element2: nestedListItem2});
+
+ var section = c.doc(),
+ list = section.children()[0],
+ item1 = list.children()[0],
+ item2 = list.children()[1],
+ item3 = list.children()[2],
+ item4 = list.children()[3];
+
+ expect(list.children().length).to.equal(4, 'top list has four items');
+ expect(item1.children()[0].getText()).to.equal('0', 'first item ok');
+ expect(item2.children()[0].getText()).to.equal('1.1', 'second item ok');
+ expect(item3.children()[0].getText()).to.equal('1.2', 'third item ok');
+ expect(item4.children()[0].getText()).to.equal('2', 'fourth item ok');
+ });
+
+ it('extracts items out of outer most list when merge flag is set to false', function() {
+ var c = canvas.fromXML('\
+ <section>\
+ <div class="list.items">\
+ <div class="item">0</div>\
+ <div class="item">\
+ <div class="list.items">\
+ <div class="item">1.1</div>\
+ <div class="item">1.2</div>\
+ </div>\
+ </div>\
+ <div class="item">2</div>\
+ </div>\
+ </section>'),
+ section = c.doc(),
+ list = section.children()[0],
+ nestedList = list.children()[1].children()[0],
+ nestedListItem = nestedList.children()[0];
+
+ var test = c.list.extractItems({element1: nestedListItem, element2: nestedListItem, merge: false});
+
+ expect(test).to.equal(true, 'extraction status ok');
+
+ var sectionChildren = section.children(),
+ extractedItem = sectionChildren[1];
+
+ expect(sectionChildren.length).to.equal(3, 'section has three children');
+ expect(sectionChildren[0].is('list')).to.equal(true, 'first child is a list');
+
+ expect(extractedItem.getWlxmlTag()).to.equal('div', 'extracted item is a wlxml div');
+ expect(extractedItem.getWlxmlClass()).to.equal(undefined, 'extracted item has no wlxml class');
+ expect(extractedItem.children()[0].getText()).to.equal('1.1', 'extracted item ok');
+ expect(sectionChildren[2].is('list')).to.equal(true, 'second child is a list');
+ });
+ });
+ });
+
+ });
+
+ describe('Cursor', function() {
+
+ var getSelection;
+
+ var findTextNode = function(inside, text) {
+ var nodes = inside.find(':not(iframe)').addBack().contents().filter(function() {
+ return this.nodeType === Node.TEXT_NODE && this.data === text;
+ });
+ if(nodes.length)
+ return nodes[0];
+ return null;
+ }
+
+ beforeEach(function() {
+ getSelection = sinon.stub(window, 'getSelection');
+ });
+
+ afterEach(function() {
+ getSelection.restore();
+ });
+
+ it('returns position when browser selection collapsed', function() {
+ var c = canvas.fromXML('<section>Alice has a cat</section>'),
+ dom = c.doc().dom(),
+ text = findTextNode(dom, 'Alice has a cat');
+
+ expect(text.nodeType).to.equal(Node.TEXT_NODE, 'correct node selected');
+ expect($(text).text()).to.equal('Alice has a cat');
+
+ getSelection.returns({
+ anchorNode: text,
+ focusNode: text,
+ anchorOffset: 5,
+ focusOffset: 5,
+ isCollapsed: true
+ });
+ var cursor = c.getCursor(),
+ position = cursor.getPosition();
+
+ expect(cursor.isSelecting()).to.equal(false, 'cursor is not selecting anything');
+ expect(position.element.getText()).to.equal('Alice has a cat');
+ expect(position.offset).to.equal(5);
+ expect(position.offsetAtEnd).to.equal(false, 'offset is not at end');
+
+ getSelection.returns({
+ anchorNode: text,
+ focusNode: text,
+ anchorOffset: 15,
+ focusOffset: 15,
+ isCollapsed: true
+ });
+
+ expect(cursor.getPosition().offsetAtEnd).to.equal(true, 'offset at end');
+ });
+
+ it('returns boundries of selection when browser selection not collapsed', function() {
+ var c = canvas.fromXML('<section>Alice <span>has</span> a <span>big</span> cat</section>'),
+ dom = c.doc().dom(),
+ text = {
+ alice: findTextNode(dom, 'Alice '),
+ has: findTextNode(dom, 'has'),
+ cat: findTextNode(dom, ' cat')
+ },
+ cursor = c.getCursor(),
+ aliceElement = c.getDocumentElement(text.alice),
+ catElement = c.getDocumentElement(text.cat);
+
+
+ [
+ {focus: text.alice, focusOffset: 1, anchor: text.cat, anchorOffset: 2, selectionAnchor: catElement},
+ {focus: text.cat, focusOffset: 2, anchor: text.alice, anchorOffset: 1, selectionAnchor: aliceElement}
+ ].forEach(function(s, idx) {
+ getSelection.returns({isColapsed: false, anchorNode: s.anchor, anchorOffset: s.anchorOffset, focusNode: s.focus, focusOffset: s.focusOffset});
+
+ var selectionStart = cursor.getSelectionStart(),
+ selectionEnd = cursor.getSelectionEnd(),
+ selectionAnchor = cursor.getSelectionAnchor();
+
+ expect(cursor.isSelecting()).to.equal(true, 'cursor is selecting');
+ expect(selectionStart.element.sameNode(aliceElement)).to.equal(true, '"Alice" is the start of the selection ' + idx);
+ expect(selectionStart.offset).to.equal(1, '"Alice" offset ok' + idx);
+ expect(selectionEnd.element.sameNode(catElement)).to.equal(true, '"Cat" is the start of the selection ' + idx);
+ expect(selectionEnd.offset).to.equal(2, '"Cat" offset ok' + idx);
+ expect(selectionAnchor.element.sameNode(s.selectionAnchor)).to.equal(true, 'anchor ok');
+ expect(selectionAnchor.offset).to.equal(s.anchorOffset, 'anchor offset ok');
+ });
+ });
+
+ it('recognizes when browser selection boundries lies in sibling DocumentTextElements', function() {
+ var c = canvas.fromXML('<section>Alice <span>has</span> a <span>big</span> cat</section>'),
+ dom = c.doc().dom(),
+ text = {
+ alice: findTextNode(dom, 'Alice '),
+ has: findTextNode(dom, 'has'),
+ a: findTextNode(dom, ' a '),
+ big: findTextNode(dom, 'big'),
+ cat: findTextNode(dom, ' cat'),
+ },
+ cursor = c.getCursor();
+
+ expect($(text.alice).text()).to.equal('Alice ');
+ expect($(text.has).text()).to.equal('has');
+ expect($(text.a).text()).to.equal(' a ');
+ expect($(text.big).text()).to.equal('big');
+ expect($(text.cat).text()).to.equal(' cat');
+
+ getSelection.returns({anchorNode: text.alice, focusNode: text.a});
+ expect(cursor.isSelectingSiblings()).to.equal(true, '"Alice" and "a" are children');
+
+ getSelection.returns({anchorNode: text.alice, focusNode: text.cat});
+ expect(cursor.isSelectingSiblings()).to.equal(true, '"Alice" and "cat" are children');
+
+ getSelection.returns({anchorNode: text.alice, focusNode: text.has});
+ expect(cursor.isSelectingSiblings()).to.equal(false, '"Alice" and "has" are not children');
+
+ getSelection.returns({anchorNode: text.has, focusNode: text.big});
+ expect(cursor.isSelectingSiblings()).to.equal(false, '"has" and "big" are not children');
+
+ })
+ });
+
+ describe('Serializing document to WLXML', function() {
+ it('keeps document intact when no changes have been made', function() {
+ var xmlIn = '<section>Alice<div>has</div>a <span class="uri" meta-uri="http://cat.com">cat</span>!</section>',
+ c = canvas.fromXML(xmlIn),
+ xmlOut = c.toXML();
+
+ var parser = new DOMParser(),
+ input = parser.parseFromString(xmlIn, "application/xml").childNodes[0],
+ output = parser.parseFromString(xmlOut, "application/xml").childNodes[0];
+
+ expect(input.isEqualNode(output)).to.be.true;
+ });
+
+ it('keeps arbitrary node attributes intact', function() {
+ var xmlIn = '<section a="1" xmlns:dcterms="http://purl.org/dc/terms/"></section>',
+ $xmlOut = $(canvas.fromXML(xmlIn).toXML());
+
+ expect($xmlOut.attr('a')).to.equal('1');
+ expect($xmlOut.attr('xmlns:dcterms')).to.equal('http://purl.org/dc/terms/');
+ });
+
+ it('doesn\' serialize meta attribute if its empty', function() {
+ var c;
+
+ c = canvas.fromXML('<section class="uri" meta-uri="some.uri"></section>');
+ c.doc().setWlxmlMetaAttr('uri', '');
+ expect($(c.toXML()).attr('meta-uri')).to.equal(undefined, 'overriding attribute with zero length string');
+
+ c = canvas.fromXML('<section class="uri"></section>');
+ c.doc().setWlxmlMetaAttr('uri', '');
+ expect($(c.toXML()).attr('meta-uri')).to.equal(undefined, 'setting attribute to zero length string');
+ });
+
+ describe('output xml', function() {
+ it('keeps entities intact', function() {
+ var xmlIn = '<section>< ></section>',
+ c = canvas.fromXML(xmlIn),
+ xmlOut = c.toXML();
+ expect(xmlOut).to.equal(xmlIn);
+ });
+ it('keeps entities intact when they form html/xml', function() {
+ var xmlIn = '<section><abc></section>',
+ c = canvas.fromXML(xmlIn),
+ xmlOut = c.toXML();
+ expect(xmlOut).to.equal(xmlIn);
+ });
+ });
+
+ describe('formatting output xml', function() {
+ /*it('keeps white spaces at the edges of input xml', function() {
+ var xmlIn = ' <section></section> ',
+ c = canvas.fromXML(xmlIn),
+ xmlOut = c.toXML();
+
+ expect(xmlOut.substr(4)).to.equal(' <', 'start');
+ expect(xmlOut.substr(-2)).to.equal('> ', 'end');
+ });*/
+ it('keeps white space between XML nodes', function() {
+ var xmlIn = '<section>\n\n\n<div></div>\n\n\n<div></div>\n\n\n</section>',
+ c = canvas.fromXML(xmlIn),
+ xmlOut = c.toXML();
+
+ var partsIn = xmlIn.split('\n\n\n'),
+ partsOut = xmlOut.split('\n\n\n');
+
+ expect(partsIn).to.deep.equal(partsOut);
+ });
+
+ it('keeps white space between XML nodes - inline case', function() {
+ var xmlIn = '<section>\n\n\n<span></span>\n\n\n<span></span>\n\n\n</section>',
+ c = canvas.fromXML(xmlIn);
+
+ var xmlOut = c.toXML();
+
+ var partsIn = xmlIn.split('\n\n\n'),
+ partsOut = xmlOut.split('\n\n\n');
+
+ expect(partsIn).to.deep.equal(partsOut);
+ });
+
+ it('keeps white space at the beginning of text', function() {
+ var xmlIn = '<section> abc<div>some div</div> abc</section>',
+ c = canvas.fromXML(xmlIn),
+ xmlOut = c.toXML();
+
+ expect(xmlOut).to.equal(xmlIn);
+ });
+
+ it('nests new children block elements', function() {
+ var c = canvas.fromXML('<section></section>');
+
+ c.doc().append({tag: 'header'});
+
+ var xmlOut = c.toXML();
+ expect(xmlOut.split('\n ')[0]).to.equal('<section>', 'nesting start ok');
+ expect(xmlOut.split('\n').slice(-1)[0]).to.equal('</section>', 'nesting end ok');
+
+ });
+
+ it('doesn\'t nest new children inline elements', function() {
+ var c = canvas.fromXML('<section></section>');
+
+ c.doc().append({tag: 'span'});
+
+ var xmlOut = c.toXML();
+ expect(xmlOut).to.equal('<section><span></span></section>');
+ });
+
+ it('keeps original white space at the end of text', function() {
+
+ var xmlIn = '<header> Some text ended with white space \
+ \
+ <span class="uri">Some text</span> some text\
+ \
+ </header>',
+ c = canvas.fromXML(xmlIn);
+
+ var xmlOut = c.toXML();
+ console.log(xmlOut);
+ expect(xmlOut).to.equal(xmlIn);
+ });
+
+ it('keeps white space around text node', function() {
+ var xmlIn = '<section>\
+ <header>header1</header>\
+ Some text surrounded by white space\
+ <header>header2</header>\
+ </section>',
+ c = canvas.fromXML(xmlIn);
+
+ var xmlOut = c.toXML();
+ expect(xmlOut).to.equal(xmlIn);
+ });
+
+ it('keeps white space around text node - last node case', function() {
+ var xmlIn = '<section>\
+ <header>header</header>\
+ \
+ Some text surrounded by white space\
+ \
+ </section>',
+ c = canvas.fromXML(xmlIn);
+
+ var xmlOut = c.toXML();
+ expect(xmlOut).to.equal(xmlIn);
+ });
+
+ it('keeps white space after detaching text element', function() {
+ var xmlIn = '<section><header>header</header>\n\
+ \n\
+ text1\n\
+ \n\
+ </section>',
+ expectedXmlOut = '<section><header>header</header>\n\
+ \n\
+ \n\
+ \n\
+ </section>',
+ c = canvas.fromXML(xmlIn),
+ children = c.doc().children(),
+ text = children[children.length-1];
+
+ expect(text.getText()).to.equal('text1');
+
+ text.detach();
+
+ var xmlOut = c.toXML();
+ expect(xmlOut).to.equal(expectedXmlOut);
+ });
+
+ })
+ })
+});
+
+
+});
\ No newline at end of file
--- /dev/null
+define([
+'libs/jquery',
+'libs/underscore',
+'modules/documentCanvas/classAttributes',
+'modules/documentCanvas/canvas/utils',
+'modules/documentCanvas/canvas/widgets',
+'modules/documentCanvas/canvas/wlxmlManagers'
+], function($, _, classAttributes, utils, widgets, wlxmlManagers) {
+
+'use strict';
+
+
+// DocumentElement represents a text or an element node from WLXML document rendered inside Canvas
+var DocumentElement = function(htmlElement, canvas) {
+ if(arguments.length === 0)
+ return;
+ this.canvas = canvas;
+ this._setupDOMHandler(htmlElement);
+}
+
+var elementTypeFromParams = function(params) {
+ return params.text !== undefined ? DocumentTextElement : DocumentNodeElement;
+
+};
+
+$.extend(DocumentElement, {
+ create: function(params, canvas) {
+ return elementTypeFromParams(params).create(params);
+ },
+
+ createDOM: function(params) {
+ return elementTypeFromParams(params).createDOM(params);
+ },
+
+ fromHTMLElement: function(htmlElement, canvas) {
+ var $element = $(htmlElement);
+ if(htmlElement.nodeType === Node.ELEMENT_NODE && $element.attr('document-node-element') !== undefined)
+ return DocumentNodeElement.fromHTMLElement(htmlElement, canvas);
+ if($element.attr('document-text-element') !== undefined || (htmlElement.nodeType === Node.TEXT_NODE && $element.parent().attr('document-text-element') !== undefined))
+ return DocumentTextElement.fromHTMLElement(htmlElement, canvas);
+ return undefined;
+ }
+});
+
+$.extend(DocumentElement.prototype, {
+ _setupDOMHandler: function(htmlElement) {
+ this.$element = $(htmlElement);
+ },
+ bound: function() {
+ return $.contains(document.documentElement, this.dom()[0]);
+ },
+ dom: function() {
+ return this.$element;
+ },
+ parent: function() {
+ var parents = this.$element.parents('[document-node-element]');
+ if(parents.length)
+ return DocumentElement.fromHTMLElement(parents[0], this.canvas);
+ return null;
+ },
+
+ parents: function() {
+ var parents = [],
+ parent = this.parent();
+ while(parent) {
+ parents.push(parent);
+ parent = parent.parent();
+ }
+ return parents;
+ },
+
+ sameNode: function(other) {
+ return other && (typeof other === typeof this) && other.dom()[0] === this.dom()[0];
+ },
+
+ wrapWithNodeElement: function(wlxmlNode) {
+ var wrapper = DocumentNodeElement.create({tag: wlxmlNode.tag, klass: wlxmlNode.klass}, this);
+ this.dom().replaceWith(wrapper.dom());
+ wrapper.append(this);
+ return wrapper;
+ },
+
+ markAsCurrent: function() {
+ this.canvas.markAsCurrent(this);
+ },
+
+ getVerticallyFirstTextElement: function() {
+ var toret;
+ this.children().some(function(child) {
+ if(!child.isVisible())
+ return false; // continue
+ if(child instanceof DocumentTextElement) {
+ toret = child;
+ return true; // break
+ } else {
+ toret = child.getVerticallyFirstTextElement();
+ if(toret)
+ return true; // break
+ }
+ });
+ return toret;
+ },
+
+ getPreviousTextElement: function(includeInvisible) {
+ return this.getNearestTextElement('above', includeInvisible);
+ },
+
+ getNextTextElement: function(includeInvisible) {
+ return this.getNearestTextElement('below', includeInvisible);
+ },
+
+ getNearestTextElement: function(direction, includeInvisible) {
+ includeInvisible = includeInvisible !== undefined ? includeInvisible : false;
+ var selector = '[document-text-element]' + (includeInvisible ? '' : ':visible');
+ return this.canvas.getDocumentElement(utils.nearestInDocumentOrder(selector, direction, this.dom()[0]));
+ },
+
+ isVisible: function() {
+ return this instanceof DocumentTextElement || this.getWlxmlTag() !== 'metadata';
+ },
+
+ isInsideList: function() {
+ return this.parents().some(function(parent) {
+ return parent.is('list');
+ });
+ },
+
+ exec: function(method) {
+ var manager = this.data('_wlxmlManager');
+ if(manager[method])
+ return manager[method].apply(manager, Array.prototype.slice.call(arguments, 1));
+ }
+});
+
+
+// DocumentNodeElement represents an element node from WLXML document rendered inside Canvas
+var DocumentNodeElement = function(htmlElement, canvas) {
+ DocumentElement.call(this, htmlElement, canvas);
+};
+
+$.extend(DocumentNodeElement, {
+ createDOM: function(params, canvas) {
+ var dom = $('<div>')
+ .attr('document-node-element', ''),
+ widgetsContainer = $('<div>')
+ .addClass('canvas-widgets')
+ .attr('contenteditable', false),
+ container = $('<div>')
+ .attr('document-element-content', '');
+
+ dom.append(widgetsContainer, container);
+ // Make sure widgets aren't navigable with arrow keys
+ widgetsContainer.find('*').add(widgetsContainer).attr('tabindex', -1);
+
+ var element = this.fromHTMLElement(dom[0], canvas);
+
+ element.setWlxml({tag: params.tag, klass: params.klass});
+ if(params.meta) {
+ _.keys(params.meta).forEach(function(key) {
+ element.setWlxmlMetaAttr(key, params.meta[key]);
+ });
+ }
+ element.data('other-attrs', params.others);
+
+ if(params.rawChildren && params.rawChildren.length) {
+ container.append(params.rawChildren);
+ } else if(params.prepopulateOnEmpty) {
+ element.append(DocumentTextElement.create({text: ''}));
+ }
+ return dom;
+ },
+
+ create: function(params, canvas) {
+ return this.fromHTMLElement(this.createDOM(params, canvas)[0], canvas);
+ },
+
+ fromHTMLElement: function(htmlElement, canvas) {
+ return new this(htmlElement, canvas);
+ }
+});
+
+var manipulate = function(e, params, action) {
+ var element;
+ if(params instanceof DocumentElement) {
+ element = params;
+ } else {
+ element = DocumentElement.create(params);
+ }
+ var target = (action === 'append' || action === 'prepend') ? e._container() : e.dom();
+ target[action](element.dom());
+ return element;
+};
+
+DocumentNodeElement.prototype = new DocumentElement();
+
+
+$.extend(DocumentNodeElement.prototype, {
+ _container: function() {
+ return this.dom().children('[document-element-content]');
+ },
+ detach: function() {
+ var parent = this.parent();
+ if(!parent)
+ return;
+
+ var parentChildren = parent.children(),
+ myIdx = parent.childIndex(this);
+
+ if(myIdx > 0 && myIdx < parentChildren.length) {
+ if((parentChildren[myIdx-1] instanceof DocumentTextElement) && (parentChildren[myIdx+1] instanceof DocumentTextElement)) {
+ parentChildren[myIdx-1].appendText(parentChildren[myIdx+1].getText());
+ parentChildren[myIdx+1].detach();
+ }
+ }
+ this.dom().detach();
+ this.canvas = null;
+ return this;
+ },
+ unwrapContents: function() {
+ var parent = this.parent();
+ if(!parent)
+ return;
+
+ var parentChildren = parent.children(),
+ myChildren = this.children(),
+ myIdx = parent.childIndex(this);
+
+ if(myChildren.length === 0)
+ return this.detach();
+
+ var moveLeftRange, moveRightRange, leftMerged;
+
+ if(myIdx > 0 && (parentChildren[myIdx-1] instanceof DocumentTextElement) && (myChildren[0] instanceof DocumentTextElement)) {
+ parentChildren[myIdx-1].appendText(myChildren[0].getText());
+ myChildren[0].detach();
+ moveLeftRange = true;
+ leftMerged = true;
+ } else {
+ leftMerged = false;
+ }
+
+ if(!(leftMerged && myChildren.length === 1)) {
+ if(myIdx < parentChildren.length - 1 && (parentChildren[myIdx+1] instanceof DocumentTextElement) && (myChildren[myChildren.length-1] instanceof DocumentTextElement)) {
+ parentChildren[myIdx+1].prependText(myChildren[myChildren.length-1].getText());
+ myChildren[myChildren.length-1].detach();
+ moveRightRange = true;
+ }
+ }
+
+ var childrenLength = this.children().length;
+ this.children().forEach(function(child) {
+ this.before(child);
+ }.bind(this));
+
+ this.detach();
+
+ return {
+ element1: parent.children()[myIdx + (moveLeftRange ? -1 : 0)],
+ element2: parent.children()[myIdx + childrenLength-1 + (moveRightRange ? 1 : 0)]
+ };
+ },
+ data: function() {
+ var dom = this.dom(),
+ args = Array.prototype.slice.call(arguments, 0);
+ if(args.length === 2 && args[1] === undefined)
+ return dom.removeData(args[0]);
+ return dom.data.apply(dom, arguments);
+ },
+ toXML: function(level) {
+ var node = $('<' + this.getWlxmlTag() + '>');
+
+ if(this.getWlxmlClass())
+ node.attr('class', this.getWlxmlClass());
+ var meta = this.getWlxmlMetaAttrs();
+ meta.forEach(function(attr) {
+ if(attr.value)
+ node.attr('meta-' + attr.name, attr.value);
+ });
+ _.keys(this.data('other-attrs') || {}).forEach(function(key) {
+ node.attr(key, this.data('other-attrs')[key]);
+ }, this);
+
+ var addFormatting = function() {
+ var toret = $('<div>');
+ var formattings = {};
+
+ if(this.data('orig-before') !== undefined) {
+ if(this.data('orig-before')) {
+ toret.prepend(document.createTextNode(this.data('orig-before')));
+ }
+ } else if(level && this.getWlxmlTag() !== 'span') {
+ toret.append('\n' + (new Array(level * 2 + 1)).join(' '));
+ }
+
+ toret.append(node);
+
+ if(this.data('orig-after')) {
+ toret.append(document.createTextNode(this.data('orig-after')));
+ }
+
+ /* Inside node */
+ if(this.data('orig-begin')) {
+ node.prepend(this.data('orig-begin'));
+ formattings.begin = true;
+ }
+
+ if(this.data('orig-end') !== undefined) {
+ if(this.data('orig-end')) {
+ node.append(this.data('orig-end'));
+ }
+ } else if(this.getWlxmlTag() !== 'span' && children.length){
+ node.append('\n' + (new Array(level * 2 + 1)).join(' '));
+ }
+
+ return {parts: toret.contents(), formattings: formattings};
+ }.bind(this);
+
+
+
+ var children = this.children(),
+ childParts;
+
+ var formatting = addFormatting(node);
+
+ for(var i = children.length - 1; i >= 0; i--) {
+ childParts = children[i].toXML(level + 1);
+ if(typeof childParts === 'string')
+ childParts = [document.createTextNode(childParts)];
+
+ if(formatting.formattings.begin) {
+ $(node.contents()[0]).after(childParts);
+ } else
+ node.prepend(childParts);
+ }
+ return formatting.parts;
+ },
+ append: function(params) {
+ if(params.tag !== 'span')
+ this.data('orig-end', undefined);
+ return manipulate(this, params, 'append');
+ },
+ prepend: function(params) {
+ return manipulate(this, params, 'prepend');
+ },
+ before: function(params) {
+ return manipulate(this, params, 'before');
+
+ },
+ after: function(params) {
+ return manipulate(this, params, 'after');
+ },
+ children: function() {
+ var toret = [];
+ if(this instanceof DocumentTextElement)
+ return toret;
+
+
+ var elementContent = this._container().contents();
+ var element = this;
+ elementContent.each(function(idx) {
+ var childElement = DocumentElement.fromHTMLElement(this, element.canvas);
+ if(childElement === undefined)
+ return true;
+ if(idx === 0 && elementContent.length > 1 && elementContent[1].nodeType === Node.ELEMENT_NODE && (childElement instanceof DocumentTextElement) && $.trim($(this).text()) === '')
+ return true;
+ if(idx > 0 && childElement instanceof DocumentTextElement) {
+ if(toret[toret.length-1] instanceof DocumentNodeElement && $.trim($(this).text()) === '')
+ return true;
+ }
+ toret.push(childElement);
+ });
+ return toret;
+ },
+ childIndex: function(child) {
+ var children = this.children(),
+ toret = null;
+ children.forEach(function(c, idx) {
+ if(c.sameNode(child)) {
+ toret = idx;
+ return false;
+ }
+ });
+ return toret;
+ },
+ getWlxmlTag: function() {
+ return this._container().attr('wlxml-tag');
+ },
+ setWlxmlTag: function(tag) {
+ if(tag === this.getWlxmlTag())
+ return;
+
+ this._container().attr('wlxml-tag', tag);
+ if(!this.__updatingWlxml)
+ this._updateWlxmlManager();
+ },
+ getWlxmlClass: function() {
+ var klass = this._container().attr('wlxml-class');
+ if(klass)
+ return klass.replace(/-/g, '.');
+ return undefined;
+ },
+ setWlxmlClass: function(klass) {
+ if(klass === this.getWlxmlClass())
+ return;
+
+ this.getWlxmlMetaAttrs().forEach(function(attr) {
+ if(!classAttributes.hasMetaAttr(klass, attr.name))
+ this.dom().removeAttr('wlxml-meta-' + attr.name);
+ }, this);
+
+ if(klass)
+ this._container().attr('wlxml-class', klass.replace(/\./g, '-'));
+ else
+ this._container().removeAttr('wlxml-class');
+ if(!this.__updatingWlxml)
+ this._updateWlxmlManager();
+ },
+ setWlxml: function(params) {
+ this.__updatingWlxml = true;
+ if(params.tag !== undefined)
+ this.setWlxmlTag(params.tag);
+ if(params.klass !== undefined)
+ this.setWlxmlClass(params.klass);
+ this._updateWlxmlManager();
+ this.__updatingWlxml = false;
+ },
+ _updateWlxmlManager: function() {
+ var manager = wlxmlManagers.getFor(this);
+ this.data('_wlxmlManager', manager);
+ manager.setup();
+ },
+ is: function(what) {
+ if(what === 'list' && _.contains(['list.items', 'list.items.enum'], this.getWlxmlClass()))
+ return true;
+ return false;
+ },
+
+ getWlxmlMetaAttr: function(attr) {
+ return this.dom().attr('wlxml-meta-'+attr);
+ },
+
+ getWlxmlMetaAttrs: function() {
+ var toret = [];
+ var attrList = classAttributes.getMetaAttrsList(this.getWlxmlClass());
+ attrList.all.forEach(function(attr) {
+ toret.push({name: attr.name, value: this.getWlxmlMetaAttr(attr.name) || ''});
+ }, this);
+ return toret;
+ },
+
+ setWlxmlMetaAttr: function(attr, value) {
+ this.dom().attr('wlxml-meta-'+attr, value);
+ },
+
+ toggleLabel: function(toggle) {
+ var displayCss = toggle ? 'inline-block' : 'none';
+ var label = this.dom().children('.canvas-widgets').find('.canvas-widget-label');
+ label.css('display', displayCss);
+ this.toggleHighlight(toggle);
+ },
+
+ toggleHighlight: function(toggle) {
+ this._container().toggleClass('highlighted-element', toggle);
+ },
+
+ toggle: function(toggle) {
+ var mng = this.data('_wlxmlManager');
+ if(mng) {
+ mng.toggle(toggle);
+ }
+ }
+});
+
+
+// DocumentNodeElement represents a text node from WLXML document rendered inside Canvas
+var DocumentTextElement = function(htmlElement, canvas) {
+ DocumentElement.call(this, htmlElement, canvas);
+};
+
+$.extend(DocumentTextElement, {
+ createDOM: function(params) {
+ return $('<div>')
+ .attr('document-text-element', '')
+ .text(params.text || utils.unicode.ZWS);
+ },
+
+ create: function(params, canvas) {
+ return this.fromHTMLElement(this.createDOM(params)[0]);
+ },
+
+ fromHTMLElement: function(htmlElement, canvas) {
+ return new this(htmlElement, canvas);
+ },
+ isContentContainer: function(htmlElement) {
+ return htmlElement.nodeType === Node.TEXT_NODE && $(htmlElement).parent().is('[document-text-element]');
+ }
+});
+
+DocumentTextElement.prototype = new DocumentElement();
+
+$.extend(DocumentTextElement.prototype, {
+ toXML: function(parent) {
+ return this.getText();
+ },
+ _setupDOMHandler: function(htmlElement) {
+ var $element = $(htmlElement);
+ if(htmlElement.nodeType === Node.TEXT_NODE)
+ this.$element = $element.parent();
+ else
+ this.$element = $element;
+ },
+ detach: function() {
+ this.dom().detach();
+ this.canvas = null;
+ return this;
+ },
+ setText: function(text) {
+ this.dom().contents()[0].data = text;
+ },
+ appendText: function(text) {
+ this.dom().contents()[0].data += text;
+ },
+ prependText: function(text) {
+ this.dom().contents()[0].data = text + this.dom().contents()[0].data;
+ },
+ getText: function() {
+ return this.dom().text().replace(utils.unicode.ZWS, '');
+ },
+ isEmpty: function() {
+ // Having at least Zero Width Space is guaranteed be Content Observer
+ return this.dom().contents()[0].data === utils.unicode.ZWS;
+ },
+ after: function(params) {
+ if(params instanceof DocumentTextElement || params.text)
+ return false;
+ var element;
+ if(params instanceof DocumentNodeElement) {
+ element = params;
+ } else {
+ element = DocumentNodeElement.create(params, this.canvas);
+ }
+ this.dom().wrap('<div>');
+ this.dom().parent().after(element.dom());
+ this.dom().unwrap();
+ return element;
+ },
+ before: function(params) {
+ if(params instanceof DocumentTextElement || params.text)
+ return false;
+ var element;
+ if(params instanceof DocumentNodeElement) {
+ element = params;
+ } else {
+ element = DocumentNodeElement.create(params, this.canvas);
+ }
+ this.dom().wrap('<div>');
+ this.dom().parent().before(element.dom());
+ this.dom().unwrap();
+ return element;
+ },
+ wrapWithNodeElement: function(wlxmlNode) {
+ if(typeof wlxmlNode.start === 'number' && typeof wlxmlNode.end === 'number') {
+ return this.canvas.wrapText({
+ inside: this.parent(),
+ textNodeIdx: this.parent().childIndex(this),
+ offsetStart: Math.min(wlxmlNode.start, wlxmlNode.end),
+ offsetEnd: Math.max(wlxmlNode.start, wlxmlNode.end),
+ _with: {tag: wlxmlNode.tag, klass: wlxmlNode.klass}
+ });
+ } else {
+ return DocumentElement.prototype.wrapWithNodeElement.call(this, wlxmlNode);
+ }
+ },
+ unwrap: function() {
+ var parent = this.parent(),
+ toret;
+ if(parent.children().length === 1) {
+ toret = parent.parent();
+ var grandParent = parent.parent();
+ if(grandParent) {
+ var grandParentChildren = grandParent.children(),
+ idx = grandParent.childIndex(parent),
+ prev = idx - 1 > -1 ? grandParentChildren[idx-1] : null,
+ next = idx + 1 < grandParentChildren.length ? grandParentChildren[idx+1] : null;
+
+ prev = (prev instanceof DocumentTextElement) ? prev : null;
+ next = (next instanceof DocumentTextElement) ? next : null;
+
+ if(prev && next) {
+ prev.setText(prev.getText() + this.getText() + next.getText());
+ next.detach();
+ } else if (prev || next) {
+ var target = prev ? prev : next,
+ newText = prev ? target.getText() + this.getText() : this.getText() + target.getText();
+ target.setText(newText);
+ } else {
+ parent.after(this);
+ }
+ } else {
+ parent.after(this);
+ }
+ parent.detach();
+ return toret;
+ }
+ },
+ split: function(params) {
+ var parentElement = this.parent(),
+ myIdx = parentElement.childIndex(this),
+ myCanvas = this.canvas,
+ passed = false,
+ succeedingChildren = [],
+ thisElement = this,
+ prefix = this.getText().substr(0, params.offset),
+ suffix = this.getText().substr(params.offset);
+
+ parentElement.children().forEach(function(child) {
+ if(passed)
+ succeedingChildren.push(child);
+ if(child.sameNode(thisElement))
+ passed = true;
+ });
+
+ if(prefix.length > 0)
+ this.setText(prefix);
+ else
+ this.detach();
+
+ var newElement = DocumentNodeElement.create({tag: parentElement.getWlxmlTag(), klass: parentElement.getWlxmlClass()}, myCanvas);
+ parentElement.after(newElement);
+
+ if(suffix.length > 0)
+ newElement.append({text: suffix});
+ succeedingChildren.forEach(function(child) {
+ newElement.append(child);
+ });
+
+ return {first: parentElement, second: newElement};
+ },
+ divide: function(params) {
+ var myText = this.getText();
+
+ if(params.offset === myText.length)
+ return this.after(params);
+ if(params.offset === 0)
+ return this.before(params);
+
+ var lhsText = myText.substr(0, params.offset),
+ rhsText = myText.substr(params.offset),
+ newElement = DocumentNodeElement.create({tag: params.tag, klass: params.klass}, this.canvas),
+ rhsTextElement = DocumentTextElement.create({text: rhsText});
+
+ this.setText(lhsText);
+ this.after(newElement);
+ newElement.after(rhsTextElement);
+ return newElement;
+ },
+
+ toggleHighlight: function() {
+ // do nothing for now
+ }
+});
+
+return {
+ DocumentElement: DocumentElement,
+ DocumentNodeElement: DocumentNodeElement,
+ DocumentTextElement: DocumentTextElement
+};
+
+});
\ No newline at end of file
--- /dev/null
+define([
+'modules/documentCanvas/canvas/documentElement',
+'modules/documentCanvas/canvas/utils'
+], function(documentElement, utils) {
+
+'use strict';
+
+
+var KEYS = {
+ ENTER: 13,
+ ARROW_LEFT: 37,
+ ARROW_UP: 38,
+ ARROW_RIGHT: 39,
+ ARROW_DOWN: 40,
+ BACKSPACE: 8,
+ DELETE: 46,
+ X: 88
+};
+
+var handleKey = function(event, canvas) {
+ handlers.some(function(handler) {
+ if(handles(handler, event) && handler[event.type]) {
+ handler[event.type](event, canvas);
+ return true;
+ }
+ });
+};
+
+var handles = function(handler, event) {
+ if(handler.key === event.which)
+ return true;
+ if(handler.keys && handler.keys.indexOf(event.which) !== -1)
+ return true;
+ return false;
+};
+
+var handlers = [];
+
+
+handlers.push({key: KEYS.ENTER,
+ keydown: function(event, canvas) {
+ event.preventDefault();
+ var cursor = canvas.getCursor(),
+ position = cursor.getPosition(),
+ element = position.element;
+
+ if(!cursor.isSelecting()) {
+ if(event.ctrlKey) {
+ var added = element.after({tag: 'block'});
+ added.append({text:''});
+ canvas.setCurrentElement(added, {caretTo: 'start'});
+
+ } else {
+
+ if(!(element.parent().parent())) {
+ return false; // top level element is unsplittable
+ }
+
+ var elements = position.element.split({offset: position.offset}),
+ newEmpty,
+ goto,
+ gotoOptions;
+
+ if(position.offsetAtBeginning)
+ newEmpty = elements.first;
+ else if(position.offsetAtEnd)
+ newEmpty = elements.second;
+
+ if(newEmpty) {
+ goto = newEmpty.append(documentElement.DocumentTextElement.create({text: ''}, this));
+ gotoOptions = {};
+ } else {
+ goto = elements.second;
+ gotoOptions = {caretTo: 'start'};
+ }
+
+ canvas.setCurrentElement(goto, gotoOptions);
+ }
+ }
+ }
+});
+
+handlers.push({keys: [KEYS.ARROW_UP, KEYS.ARROW_DOWN, KEYS.ARROW_LEFT, KEYS.ARROW_RIGHT],
+ keydown: function(event, canvas) {
+ var position = canvas.getCursor().getPosition(),
+ element = position.element;
+ if(element && (element instanceof documentElement.DocumentTextElement) && element.isEmpty()) {
+ var direction, caretTo;
+ if(event.which === KEYS.ARROW_LEFT || event.which === KEYS.ARROW_UP) {
+ direction = 'above';
+ caretTo = 'end';
+ } else {
+ direction = 'below';
+ caretTo = 'start';
+ }
+ var el = canvas.getDocumentElement(utils.nearestInDocumentOrder('[document-text-element]', direction, element.dom()[0]));
+ canvas.setCurrentElement(el, {caretTo: caretTo});
+ }
+ },
+ keyup: function(event, canvas) {
+ var element = canvas.getCursor().getPosition().element,
+ caretTo = false;
+ if(!element) {
+ // Chrome hack
+ var direction;
+ if(event.which === KEYS.ARROW_LEFT || event.which === KEYS.ARROW_UP) {
+ direction = 'above';
+ caretTo = 'end';
+ } else {
+ direction = 'below';
+ caretTo = 'start';
+ }
+ element = canvas.getDocumentElement(utils.nearestInDocumentOrder('[document-text-element]:visible', direction, window.getSelection().focusNode));
+ }
+ canvas.setCurrentElement(element, {caretTo: caretTo});
+ }
+});
+
+
+var selectsWholeTextElement = function(cursor) {
+ if(cursor.isSelecting() && cursor.getSelectionStart().offsetAtBeginning && cursor.getSelectionEnd().offsetAtEnd)
+ return true;
+ return false;
+}
+
+handlers.push({key: KEYS.X,
+ keydown: function(event, canvas) {
+ if(event.ctrlKey && selectsWholeTextElement(canvas.getCursor()))
+ event.preventDefault();
+ }
+});
+
+handlers.push({keys: [KEYS.BACKSPACE, KEYS.DELETE],
+ keydown: function(event, canvas) {
+ var cursor = canvas.getCursor(),
+ position = canvas.getCursor().getPosition(),
+ element = position.element;
+
+ if(cursor.isSelecting() && !cursor.isSelectingWithinElement()) {
+ event.preventDefault();
+ return;
+ }
+
+ var cursorAtOperationEdge = position.offsetAtBeginning;
+ if(event.which === KEYS.DELETE) {
+ cursorAtOperationEdge = position.offsetAtEnd;
+ }
+
+ var willDeleteWholeText = function() {
+ return element.getText().length === 1 || selectsWholeTextElement(cursor);
+ }
+
+ if(willDeleteWholeText()) {
+ event.preventDefault();
+ element.setText('');
+ }
+ else if(element.isEmpty()) {
+
+ var direction = 'above',
+ caretTo = 'end';
+
+ if(event.which === KEYS.DELETE) {
+ direction = 'below';
+ caretTo = 'start';
+ }
+
+ event.preventDefault();
+
+ var parent = element.parent(),
+ grandParent = parent ? parent.parent() : null,
+ goto;
+ if(parent.children().length === 1 && parent.children()[0].sameNode(element)) {
+ if(grandParent && grandParent.children().length === 1) {
+ goto = grandParent.append({text: ''});
+ } else {
+ goto = element.getNearestTextElement(direction);
+ }
+ parent.detach();
+ } else {
+ goto = element.getNearestTextElement(direction);
+ element.detach();
+ }
+ canvas.setCurrentElement(goto, {caretTo: caretTo});
+ canvas.publisher('contentChanged');
+ }
+ else if(cursorAtOperationEdge) {
+ // todo
+ event.preventDefault();
+ }
+ }
+});
+
+return {
+ handleKey: handleKey
+};
+
+});
\ No newline at end of file
--- /dev/null
+define([
+'libs/jquery',
+], function($) {
+
+'use strict';
+
+
+var nearestInDocumentOrder = function(selector, direction, element) {
+ var parents = $(element).parents(),
+ parent = parents.length ? $(parents[parents.length-1]) : element;
+
+ var adj = parent.find(selector).filter(function() {
+ return this.compareDocumentPosition(element) & (direction === 'above' ? Node.DOCUMENT_POSITION_FOLLOWING : Node.DOCUMENT_POSITION_PRECEDING);
+ });
+
+ if(adj.length) {
+ return adj[direction === 'above' ? adj.length-1 : 0];
+ }
+ return null;
+}
+
+return {
+ nearestInDocumentOrder: nearestInDocumentOrder,
+ unicode: {
+ ZWS: '\u200B'
+ }
+};
+
+});
--- /dev/null
+define([
+'libs/chai',
+'modules/documentCanvas/canvas/utils'
+
+], function(chai, utils) {
+
+'use strict';
+
+var expect = chai.expect;
+
+
+describe('utils.nearestInDocumentOrder', function() {
+
+
+ var tests = [
+ ['return null if no match found',
+ '<span>\
+ <span></span>\
+ <div id="b">\
+ <span></span>\
+ </span>'
+ ],
+ ['returns nearest sibling if applicable',
+ '<div n1>\
+ <div n2></div>\
+ <div n3>\
+ <div id="a"></div>\
+ <div id="b"></div>\
+ <div id="c"></div>\
+ </div>\
+ <div n4></div>\
+ </div>'
+ ],
+ ['looks inside siblings children',
+ '<div>\
+ <div></div>\
+ <div>\
+ <div></div>\
+ <span>\
+ <div id="a"></div>\
+ </span>\
+ <div id="b"></div>\
+ <span>\
+ <div id="c"></div>\
+ </span>\
+ <div></div>\
+ </div>\
+ <div></div>\
+ </div>'
+ ]
+
+
+ ];
+
+ tests.forEach(function(test) {
+ var description = test[0],
+ html = test[1];
+ it(description, function() {
+ var dom = $(html),
+ a = dom.find('#a').length ? dom.find('#a')[0] : null,
+ b = dom.find('#b')[0],
+ c = dom.find('#c').length ? dom.find('#c')[0] : null;
+ expect(utils.nearestInDocumentOrder('div', 'above', b)).to.equal(a, 'above');
+ expect(utils.nearestInDocumentOrder('div', 'below', b)).to.equal(c, 'below');
+ });
+ });
+
+});
+
+});
--- /dev/null
+define([
+'libs/jquery',
+'utils/wlxml'
+], function($, wlxmlUtils) {
+
+'use strict';
+
+return {
+ labelWidget: function(tag, klass) {
+ return $('<span>')
+ .addClass('canvas-widget canvas-widget-label')
+ .text(wlxmlUtils.wlxmlTagNames[tag] + (klass ? ' / ' + wlxmlUtils.wlxmlClassNames[klass] : ''));
+ },
+
+ footnoteHandler: function(clickHandler) {
+ var mydom = $('<span>')
+ .addClass('canvas-widget canvas-widget-footnote-handle')
+ .css('display', 'inline')
+ .show();
+
+ mydom.click(function(e) {
+ e.stopPropagation();
+ clickHandler();
+ });
+
+ return mydom;
+ },
+
+ hideButton: function(clickHandler) {
+ var mydom = $('<span>x</span>')
+ .addClass('canvas-widget canvas-widget-hide-button');
+ mydom.click(function(e) {
+ e.stopPropagation();
+ clickHandler();
+ });
+ return mydom;
+ }
+
+};
+
+});
\ No newline at end of file
--- /dev/null
+[document-node-element] {
+ .canvas-widgets {
+ display: inline;
+ text-indent: 0;
+ }
+
+ .canvas-widget {
+ display: none;
+ }
+
+ .canvas-widget-label {
+ position: absolute;
+ display: none;
+ top: -20px;
+ left:0;
+ background-color: red;
+ color: white;
+ font-size:12px;
+ font-weight: bold;
+ padding: 1px 3px;
+ //width:300px;
+ opacity: 0.65;
+ font-family: monospace;
+ z-index:9999;
+ white-space: nowrap;
+ }
+
+
+
+ .canvas-widget-footnote-handle {
+ display: inline;
+ outline: 0px solid transparent;
+ cursor: pointer;
+ counter-increment: footnote;
+ vertical-align: super;
+ color: blue;
+ font-size: .8em;
+
+ &::before, {
+ content: "[" counter(footnote) "]";
+ }
+ }
+
+ .canvas-widget-hide-button {
+ @line_height: 12px;
+ @padding: 2px;
+ @temporary_footnote_hack: 10px;
+
+ position: absolute;
+ top: -(@line_height + 2 * @padding) + @temporary_footnote_hack;
+ right: 0;
+ line-height: @line_height;
+ padding: @padding;
+ font-weight: bold;
+ color: white;
+ background-image: linear-gradient(to bottom,#ee5f5b,#bd362f);
+ border-radius: 1px;
+ cursor: pointer;
+ }
+}
\ No newline at end of file
--- /dev/null
+define([
+'libs/jquery',
+'modules/documentCanvas/canvas/widgets'
+], function($, widgets) {
+
+'use strict';
+
+
+var DocumentElementWrapper = function(documentElement) {
+
+ this.documentElement = documentElement;
+
+ this.addWidget = function(widget) {
+ documentElement.dom().children('.canvas-widgets').append(widget);
+ };
+
+ this.clearWidgets = function() {
+ documentElement.dom().children('.canvas-widgets').empty();
+ }
+
+ this.setDisplayStyle = function(displayStyle) {
+ documentElement.dom().css('display', displayStyle || '');
+ documentElement._container().css('display', displayStyle || '');
+ };
+
+ this.tag = function() {
+ return documentElement.getWlxmlTag();
+ };
+
+ this.klass = function() {
+ return documentElement.getWlxmlClass();
+ };
+
+ this.toggle = function(toggle) {
+ documentElement._container().toggle(toggle);
+ }
+
+ var eventBus = documentElement.canvas ? documentElement.canvas.eventBus :
+ {trigger: function() {}};
+ this.trigger = function() {
+ eventBus.trigger.apply(eventBus, arguments);
+ }
+
+}
+
+var getDisplayStyle = function(tag, klass) {
+ if(tag === 'metadata')
+ return 'none';
+ if(tag === 'span')
+ return 'inline';
+ if(klass === 'item')
+ return null;
+ return 'block';
+}
+
+var GenericManager = function(wlxmlElement) {
+ this.el = wlxmlElement;
+};
+
+$.extend(GenericManager.prototype, {
+ setup: function() {
+ this.el.setDisplayStyle(getDisplayStyle(this.el.tag(), this.el.klass()));
+
+ this.el.clearWidgets();
+ this.el.addWidget(widgets.labelWidget(this.el.tag(), this.el.klass()));
+
+ },
+ toggle: function(toggle) {
+ this.el.toggle(toggle);
+ }
+
+})
+
+var managers = {
+ _m: {},
+ set: function(tag, klass, manager) {
+ if(!this._m[tag])
+ this._m[tag] = {};
+ this._m[tag][klass] = manager;
+ },
+ get: function(tag,klass) {
+ if(this._m[tag] && this._m[tag][klass])
+ return this._m[tag][klass];
+ return GenericManager;
+ }
+}
+
+var FootnoteManager = function(wlxmlElement) {
+ this.el = wlxmlElement;
+};
+$.extend(FootnoteManager.prototype, {
+ setup: function() {
+ this.el.clearWidgets();
+
+ var clickHandler = function() {
+ this.toggle(true);
+ }.bind(this);
+ this.footnoteHandler = widgets.footnoteHandler(clickHandler);
+ this.el.addWidget(this.footnoteHandler);
+
+ var closeHandler = function() {
+ this.toggle(false);
+
+ }.bind(this);
+ this.hideButton = widgets.hideButton(closeHandler);
+ this.el.addWidget(this.hideButton);
+
+ this.toggle(false, {silent: true});
+ },
+ toggle: function(toggle, options) {
+ options = options || {};
+ this.hideButton.toggle(toggle);
+ this.footnoteHandler.toggle(!toggle);
+
+ this.el.setDisplayStyle(toggle ? 'block' : 'inline');
+ this.el.toggle(toggle);
+ if(!options.silent)
+ this.el.trigger('elementToggled', toggle, this.el.documentElement);
+ }
+})
+managers.set('aside', 'footnote', FootnoteManager);
+
+
+var ListItemManager = function(wlxmlElement) {
+ this.el = wlxmlElement;
+};
+$.extend(ListItemManager.prototype, {
+ setup: function() {
+ this.el.clearWidgets();
+ this.el.addWidget(widgets.labelWidget(this.el.tag(), this.el.klass()));
+ this.el.documentElement._container().css({display: 'list-item'});
+ },
+ toggleBullet: function(toggle) {
+ this.el.documentElement._container().css({display : toggle ? 'list-item' : 'block'});
+ }
+});
+managers.set('div', 'item', ListItemManager);
+
+return {
+ getFor: function(documentElement) {
+ var wlxmlElement = new DocumentElementWrapper(documentElement);
+ return new (managers.get(wlxmlElement.tag(), wlxmlElement.klass()))(wlxmlElement);
+
+ }
+};
+
+});
\ No newline at end of file
--- /dev/null
+define([], function() {
+
+'use strict';
+
+var wlxmlDict = {
+ 'uri': {
+ 'uri': 'string'
+ }
+};
+
+var hasMetaAttr = function(klass, attrName, dict) {
+ dict = dict || wlxmlDict;
+ if(!klass)
+ return false;
+
+ var parts = klass.split('.');
+ var partialClass = '';
+ for(var i = 0; i < parts.length; i++) {
+ partialClass += (partialClass === '' ? '' : '.') + parts[i];
+ if(dict[partialClass] && dict[partialClass][attrName])
+ return true;
+ }
+ return false;
+};
+
+var getMetaAttrsList = function(klass, dict) {
+ dict = dict || wlxmlDict;
+ klass = klass || '';
+
+ var toret = {own: [], inheritedFrom: {}, all: []};
+ var parts = klass.split('.');
+ var partialClass = '';
+
+ var generate = function(klass) {
+ var toret = [],
+ desc = dict[klass];
+
+ if(!desc)
+ return toret;
+
+ _.keys(desc).forEach(function(key) {
+ toret.push({name: key, type: desc[key]});
+ });
+ return toret;
+ };
+
+ toret.own = generate(klass);
+ for(var i = 0; i < parts.length; i++) {
+ partialClass += (partialClass === '' ? '' : '.') + parts[i];
+ var list = generate(partialClass);
+ if(list.length > 0) {
+ toret.inheritedFrom[partialClass] = generate(partialClass);
+ toret.all = toret.all.concat(toret.inheritedFrom[partialClass]);
+ }
+ }
+ return toret;
+};
+
+return {
+ hasMetaAttr: hasMetaAttr,
+ getMetaAttrsList: getMetaAttrsList
+};
+
+});
\ No newline at end of file
--- /dev/null
+define([
+'modules/documentCanvas/canvas/documentElement'
+], function(documentElement) {
+
+'use strict';
+
+
+var gridToggled = false;
+
+var commands = {
+ _cmds: {},
+ register: function(name, command) {
+ this._cmds[name] = command;
+ },
+
+ run: function(name, params, canvas) {
+ return this._cmds[name](canvas, params);
+ }
+};
+
+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;
+
+ if(canvas.list.areItemsOfTheSameList({element1: parent1, element2: parent2})) {
+ var selectionAnchor = cursor.getSelectionAnchor();
+ canvas.list.extractItems({element1: parent1, element2: parent2});
+ canvas.setCurrentElement(selectionAnchor.element, {caretTo: selectionAnchor.offset});
+ } else if(!cursor.isSelecting()) {
+ var toUnwrap = cursor.getPosition().element,
+ parent = toUnwrap.unwrap();
+ canvas.setCurrentElement(parent);
+ }
+});
+
+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;
+
+ if(canvas.list.areItemsOfTheSameList({element1: parent1, element2: parent2})) {
+ canvas.list.create({element1: parent1, element2: parent2});
+ }
+});
+
+commands.register('list', function(canvas, params) {
+ var cursor = canvas.getCursor(),
+ selectionStart = cursor.getSelectionStart(),
+ selectionEnd = cursor.getSelectionEnd(),
+ parent1 = selectionStart.element.parent() || undefined,
+ parent2 = selectionEnd.element.parent() || undefined;
+
+ var selectionFocus = cursor.getSelectionFocus();
+
+ if(selectionStart.element.isInsideList() || selectionEnd.element.isInsideList()) {
+ return;
+ }
+
+ canvas.list.create({element1: parent1, element2: parent2});
+
+ canvas.setCurrentElement(selectionFocus.element, {caretTo: selectionFocus.offset});
+});
+
+commands.register('toggle-grid', function(canvas, params) {
+ canvas.doc().dom().find('[wlxml-tag]').toggleClass('rng-common-hoveredNode', params.toggle);
+ gridToggled = params.toggle;
+});
+
+commands.register('newNodeRequested', function(canvas, params) {
+ var cursor = canvas.getCursor(),
+ selectionStart = cursor.getSelectionStart(),
+ selectionEnd = cursor.getSelectionEnd();
+
+ if(cursor.isSelecting()) {
+ if(cursor.isSelectingSiblings()) {
+ if(cursor.isSelectingWithinElement()) {
+ var newElement = selectionStart.element.wrapWithNodeElement({tag: params.wlxmlTag, klass: params.wlxmlClass, start: selectionStart.offset, end: selectionEnd.offset}),
+ caretTo = selectionStart.offset < selectionEnd.offset ? 'start' : 'end';
+ canvas.setCurrentElement(newElement.children()[0], {caretTo: caretTo});
+ }
+ else {
+ var parent = selectionStart.element.parent(),
+ caretTo = selectionStart.element.sameNode(cursor.getSelectionAnchor().element) ? 'end' : 'start';
+
+ var wrapper = canvas.wrapText({
+ inside: parent,
+ _with: {tag: params.wlxmlTag, klass: params.wlxmlClass},
+ offsetStart: selectionStart.offset,
+ offsetEnd: selectionEnd.offset,
+ textNodeIdx: [parent.childIndex(selectionStart.element), parent.childIndex(selectionEnd.element)]
+ });
+
+ canvas.setCurrentElement(wrapper.children()[caretTo === 0 ? 0 : wrapper.children().length - 1], {caretTo: caretTo});
+ }
+ } else {
+ var siblingParents = canvas.getSiblingParents({element1: selectionStart.element, element2: selectionEnd.element})
+ if(siblingParents) {
+ canvas.wrapElements({
+ element1: siblingParents.element1,
+ element2: siblingParents.element2,
+ _with: {tag: params.wlxmlTag, klass: params.wlxmlClass}
+ });
+ }
+ }
+ } else if(canvas.getCurrentNodeElement()) {
+ var el = canvas.getCurrentNodeElement().wrapWithNodeElement({tag: params.wlxmlTag, klass: params.wlxmlClass});
+ canvas.setCurrentElement(el);
+ }
+
+
+});
+
+commands.register('footnote', function(canvas, params) {
+ var cursor = canvas.getCursor(),
+ position = cursor.getPosition(),
+ asideElement;
+
+
+ if(cursor.isSelectingWithinElement()) {
+ asideElement = position.element.wrapWithNodeElement({tag: 'aside', klass: 'footnote', start: cursor.getSelectionStart().offset, end: cursor.getSelectionEnd().offset});
+ } else {
+ asideElement = position.element.divide({tag: 'aside', klass: 'footnote', offset: position.offset});
+ asideElement.append({text: ''});
+ }
+
+ 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.unwrapContents();
+
+ if(element) {
+ var elementIsFirstChild = nodeElement.childIndex(element);
+ if(element.bound()) {
+ canvas.setCurrentElement(element, {caretTo: position.offset});
+ } else {
+ if(elementIsFirstChild) {
+ canvas.setCurrentElement(range.element1, {caretTo: 'end'});
+ } else {
+ canvas.setCurrentElement(range.element2, {caretTo: 'end'});
+ }
+ }
+ } else {
+ canvas.setCurrentElement(range.element1, {caretTo: 'start'});
+ }
+
+});
+
+
+return {
+ run: function(name, params, canvas) {
+ return commands.run(name, params, canvas);
+ }
+};
+
+});
\ No newline at end of file
--- /dev/null
+// Module that implements main WYSIWIG edit area
+
+define([
+'libs/underscore',
+'./canvas/canvas',
+'./commands',
+'libs/text!./template.html'], function(_, canvas3, commands, template) {
+
+'use strict';
+
+return function(sandbox) {
+
+ var canvas = canvas3.fromXML('', sandbox.publish);
+ var canvasWrapper = $(template);
+ var shownAlready = false;
+ var scrollbarPosition = 0,
+ cursorPosition;
+
+ canvasWrapper.onShow = function() {
+ if(!shownAlready) {
+ shownAlready = true;
+ canvas.setCurrentElement(canvas.doc().getVerticallyFirstTextElement());
+ } else {
+ canvas.setCursorPosition(cursorPosition);
+ this.find('#rng-module-documentCanvas-contentWrapper').scrollTop(scrollbarPosition);
+ }
+ };
+
+ canvasWrapper.onHide = function() {
+ scrollbarPosition = this.find('#rng-module-documentCanvas-contentWrapper').scrollTop();
+ cursorPosition = canvas.getCursor().getPosition();
+ };
+
+ /* public api */
+ return {
+ start: function() { sandbox.publish('ready'); },
+ getView: function() {
+ return canvasWrapper;
+ },
+ setDocument: function(xml) {
+ canvas.loadWlxml(xml);
+ canvasWrapper.find('#rng-module-documentCanvas-content').empty().append(canvas.view());
+ sandbox.publish('documentSet');
+ },
+ getDocument: function() {
+ return canvas.toXML();
+ },
+ modifyCurrentNodeElement: function(attr, value) {
+ var currentNodeElement = canvas.getCurrentNodeElement();
+ if(attr === 'class' || attr === 'tag') {
+ currentNodeElement['setWlxml'+(attr[0].toUpperCase() + attr.substring(1))](value);
+ } else {
+ currentNodeElement.setWlxmlMetaAttr(attr, value);
+ }
+ sandbox.publish('currentNodeElementChanged', currentNodeElement);
+ },
+ highlightElement: function(element) {
+ element.toggleHighlight(true);
+ },
+ dimElement: function(element) {
+ element.toggleHighlight(false);
+ },
+ jumpToElement: function(element) {
+ canvas.setCurrentElement(element);
+ },
+ command: function(command, params) {
+ commands.run(command, params, canvas);
+ sandbox.publish('contentChanged');
+ }
+ };
+
+};
+
+});
\ No newline at end of file
--- /dev/null
+@import 'nodes.less';
+@import 'canvas/widgets.less';
+
+#rng-module-documentCanvas {
+ height: 100%;
+}
+
+#rng-module-documentCanvas-mainArea {
+ height: 100%;
+ margin-bottom: 20px;
+}
+
+#rng-module-documentCanvas-contentWrapper {
+ border-color: #ddd;
+ border-style: solid;
+ border-width: 1px;
+ float:left;
+ width: 100%;
+ height: 100%;
+ overflow-y: scroll;
+ padding: 0 10px;
+
+ &::-webkit-scrollbar {
+ .rng-mixin-scrollbar;
+ }
+ &::-webkit-scrollbar-track {
+ .rng-mixin-scrollbar-track;
+ }
+ &::-webkit-scrollbar-thumb {
+ .rng-mixin-scrollbar-thumb;
+ }
+ &::-webkit-scrollbar-thumb:window-inactive {
+ .rng-mixin-scrollbar-thumb-window-inactive;
+ }
+
+ .canvas-wrapper {
+ outline: 0px solid transparent;
+ }
+
+ .current-text-element {
+ outline: 1px dashed black;
+ }
+
+ .current-node-element {
+ border-color: lighten(#000, 15%);
+ border-style: solid;
+ border-width: 1px;
+ }
+
+ .highlighted-element {
+ border: 1px solid red;
+ }
+
+ counter-reset: footnote;
+}
+
+.rng-module-documentCanvas-currentNode {
+ background: #fffacd;
+ border-color: grey;
+ border-style:dashed;
+ border-width:1px;
+}
+
+.rng-module-documentCanvas-hoveredNodeTag {
+ position:absolute;
+ height:20px;
+ top:-20px;
+ left:0;
+ background: #bd362f;
+ color: white;
+ font-size:9px;
+ font-weight: normal;
+ font-style: normal;
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ padding: 0 5px;
+ text-indent: 0;
+}
+
+[document-node-element] {
+ position:relative;
+ border: 1px solid white;
+}
\ No newline at end of file
--- /dev/null
+[document-text-element] {
+ font-size: 14px;
+ display: inline;
+ margin: 0 1px;
+ border: 1px solid white;
+ white-space: pre-wrap;
+}
+
+[wlxml-tag] {
+ float: none !important; /* temporaty workaround for Bootstrap's influence via [class*="span"] { float: left; } */
+ border-color: white;
+ border-style:solid;
+ border-width:1px;
+ min-height:20px;
+ position:relative;
+ text-indent: 0;
+ padding: 1px 1px;
+}
+
+[wlxml-tag=span] {
+ min-width: 10px;
+}
+
+[wlxml-tag=header] [document-text-element] {
+ font-size: 13px;
+ font-weight: bold;
+ margin: 10px 0;
+}
+
+[wlxml-tag=section] {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+[wlxml-tag=section] [wlxml-tag=section] {
+ margin-left:10px;
+}
+
+[wlxml-class|="cite"] {
+ font-style: italic;
+}
+
+[wlxml-class|="cite-code"] {
+ font-family: monospace;
+}
+
+[wlxml-class|="cite-code-xml"] {
+ color: blue;
+}
+
+[wlxml-tag=header] [wlxml-class=author] [document-text-element] {
+ font-size: 14px;
+}
+
+[wlxml-tag=header] [wlxml-class=title] [document-text-element] {
+ font-size:18px;
+}
+
+[wlxml-class|="uri"] {
+ color: blue;
+ text-decoration: underline;
+}
+
+[wlxml-class|="p"] {
+ text-indent: 1.5em;
+}
+
+[wlxml-class|="emph-tech"] {
+ font-style: italic;
+}
+
+[wlxml-tag=metadata] {
+ display:none;
+}
+
+[wlxml-class="list-items"] {
+
+ [wlxml-class="item"] {
+ display: list-item;
+ margin-left: 10px;
+ padding-left: 5px;
+ }
+}
+
+[wlxml-class="item"] {
+ [wlxml-class="list-items"] {
+ display: block;
+ }
+}
+
+
+[wlxml-class="list-items-enum"] {
+
+ counter-reset: myitem;
+
+ > [wlxml-class="item"] {
+ counter-increment: myitem;
+ margin-left: 10px;
+ padding-left: 5px;
+
+ &:before {
+ content: counter(myitem) '. ';
+ margin-right:10px;
+ padding-right:10px;
+ }
+ }
+}
+
+.canvas-silent-item {
+ display: block !important;
+ counter-increment: none !important;
+ &:before {
+ content: normal !important;
+ }
+}
+
+[wlxml-class="table"] {
+
+ display: table;
+ border: 1px solid black;
+
+ [wlxml-class="row"] {
+ display: table-row;
+ border: 1px solid black;
+ }
+ [wlxml-class="cell"] {
+ display: table-cell;
+ border: 1px solid black;
+ padding: 5px;
+ }
+
+}
+
+[wlxml-tag="aside"] {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
\ No newline at end of file
--- /dev/null
+<div id="rng-module-documentCanvas">
+ <div id="rng-module-documentCanvas-mainArea">
+ <div id="rng-module-documentCanvas-contentWrapper">
+ <div id="rng-module-documentCanvas-content"></div>
+ </div>
+ </div>
+</div>
\ No newline at end of file
--- /dev/null
+define([
+'libs/chai',
+'modules/documentCanvas/classAttributes'
+], function(chai, classAttributes) {
+
+var stubDict = {
+ 'klass': {
+ 'prop': 'string'
+ },
+ 'klass.sub1': {
+ 'prop1': 'string'
+ },
+ 'klass.sub1.sub2': {
+ 'prop2': 'string'
+ }
+};
+
+var assert = chai.assert;
+
+describe('Class attributes', function() {
+ it('class has own attribute', function() {
+ assert.ok(classAttributes.hasMetaAttr('klass.sub1.sub2', 'prop2', stubDict));
+ });
+
+ it('class has attributes from parent classes', function() {
+ assert.ok(classAttributes.hasMetaAttr('klass.sub1.sub2', 'prop', stubDict));
+ assert.ok(classAttributes.hasMetaAttr('klass.sub1.sub2', 'prop1', stubDict));
+ });
+
+ it('list of class meta attributes', function() {
+ var attrList = classAttributes.getMetaAttrsList('klass.sub1.sub2', stubDict);
+
+ assert.deepEqual(attrList.own, [{name: 'prop2', type: 'string'}]);
+ assert.deepEqual(attrList.inheritedFrom['klass.sub1'], [{name: 'prop1', type: 'string'}]);
+ assert.deepEqual(attrList.inheritedFrom.klass, [{name: 'prop', type: 'string'}]);
+ assert.deepEqual(attrList.all.sort(), [
+ {name: 'prop', type: 'string'},
+ {name: 'prop1', type: 'string'},
+ {name: 'prop2', type: 'string'}
+ ].sort(), 'all values');
+ });
+
+ it('class without meta attrs', function() {
+ var attrList = classAttributes.getMetaAttrsList('some.class', {});
+ assert.deepEqual(attrList.own, [], 'empty own list');
+ assert.deepEqual(attrList.inheritedFrom, {}, 'empty inherited dict');
+ assert.deepEqual(attrList.all, [], 'empty all list');
+ });
+
+ it('empty class', function() {
+ var attrList = classAttributes.getMetaAttrsList('', {});
+ assert.deepEqual(attrList.own, [], 'empty own list');
+ assert.deepEqual(attrList.inheritedFrom, {}, 'empty inherited dict');
+ assert.deepEqual(attrList.all, [], 'empty all list');
+ });
+});
+
+});
\ No newline at end of file
--- /dev/null
+define([
+'libs/jquery',
+'libs/underscore',
+'./restoreDialog',
+'libs/text!./templates/main.html',
+'libs/text!./templates/item.html'
+], function($, _, restoreDialog, mainTemplateSrc, itemTemplateSrc) {
+
+'use strict';
+
+return function(sandbox) {
+
+ var dom = $(_.template(mainTemplateSrc)());
+ var domNodes = {
+ itemList: dom.find('.rng-module-documentHistory-itemsList'),
+ };
+ var itemViews = [];
+
+
+ dom.find('.btn.compare').click(function(e) {
+ var selected = historyItems.getSelected();
+ sandbox.publish('compare', selected[0], selected[1]);
+ });
+
+ dom.find('.btn.restore').click(function(e) {
+ var dialog = restoreDialog.create();
+ dialog.on('restore', function(event) {
+ sandbox.publish('restoreVersion', {version: historyItems.getSelected()[0], description: event.data.description});
+ event.success();
+ });
+ dialog.show();
+ });
+
+ dom.find('.btn.display').click(function(e) {
+ sandbox.publish('displayVersion', {version: historyItems.getSelected()[0]});
+ });
+
+ var addHistoryItem = function(item, options) {
+ historyItems.add(item);
+ var view = new ItemView(item);
+ itemViews.push(view);
+ domNodes.itemList.prepend(view.dom);
+ if(options.animate) {
+ view.dom.hide().slideDown();
+ }
+ };
+
+ var toggleItemViews = function(toggle) {
+ itemViews.forEach(function(view) {
+ if(!historyItems.isSelected(view.item))
+ view.toggle(toggle);
+ });
+ };
+
+ var toggleButton = function(btn, toggle) {
+ dom.find('button.'+btn).toggleClass('disabled', !toggle);
+ };
+
+ var historyItems = {
+ _itemsById: {},
+ _selected: [],
+ select: function(item) {
+ if(this._selected.length < 2) {
+ this._selected.push(item.version);
+ this._updateUI();
+ return true;
+ }
+ return false;
+ },
+ unselect: function(item) {
+ this._selected = _.without(this._selected, item.version);
+ this._updateUI();
+ },
+ add: function(item) {
+ this._itemsById[item.version] = item;
+ },
+ isSelected: function(item) {
+ return _.contains(this._selected, item.version);
+ },
+ getSelected: function() {
+ return this._selected;
+ },
+ _updateUI: function() {
+ var len = this._selected.length;
+ if(len === 0) {
+ toggleButton('compare', false);
+ toggleButton('display', false);
+ toggleButton('restore', false);
+ }
+ if(len === 1) {
+ toggleButton('compare', false);
+ toggleButton('display', true);
+ toggleButton('restore', true);
+ }
+ if(len === 2) {
+ toggleItemViews(false);
+ toggleButton('compare', true);
+ toggleButton('display', false);
+ toggleButton('restore', false);
+ } else {
+ toggleItemViews(true);
+ }
+ }
+ };
+ historyItems._updateUI();
+
+ var ItemView = function(item) {
+ this.item = item;
+ this.dom = $(this.template(item));
+ this.dom.on('click', _.bind(this.onItemClicked, this));
+ };
+ ItemView.prototype.template = _.template(itemTemplateSrc);
+ ItemView.prototype.onItemClicked = function() {
+ if(historyItems.isSelected(this.item)) {
+ historyItems.unselect(this.item);
+ this.dimItem();
+ } else if(historyItems.select(this.item)) {
+ this.highlightItem();
+ }
+ };
+ ItemView.prototype.highlightItem = function() {
+ this.dom.addClass('highlighted');
+ };
+ ItemView.prototype.dimItem = function() {
+ this.dom.removeClass('highlighted');
+ };
+ ItemView.prototype.toggle = function(toggle) {
+ this.dom.toggleClass('disabled', !toggle);
+ };
+
+
+
+ return {
+ start: function() { sandbox.publish('ready'); },
+ addHistory: function(history, options) {
+ history.forEach(function(historyItem) {
+ addHistoryItem(historyItem, options || {});
+ });
+ },
+ getView: function() {
+ return dom;
+ }
+ };
+};
+
+});
\ No newline at end of file
--- /dev/null
+.rng-module-documentHistory {
+
+ .item {
+ padding: 5px 5px;
+ margin: 0 0 15px 0;
+ cursor: pointer;
+
+ .version {
+ float: left;
+ width: 30px;
+ font-weight: bold;
+ }
+
+ .date, .author, .description {
+ margin: 5px 10px 0 40px;
+ }
+
+ .description {
+ font-size: .9em;
+ }
+ }
+
+ .item.highlighted {
+ background: #ffec63;
+ }
+
+ .item.disabled {
+ cursor: default;
+ }
+
+ .toolbar {
+ margin: 0 0 15px 0;
+ white-space:nowrap;
+ word-spacing:0;
+ min-height: 22px;
+ button {
+ margin-right: 10px;
+ }
+ }
+
+ .rng-module-documentHistory-itemsList {
+ overflow-y: scroll;
+ position: absolute;
+ top: 30px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ &::-webkit-scrollbar {
+ .rng-mixin-scrollbar;
+ }
+ &::-webkit-scrollbar-track {
+ .rng-mixin-scrollbar-track;
+ }
+ &::-webkit-scrollbar-thumb {
+ .rng-mixin-scrollbar-thumb;
+ }
+ &::-webkit-scrollbar-thumb:window-inactive {
+ .rng-mixin-scrollbar-thumb-window-inactive;
+ }
+ }
+}
+
--- /dev/null
+define([
+'libs/text!./templates/restoreDialog.html',
+'libs/underscore',
+'libs/backbone',
+'libs/jquery'
+], function(restoreDialogTemplate, _, Backbone, $) {
+
+ var DialogView = Backbone.View.extend({
+ template: _.template(restoreDialogTemplate),
+ events: {
+ 'click .restore-btn': 'onSave',
+ 'click .cancel-btn': 'close',
+ 'click .close': 'close'
+ },
+ initialize: function() {
+ _.bindAll(this);
+ this.actionsDisabled = false;
+ },
+ show: function() {
+ this.setElement(this.template());
+ this.$el.modal({backdrop: 'static'});
+ this.$el.modal('show');
+ this.$('textarea').focus();
+ },
+ onSave: function(e) {
+ e.preventDefault();
+ var view = this;
+ this.trigger('restore', {
+ data: {description: view.$el.find('textarea').val()},
+ success: function() { view.actionsDisabled = false; view.close(); },
+ error: function() { view.actionsDisabled = false; view.close(); },
+ });
+ },
+ close: function(e) {
+ if(e)
+ e.preventDefault();
+ if(!this.actionsDisabled) {
+ this.$el.modal('hide');
+ this.$el.remove();
+ }
+ },
+ toggleButtons: function(toggle) {
+ this.$('.btn, button').toggleClass('disabled', !toggle);
+ this.$('textarea').attr('disabled', !toggle);
+ this.actionsDisabled = !toggle;
+ }
+ });
+
+ return {
+ create: function() {
+ return new DialogView();
+ }
+ };
+
+});
\ No newline at end of file
--- /dev/null
+<div class="item">
+ <div class="version"><%= version %></div>
+ <div class="date"><%= date %></div>
+ <div class="author"><%= author %></div>
+ <div class="description"><%= description %></div>
+</div>
\ No newline at end of file
--- /dev/null
+<div class="rng-module-documentHistory">
+ <div class="toolbar">
+ <div class="group">
+ <button class="btn btn-mini compare">Porównaj</button>
+ <button class="btn btn-mini restore">Przywróć</button>
+ <button class="btn btn-mini display">Zobacz</button>
+ </div>
+ </div>
+ <div style="clear:both;"></div>
+ <div class="rng-module-documentHistory-itemsList"></div>
+<div>
\ No newline at end of file
--- /dev/null
+<div class="rng-module-data-saveDialog modal hide static">
+ <div class="modal-header">
+ <button type="button" class="close">×</button>
+ <h1>Przywróć wersję</h1>
+ </div>
+ <div class="modal-body">
+ <label>Opisz powód przywrócenia</label>
+ <textarea rows="5"></textarea>
+ </div>
+ <div class="modal-footer">
+ <a href="#" class="btn btn-info btn-mini restore-btn">Przywróć</a>
+ <a href="#" class="btn btn-danger btn-mini cancel-btn">Anuluj</a>
+ </div>
+</div>
\ No newline at end of file
--- /dev/null
+define(['libs/jquery', 'libs/underscore', 'utils/wlxml', 'libs/text!./template.html'], function($, _, wlxmlUtils, template) {
+
+'use strict';
+
+return function(sandbox) {
+
+ var view = {
+ node: $(_.template(template)({tagNames: wlxmlUtils.wlxmlTagNames, classNames: wlxmlUtils.wlxmlClassNames})),
+ setup: function() {
+ var view = this;
+
+ 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(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;
+ }
+
+ sandbox.publish('command', command, params);
+ });
+ },
+ getOption: function(option) {
+ return this.node.find('.rng-module-documentToolbar-toolbarOption[data-option=' + option +']').val();
+ }
+ };
+
+ view.setup();
+
+ return {
+ start: function() { sandbox.publish('ready'); },
+ getView: function() { return view.node; },
+ getOption: function(option) { return view.getOption(option); }
+ };
+};
+
+});
\ No newline at end of file
--- /dev/null
+.rng-module-documentToolbar {
+ margin: 0;
+ white-space:nowrap;
+ word-spacing:0;
+
+ select {
+ line-height: 14px;
+ font-size:9px;
+ height: auto;
+ width: 50px;
+ padding: 1px;
+ -webkit-appearance: button;
+ -moz-appearance: button;
+ appearance: button;
+ margin-bottom: 0;
+ }
+
+ .rng-module-documentToolbar-toolbarGroup {
+ border-width: 0 1px 0 0;
+ border-style: solid;
+ border-color: #ddd;
+ padding: 0 8px 0 0;
+ margin: 0 8px 0 0;
+ float:left;
+ }
+
+}
+
--- /dev/null
+<div class="rng-module-documentToolbar">
+ <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="new-node" data-meta="div/p" data-btn-type="cmd" class="btn btn-mini">paragraf</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/uri" data-btn-type="cmd" class="btn btn-mini">uri</i></button>
+ <select class="rng-module-documentToolbar-toolbarOption" data-option="newTag-tag">
+ <% var options = ['', 'section', 'header', 'div', 'span', 'aside']; %>
+ <% options.forEach(function(option) { %>
+ <option value="<%= option %>" <% if(option==='span') { %>selected<% } %>><%= tagNames[option] %></option>
+ <% }); %>
+ </select>
+ <select class="rng-module-documentToolbar-toolbarOption" data-option="newTag-class">
+ <% var options = ['', 'author', 'title', 'cite', 'cite.code', 'cite.code.xml', 'list.items', 'item', 'uri', 'p', 'footnote', 'todo', 'emp'] %>
+ <% options.forEach(function(option) { %>
+ <option value="<%= option.replace(/\./g, '-') %>" <% if(option==='cite') { %>selected<% } %>><%= classNames[option] %></option>
+ <% }); %>
+ </select>
+ <button data-name="new-node" data-btn-type="cmd" class="btn btn-mini"><i class="icon-plus"></i></button>
+ </div>
+ <div class="rng-module-documentToolbar-toolbarGroup">
+ <button data-name="take-away-node" data-btn-type="cmd" class="btn btn-mini"><i class="icon-remove-circle"></i></button>
+ </div>
+ <div class="rng-module-documentToolbar-toolbarGroup">
+ <button data-name="list" data-btn-type="cmd" data-meta="bullets" class="btn btn-mini"><i class="icon-list"></i></button>
+ <button data-name="unwrap-node" data-btn-type="cmd" class="btn btn-mini"><i class="icon-arrow-up"></i></button>
+ <button data-name="wrap-node" data-btn-type="cmd" class="btn btn-mini"><i class="icon-arrow-down"></i></button>
+ </div>
+ <div class="rng-module-documentToolbar-toolbarGroup">
+ <button data-name="footnote" data-btn-type="cmd" class="btn btn-mini"><i class="icon-comment"></i></button>
+ </div>
+ <div class="rng-module-documentToolbar-toolbarGroup">
+ <button data-name="grid" data-btn-type="toggle" class="btn btn-mini"><i class="icon-th-large"></i></button>
+ </div>
+
+ <div style="clear: both;"></div>
+</div>
\ No newline at end of file
--- /dev/null
+define([
+'libs/jquery',
+'libs/underscore',
+'libs/text!./template.html'
+], function($, _, template) {
+
+'use strict';
+
+return function(sandbox) {
+
+ var view = {
+ dom: $(_.template(template)()),
+ setup: function() {
+
+ }
+ };
+
+ return {
+ start: function() { sandbox.publish('ready'); },
+ getView: function() { return view.dom; },
+ showMessage: function(msg) {
+ view.dom.html('<span>' + msg + '</span>').show();
+ },
+ clearMessage: function(report) {
+ view.dom.empty();
+ if(report && report.message) {
+ view.dom.html('<span class="success">' + report.message + '</span>').show().fadeOut(4000);
+ }
+ }
+
+ };
+
+};
+
+});
--- /dev/null
+.rng-module-indicator {
+ span {
+ font-weight: bold;
+ background: #f9edbe;
+ padding: 2px 5px;
+ border: solid 1px #f6e39c;
+ font-size:12px;
+ }
+
+ span.success {
+ background: #cef9be;
+ border-color: darken(#cef9be, 10%);
+ }
+}
\ No newline at end of file
--- /dev/null
+<div class="rng-module-indicator">
+
+</div>
\ No newline at end of file
--- /dev/null
+define([
+'libs/jquery',
+'libs/underscore',
+'libs/text!./template.html'], function($, _, template) {
+
+'use strict';
+
+return function(sandbox) {
+
+ var view = $(_.template(template)());
+ view.find('button').click(function(e) {
+ e.preventDefault();
+ sandbox.publish('cmd.' + $(e.target).attr('data-cmd'));
+ });
+
+ return {
+ start: function() { sandbox.publish('ready'); },
+ getView: function() {return view;},
+ setCommandEnabled: function(cmd, enabled) {
+ view.find('[data-cmd='+cmd+']').toggleClass('disabled', !enabled);
+ },
+ setVersion: function(version) {
+ view.find('.version').text(version);
+ }
+ };
+
+};
+
+});
\ No newline at end of file
--- /dev/null
+/*#rng-skelton-menu {
+ float: right;
+}*/
+
+.rng-module-mainBar {
+
+ font-size: 10px;
+
+ li {
+ display: inline;
+ border-width: 0 1px 0 0;
+ border-color: #ddd;
+ border-style: solid;
+ margin: 0 5px 0 0;
+ padding: 0 5px 0 0;
+ }
+
+ ul {
+ list-style-type: none;
+ }
+}
\ No newline at end of file
--- /dev/null
+<div class="rng-module-mainBar">
+ <ul>
+ <li>wersja: <span class="version"></span></li>
+ <li><button class="btn btn-mini btn-info" data-cmd="save">Zapisz</button></li>
+ <li><a class="btn btn-mini btn-danger" href="/">Wyjdź</a></li>
+ </ul>
+</div>
\ No newline at end of file
--- /dev/null
+define([
+'libs/jquery',
+'libs/underscore',
+'./transformations',
+'libs/text!./templates/main.html',
+'libs/text!./templates/item.html'
+], function($, _, transformations, mainTemplate, itemTemplate) {
+
+'use strict';
+
+return function(sandbox) {
+
+
+ var view = {
+ node: $(_.template(mainTemplate)()),
+ setup: function() {
+ var view = this;
+ var metaTable = this.metaTable = this.node.find('table');
+
+ this.node.find('.rng-module-metadataEditor-addBtn').click(function() {
+ var newRow = view._addMetaRow('', '');
+ $(newRow.find('td div')[0]).focus();
+ sandbox.publish('metadataChanged', view.getMetadata());
+ });
+
+ this.metaTable.on('click', '.rng-visualEditor-metaRemoveBtn', function(e) {
+ $(e.target).closest('tr').remove();
+ sandbox.publish('metadataChanged', view.getMetadata());
+ });
+
+ this.metaTable.on('keydown', '[contenteditable]', function(e) {
+ console.log(e.which);
+ if(e.which === 13) {
+ if($(document.activeElement).hasClass('rng-module-metadataEditor-metaItemKey')) {
+ metaTable.find('.rng-module-metadataEditor-metaItemValue').focus();
+ } else {
+ var input = $('<input>');
+ input.appendTo('body').focus();
+ view.node.find('.rng-module-metadataEditor-addBtn').focus();
+ input.remove();
+ }
+ e.preventDefault();
+ }
+ });
+
+
+ var onKeyUp = function(e) {
+ if(e.which !== 13)
+ sandbox.publish('metadataChanged', view.getMetadata());
+ };
+ this.metaTable.on('keyup', '[contenteditable]', _.throttle(onKeyUp, 500));
+ },
+ getMetadata: function() {
+ var toret = {};
+ this.node.find('tr').each(function() {
+ var tr = $(this);
+ var inputs = $(this).find('td [contenteditable]');
+ var key = $(inputs[0]).text();
+ var value = $(inputs[1]).text();
+ toret[key] = value;
+ });
+ return toret;
+ },
+ setMetadata: function(metadata) {
+ var view = this;
+ this.metaTable.find('tr').remove();
+ _.each(_.keys(metadata), function(key) {
+ view._addMetaRow(key, metadata[key]);
+ });
+ },
+ _addMetaRow: function(key, value) {
+ var newRow = $(_.template(itemTemplate)({key: key || '', value: value || ''}));
+ newRow.appendTo(this.metaTable);
+ return newRow;
+ }
+ };
+
+ view.setup();
+
+ return {
+ start: function() {
+ sandbox.publish('ready');
+ },
+ setDocument: function(xml) {
+ view.setMetadata(transformations.getMetadata(xml));
+ sandbox.publish('metadataSet');
+ },
+ getMetadata: function() {
+ return transformations.getXML(view.getMetadata());
+ },
+ getView: function() {
+ return view.node;
+ },
+ attachMetadata: function(document) {
+ var toret = $('<div>');
+ toret.append($(document));
+ var meta = $('<metadata></metadata>\n').append(transformations.getXML(view.getMetadata()));
+
+ var metadata = toret.find('metadata');
+ if(metadata.length === 0) {
+ var section = toret.find('section');
+ section = section.length ? $(section[0]) : null;
+ if(section) {
+ section.prepend(meta);
+ }
+ } else {
+ metadata.replaceWith(meta);
+ }
+ return toret.html();
+ }
+
+ };
+};
+
+});
\ No newline at end of file
--- /dev/null
+.rng-module-metadataEditor {
+
+ table {
+ margin-bottom:10px;
+
+ [contenteditable] {
+ cursor: pointer;
+ }
+
+ li:last-child {
+ border-bottom: none !important;
+ }
+
+ tr td:nth-child(1){
+ width: 20%;
+ }
+
+ tr td:nth-child(2) {
+ width:80%;
+ }
+ }
+
+ .rng-module-metadataEditor-addBtn {
+ float:right;
+ margin-right:6px;
+ }
+
+ .btn {
+ padding:3px;
+ line-height:10px;
+ }
+}
\ No newline at end of file
--- /dev/null
+<tr>
+ <td><div contenteditable="true" class="rng-module-metadataEditor-metaItemKey"><%= key %></div></td>
+ <td><div contenteditable="true" class="rng-module-metadataEditor-metaItemValue"><%= value %></div></td>
+ <td><button class="rng-visualEditor-metaRemoveBtn btn btn-mini btn-danger">x</button></td>
+</tr>
\ No newline at end of file
--- /dev/null
+<div class="rng-module-metadataEditor">
+ <fieldset>
+ <legend>Meta dane</legend>
+ <table class="table table-condensed table-bordered">
+ </table>
+ <button class="rng-module-metadataEditor-addBtn btn btn-mini btn-info">+</button></td>
+ </fieldset>
+</div>
\ No newline at end of file
--- /dev/null
+define(['libs/jquery'], function($) {
+
+ 'use strict';
+
+ return {
+ getMetadata: function(xml) {
+ var toret = {};
+ $(xml).find('metadata').children().each(function() {
+ var node = $(this);
+ toret[this.nodeName.split(':')[1].toLowerCase()] = node.text();
+ });
+ return toret;
+ },
+ getXML: function(metadata) {
+ var meta = $('<metadata></metadata>\n');
+ _.each(_.keys(metadata), function(key) {
+ meta.append('\n\t<dc:'+key+'>' + metadata[key] + '</dc:'+key+'>');
+ });
+ meta.append('\n');
+ return vkbeautify.xml(meta.html());
+ }
+ };
+
+});
\ No newline at end of file
--- /dev/null
+define([
+'libs/jquery',
+'libs/underscore',
+'utils/wlxml',
+'libs/text!./template.html'], function($, _, wlxmlUtils, templateSrc) {
+
+'use strict';
+
+return function(sandbox) {
+
+ var template = _.template(templateSrc);
+
+ var view = {
+ dom: $('<div>' + template({node:null, parents: null}) + '</div>'),
+ setup: function() {
+ var view = this;
+ this.dom.on('mouseenter', 'a', function(e) {
+ var target = $(e.target);
+ sandbox.publish('elementEntered', target.data('element'));
+ });
+ this.dom.on('mouseleave', 'a', function(e) {
+ var target = $(e.target);
+ sandbox.publish('elementLeft', target.data('element'));
+ });
+ this.dom.on('click', 'a', function(e) {
+ e.preventDefault();
+ var target = $(e.target);
+ sandbox.publish('elementClicked', target.data('element'));
+ });
+ },
+
+ setNodeElement: function(nodeElement) {
+ this.dom.empty();
+ this.currentNodeElement = nodeElement;
+ var parents = nodeElement.parents();
+ this.dom.html(template({node: nodeElement, parents: parents, tagNames: wlxmlUtils.wlxmlTagNames, classNames: wlxmlUtils.wlxmlClassNames}));
+
+ this.dom.find('li > a[href="#"]').each(function(idx, a) {
+ $(a).data('element', parents[parents.length - 1 - idx]);
+ });
+ this.dom.find('a.active').data('element', nodeElement);
+ },
+
+ highlightNode: function(node) {
+ this.dom.find('a[data-id="'+node.id+'"]').addClass('rng-common-hoveredNode');
+ },
+ dimNode: function(node) {
+ this.dom.find('a[data-id="'+node.id+'"]').removeClass('rng-common-hoveredNode');
+ }
+ };
+
+ view.setup();
+
+ return {
+ start: function() { sandbox.publish('ready'); },
+ getView: function() { return view.dom; },
+ setNodeElement: function(nodeElement) {
+ view.setNodeElement(nodeElement);
+ },
+ highlightNode: function(id) { view.highlightNode(id); },
+ dimNode: function(id) { view.dimNode(id); }
+ };
+};
+
+});
\ No newline at end of file
--- /dev/null
+<div class="rng-module-nodeBreadCrumbs">
+ <ul class="breadcrumb">
+ <% if(node && parents) { %>
+ <% for(var i = parents.length - 1; i >= 0; i--) { %>
+ <li><a href="#"> <%= tagNames[parents[i].getWlxmlTag()] %><% if(parents[i].getWlxmlClass()) { %>.<%= classNames[parents[i].getWlxmlClass()] %> <% } %></a><span class="divider">/</span></li>
+ <% } %>
+ <li class="active"><%= tagNames[node.getWlxmlTag()] %><% if(node.getWlxmlClass()) { %>.<%= classNames[node.getWlxmlClass()] %> <% } %></span></li>
+ <% } %>
+ </ul>
+</div>
\ No newline at end of file
--- /dev/null
+define([
+'libs/jquery',
+'libs/underscore',
+'utils/wlxml',
+'libs/text!./template.html'
+], function($, _, wlxmlUtils, templateSrc) {
+
+'use strict';
+
+return function(sandbox) {
+
+ var template = _.template(templateSrc);
+
+ var view = {
+ dom: $('<div>' + template({children: null, parent: null}) + '</div>'),
+ setup: function() {
+ var view = this;
+ this.dom.on('click', 'a', function(e) {
+ var target = $(e.target);
+ sandbox.publish('elementClicked', target.data('element'));
+ });
+
+ this.dom.on('mouseenter', 'a', function(e) {
+ var target = $(e.target);
+ sandbox.publish('elementEntered', target.data('element'));
+ });
+ this.dom.on('mouseleave', 'a', function(e) {
+ var target = $(e.target);
+ sandbox.publish('elementLeft', target.data('element'));
+ });
+ },
+ setElement: function(element) {
+ console.log('familyTree sets node');
+ var textElement = element.getText ? element : null,
+ nodeElement = element.getText ? element.parent() : element, // TODO: better type detection
+ nodeElementParent = nodeElement.parent(),
+ parent;
+
+ this.currentNodeElement = nodeElement;
+
+ if(nodeElementParent) {
+ parent = {
+ repr: wlxmlUtils.wlxmlTagNames[nodeElementParent.getWlxmlTag()] + (nodeElementParent.getWlxmlClass() ? ' / ' + wlxmlUtils.wlxmlClassNames[nodeElementParent.getWlxmlClass()] : '')
+ };
+ }
+
+ var nodeChildren = nodeElement.children(),
+ children = [];
+ nodeChildren.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 + '"';
+ }
+ children.push({repr: _.escape(text), bold: child.sameNode(textElement)});
+ } else {
+ children.push({repr: wlxmlUtils.wlxmlTagNames[child.getWlxmlTag()] + (child.getWlxmlClass() ? ' / ' + wlxmlUtils.wlxmlClassNames[child.getWlxmlClass()] : '')});
+ }
+ });
+ this.dom.empty();
+ this.dom.append($(template({parent: parent, children: children})));
+
+ if(parent) {
+ this.dom.find('.rng-module-nodeFamilyTree-parent').data('element', nodeElementParent)
+ }
+ this.dom.find('li a').each(function(idx, a) {
+ $(a).data('element', nodeChildren[idx]);
+ });
+ },
+ highlightNode: function(canvasNode) {
+ this.dom.find('a[data-id="'+canvasNode.getId()+'"]').addClass('rng-common-hoveredNode');
+ },
+ dimNode: function(canvasNode) {
+ this.dom.find('a[data-id="'+canvasNode.getId()+'"]').removeClass('rng-common-hoveredNode');
+ }
+ };
+
+ view.setup();
+
+ return {
+ start: function() {
+ sandbox.publish('ready');
+ },
+ setElement: function(element) {
+ if(!(element.sameNode(view.currentNodeElement)))
+ view.setElement(element);
+ },
+ getView: function() {
+ return view.dom;
+ },
+ highlightNode: function(canvasNode) {
+ view.highlightNode(canvasNode);
+ },
+ dimNode: function(canvasNode) {
+ view.dimNode(canvasNode);
+ }
+ };
+};
+
+});
\ No newline at end of file
--- /dev/null
+.rng-module-nodeFamilyTree {
+ overflow-y: scroll;
+ max-height: 150px;
+ width:100%;
+ margin-top:10px;
+
+ table {
+ width: 90%;
+ margin: 0;
+
+ tr {
+ td:nth-child(1) {
+ width: 30%;
+ }
+ td:nth-child(2) {
+ width: 70%;
+ }
+ td ul {
+ list-style-type: none;
+ margin: 0;
+ }
+
+ }
+ }
+
+ &::-webkit-scrollbar {
+ .rng-mixin-scrollbar;
+ }
+ &::-webkit-scrollbar-track {
+ .rng-mixin-scrollbar-track;
+ }
+ &::-webkit-scrollbar-thumb {
+ .rng-mixin-scrollbar-thumb;
+ }
+ &::-webkit-scrollbar-thumb:window-inactive {
+ .rng-mixin-scrollbar-thumb-window-inactive;
+ }
+}
\ No newline at end of file
--- /dev/null
+<div class="rng-module-nodeFamilyTree">
+ <table class="table table-bordered">
+ <tr>
+ <td>powyżej</td>
+ <td><% if(parent) { %><a href="#" class="rng-module-nodeFamilyTree-parent"><%= parent.repr %></a><% } else { %>-<% } %></td>
+ </tr>
+ <tr>
+ <td rowspan="0">poniżej</td>
+ <td>
+ <ul>
+ <% if(!children || children.length === 0) { %>-<% } else { %>
+ <% children.forEach(function(child) { %>
+ <li><% if(child.bold) { %><strong><% } %><a href="#"><%= child.repr %></a><% if(child.bold) { %></strong><% } %></li>
+ <% }); %>
+ <% } %>
+ </ul>
+ </td>
+ </tr>
+</div>
\ No newline at end of file
--- /dev/null
+define([
+'libs/jquery',
+'libs/underscore',
+'libs/backbone',
+'libs/text!./stringField.html'
+], function($, _, Backbone, stringFieldTpl) {
+
+'use strict';
+
+var templates = {
+ string: _.template(stringFieldTpl)
+};
+
+var getAttrElement = function(attr) {
+ var toret = $('<div>');
+ toret.append(templates.string({name: attr.name, value: attr.value}));
+ return toret;
+};
+
+var MetaWidget = Backbone.View.extend({
+ events: {
+ 'change [metaField-name]': 'onMetaFieldChange'
+ },
+ initialize: function() {
+ var view = this;
+ this.options.attrs.forEach(function(attr) {
+ view.$el.append(getAttrElement(attr));
+ });
+ },
+ onMetaFieldChange: function(e) {
+ var target = $(e.target);
+ this.trigger('valueChanged', target.attr('metaField-name'), target.val());
+ }
+});
+
+
+return {
+ create: function(options) {
+ return new MetaWidget(options);
+ }
+};
+
+});
\ No newline at end of file
--- /dev/null
+define([
+'libs/chai',
+'libs/sinon',
+'modules/nodePane/metaWidget/metaWidget'
+], function(chai, sinon, metaWidget) {
+
+'use strict';
+
+var assert = chai.assert;
+
+describe('metaWidget', function() {
+ it('calls calls registered callback on value change', function() {
+ var dom = $('<div>');
+ var widget = metaWidget.create({
+ el: dom,
+ attrs: [{name: 'uri', type: 'string', value: 'test string'}],
+ });
+
+ var spy = sinon.spy();
+ widget.on('valueChanged', spy);
+ var input = dom.find('input');
+
+ input.change();
+ assert.ok(spy.calledOnce, 'called once');
+ assert.ok(spy.calledWith('uri', 'test string'), 'called with');
+
+ spy.reset();
+ input.val('new val').change();
+ assert.ok(spy.calledOnce, 'called once');
+ assert.ok(spy.calledWith('uri', 'new val'), 'called with new val');
+ });
+});
+
+
+});
\ No newline at end of file
--- /dev/null
+<label><%= name %></label>
+<input type="text" metaField-name="<%= name %>" value="<%= value %>">
\ No newline at end of file
--- /dev/null
+define([
+'libs/text!./template.html',
+'libs/jquery',
+'libs/underscore',
+'modules/nodePane/metaWidget/metaWidget',
+'utils/wlxml'
+], function(templateSrc, $, _, metaWidget, wlxmlUtils) {
+
+'use strict';
+
+return function(sandbox) {
+
+ var view = $(_.template(templateSrc)({tagNames: wlxmlUtils.wlxmlTagNames, classNames: wlxmlUtils.wlxmlClassNames}));
+
+ view.on('change', 'select', function(e) {
+ var target = $(e.target);
+ var attr = target.attr('class').split('-')[3] === 'tagSelect' ? 'tag' : 'class';
+ sandbox.publish('nodeElementChange', attr, target.val().replace(/-/g, '.'));
+ });
+
+ return {
+ start: function() {
+ sandbox.publish('ready');
+ },
+ getView: function() {
+ return view;
+ },
+ setNodeElement: function(nodeElement) {
+ view.find('.rng-module-nodePane-tagSelect').val(nodeElement.getWlxmlTag());
+
+ var escapedClassName = (nodeElement.getWlxmlClass() || '').replace(/\./g, '-')
+ view.find('.rng-module-nodePane-classSelect').val(escapedClassName);
+
+ var widget = metaWidget.create({attrs:nodeElement.getWlxmlMetaAttrs()});
+ widget.on('valueChanged', function(key, value) {
+ sandbox.publish('nodeElementChange', key, value);
+ });
+ view.find('.metaFields').empty().append(widget.el);
+ }
+ };
+
+};
+
+});
\ No newline at end of file
--- /dev/null
+.rng-module-nodePane {
+ label {
+ width: 50px;
+ display: inline-block;
+ }
+ select {
+ width: 100px;
+ }
+
+ input {
+ width: 80px;
+ padding: 0 10px;
+ }
+}
\ No newline at end of file
--- /dev/null
+<div class="rng-module-nodePane">
+ <fieldset>
+ <legend><%= gettext('Current node') %></legend>
+ <div>
+ <label>Tag</label>
+ <select class="rng-module-nodePane-tagSelect">
+ <% var options = ['', 'section', 'header', 'div', 'span', 'aside']; %>
+ <% options.forEach(function(option) { %>
+ <option value="<%= option %>" <% if(option === '') { %>selected<% } %>><%= tagNames[option] %></option>
+ <% }); %>
+ </select>
+ </div>
+ <div>
+ <label>Klasa</label>
+ <select class="rng-module-nodePane-classSelect">
+ <% var options = ['', 'author', 'title', 'cite', 'cite.code', 'cite.code.xml', 'list.items', 'list.items.enum', 'item', 'uri', 'p', 'footnote', 'todo', 'emp'] %>
+ <% options.forEach(function(option) { %>
+ <option value="<%= option.replace(/\./g, '-') %>" <% if(option === '') { %>selected<% } %>><%= classNames[option] %></option>
+ <% }); %>
+ </select>
+ </div>
+ <div class="metaFields">
+ </div>
+ </fieldset>
+</div>
\ No newline at end of file
--- /dev/null
+<div class="rng-module-rng-diffLayout">
+ <div fnpjs-place="left"></div>
+ <div fnpjs-place="right"></div>
+</div>
\ No newline at end of file
--- /dev/null
+.rng-module-rng-diffLayout {
+ [fnpjs-place=left] {
+ width: 300px;
+ margin: 0 20px 0 0px;
+ left: 0;
+ }
+ [fnpjs-place=right] {
+ left: 320px;
+ right: 0;
+ }
+
+ [fnpjs-place=left], [fnpjs-place=right] {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<div class="fnp-module-rng-editingLayout">
+ <div fnpjs-place="toolbar"></div>
+ <div class="rng-module-rng2-left" fnpjs-place="leftColumn"></div>
+ <div class="rng-module-rng2-right" fnpjs-place="rightColumn"></div>
+ <div style="clear:both;"></div>
+ <div class="rng-module-rng2-statusBar" fnpjs-place="statusBar"></div>
+</div>
\ No newline at end of file
--- /dev/null
+.rng-module-rng2-left {
+ /*float: left;
+ width: 600px;*/
+}
+
+.rng-module-rng2-right {
+ /*float: right;
+ position: relative;
+ width: 258px;
+ margin-left: 50px;*/
+
+ border-width: 1px 1px 1px 1px;
+ border-style: solid;
+ border-color: #ddd;
+ padding: 5px 15px;
+
+ p, td, label, input, select {
+ font-size: 11px;
+ line-height:13px;
+ }
+
+ select {
+ -webkit-appearance: button;
+ -moz-appearance: button;
+ appearance: button;
+ height: auto;
+ line-height: 14px;
+ }
+
+ legend {
+ font-size:11px;
+ height:30px;
+ }
+
+
+ .rng-view-tabs-tabBar {
+ position:absolute;
+ top:-1px;
+ right:-50px;
+ border-width: 1px 1px 1px 0px;
+ border-style: solid;
+ border-color: #ddd;
+ padding: 5px;
+ background: #ededed;
+ }
+
+ label + select {
+ position:relative;
+ top: 5px;
+ }
+
+}
+
+.rng-module-rng2-statusBar {
+ margin: 10px 5px;
+ font-size:0.9em;
+}
+
+.fnp-module-rng-editingLayout {
+
+ [fnpjs-place="statusBar"] {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 30px;
+
+ }
+
+ [fnpjs-place="leftColumn"], [fnpjs-place="rightColumn"] {
+ position: absolute;
+ top: 30px; //
+ bottom: 50px; //
+ }
+
+ [fnpjs-place="leftColumn"] {
+ left:0;
+ right: 320px;
+ }
+
+ [fnpjs-place="rightColumn"] {
+ right: 0px;
+ width: 250px;
+
+ }
+
+
+}
\ No newline at end of file
--- /dev/null
+<div id="rng-module-rng-mainLayout">
+ <div fnpjs-place="messages"></div>
+ <div fnpjs-place="topPanel">
+ </div>
+ <div style="clear:both"></div>
+ <div fnpjs-place="mainView">
+ </div>
+</div>
\ No newline at end of file
--- /dev/null
+#rng-module-rng-mainLayout {
+ position: fixed;
+ top: 5px;
+ bottom: 5px;
+ left: 80px;
+ right: 80px;
+
+ [fnpjs-place="messages"] {
+ position: absolute;
+ top: 5px;
+ width:100%;
+ text-align:center;
+ }
+
+ [fnpjs-place="topPanel"] {
+ float: right;
+ position: relative;
+ z-index: 2;
+ }
+
+ [fnpjs-place="mainView"] {
+ position: absolute;
+ top: 0;
+ left:0;
+ right:0;
+ bottom:0;
+ z-index: 1;
+
+ > .rng-view-tabs {
+ position: relative;
+ height: 100%;
+
+ > .rng-view-tabs-content {
+ position: absolute;
+ top: 45px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ }
+ }
+
+ }
+}
\ No newline at end of file
--- /dev/null
+define([
+'fnpjs/layout',
+'fnpjs/vbox',
+'views/tabs/tabs',
+'libs/text!./mainLayout.html',
+'libs/text!./editingLayout.html',
+'libs/text!./diffLayout.html',
+], function(layout, vbox, tabs, mainLayoutTemplate, visualEditingLayoutTemplate, diffLayoutTemplate) {
+
+'use strict';
+
+return function(sandbox) {
+
+ function addMainTab(title, slug, view) {
+ views.mainTabs.addTab(title, slug, view);
+ }
+
+ var dirty = {
+ sourceEditor: false,
+ documentCanvas: false,
+ metadataEditor: false,
+ };
+
+ var synchronizeTab = function(slug) {
+ function tabIsDirty(slug) {
+ if(slug === 'editor' && (dirty.documentCanvas || dirty.metadataEditor))
+ return true;
+ if(slug === 'sourceEditor' && dirty.sourceEditor)
+ return true;
+ return false;
+ }
+
+ if(tabIsDirty(slug)) {
+ var reason, doc;
+ if(slug === 'sourceEditor') {
+ doc = sandbox.getModule('sourceEditor').getDocument();
+ reason = 'source_edit';
+ dirty.sourceEditor = false;
+ }
+ if(slug === 'editor') {
+ doc = dirty.documentCanvas ? sandbox.getModule('documentCanvas').getDocument() : sandbox.getModule('data').getDocument();
+ if(dirty.metadataEditor) {
+ doc = sandbox.getModule('metadataEditor').attachMetadata(doc);
+ }
+ reason = 'edit';
+ dirty.documentCanvas = dirty.metadataEditor = false;
+ }
+ sandbox.getModule('data').commitDocument(doc, reason);
+ }
+ };
+
+ var commands = {
+ highlightDocumentElement: function(element, origin) {
+ ///'nodeBreadCrumbs', 'nodeFamilyTree'
+ ['documentCanvas', ].forEach(function(moduleName) {
+ if(!origin || moduleName != origin)
+ sandbox.getModule(moduleName).highlightElement(element);
+ });
+ },
+ dimDocumentElement: function(element, origin) {
+ //'nodeBreadCrumbs', 'nodeFamilyTree'
+ ['documentCanvas'].forEach(function(moduleName) {
+ if(!origin || moduleName != origin)
+ sandbox.getModule(moduleName).dimElement(element);
+ });
+ },
+ 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);
+ },
+ updateCurrentTextElement: function(textElement) {
+ sandbox.getModule('nodeFamilyTree').setElement(textElement);
+ },
+ resetDocument: function(document, reason) {
+ var modules = [];
+ if(reason === 'source_edit')
+ modules = ['documentCanvas', 'metadataEditor'];
+ else if (reason === 'edit')
+ modules = ['sourceEditor'];
+ else if (reason === 'revert')
+ modules = ['documentCanvas', 'metadataEditor', 'sourceEditor'];
+
+ modules.forEach(function(moduleName) {
+ sandbox.getModule(moduleName).setDocument(document);
+ });
+ }
+ };
+
+
+ var views = {
+ mainLayout: new layout.Layout(mainLayoutTemplate),
+ mainTabs: (new tabs.View()).render(),
+ visualEditing: new layout.Layout(visualEditingLayoutTemplate),
+ visualEditingSidebar: (new tabs.View({stacked: true})).render(),
+ currentNodePaneLayout: new vbox.VBox(),
+ diffLayout: new layout.Layout(diffLayoutTemplate)
+ };
+
+ views.visualEditing.setView('rightColumn', views.visualEditingSidebar.getAsView());
+ addMainTab('Edytor', 'editor', views.visualEditing.getAsView());
+ addMainTab(gettext('Source'), 'sourceEditor', '');
+ addMainTab('Historia', 'history', views.diffLayout.getAsView());
+
+ sandbox.getDOM().append(views.mainLayout.getAsView());
+
+ views.visualEditingSidebar.addTab({icon: 'pencil'}, 'edit', views.currentNodePaneLayout.getAsView());
+
+ views.mainTabs.on('tabSelected', function(event) {
+ if(event.prevSlug) {
+ synchronizeTab(event.prevSlug);
+ }
+ });
+
+ /* Events handling */
+
+ var eventHandlers = {};
+
+ eventHandlers.sourceEditor = {
+ ready: function() {
+ addMainTab(gettext('Source'), 'sourceEditor', sandbox.getModule('sourceEditor').getView());
+ sandbox.getModule('sourceEditor').setDocument(sandbox.getModule('data').getDocument());
+ },
+ xmlChanged: function() {
+ dirty.sourceEditor = true;
+ },
+ documentSet: function() {
+ dirty.sourceEditor = false;
+ }
+ };
+
+ eventHandlers.data = {
+ ready: function() {
+ views.mainLayout.setView('mainView', views.mainTabs.getAsView());
+
+ _.each(['sourceEditor', 'documentCanvas', 'documentToolbar', 'nodePane', 'metadataEditor', 'nodeFamilyTree', 'nodeBreadCrumbs', 'mainBar', 'indicator', 'documentHistory', 'diffViewer'], function(moduleName) {
+ sandbox.getModule(moduleName).start();
+ });
+ },
+ documentChanged: function(document, reason) {
+ commands.resetDocument(document, reason);
+ },
+ savingStarted: function() {
+ sandbox.getModule('mainBar').setCommandEnabled('save', false);
+ sandbox.getModule('indicator').showMessage(gettext('Saving...'));
+ },
+ savingEnded: function(status) {
+ sandbox.getModule('mainBar').setCommandEnabled('save', true);
+ sandbox.getModule('indicator').clearMessage({message:'Dokument zapisany'});
+ },
+ restoringStarted: function(event) {
+ sandbox.getModule('mainBar').setCommandEnabled('save', false);
+ sandbox.getModule('indicator').showMessage(gettext('Restoring version ') + event.version + '...');
+ },
+ historyItemAdded: function(item) {
+ sandbox.getModule('documentHistory').addHistory([item], {animate: true});
+ },
+ diffFetched: function(diff) {
+ sandbox.getModule('diffViewer').setDiff(diff);
+ },
+ documentReverted: function(event) {
+ commands.resetDocument(event.document, 'revert');
+ sandbox.getModule('mainBar').setCommandEnabled('save', true);
+ sandbox.getModule('indicator').clearMessage({message:'Wersja ' + event.reverted_version + ' przywrócona'});
+ sandbox.getModule('mainBar').setVersion(event.current_version);
+ }
+ };
+
+ eventHandlers.mainBar = {
+ ready: function() {
+ sandbox.getModule('mainBar').setVersion(sandbox.getModule('data').getDocumentVersion());
+ views.mainLayout.setView('topPanel', sandbox.getModule('mainBar').getView());
+ },
+ 'cmd.save': function() {
+ synchronizeTab(views.mainTabs.getCurrentSlug());
+ sandbox.getModule('data').saveDocument();
+ }
+ };
+
+ eventHandlers.indicator = {
+ ready: function() {
+ views.mainLayout.setView('messages', sandbox.getModule('indicator').getView());
+ }
+ };
+
+
+
+ eventHandlers.documentCanvas = {
+ ready: function() {
+ sandbox.getModule('documentCanvas').setDocument(sandbox.getModule('data').getDocument());
+ views.visualEditing.setView('leftColumn', sandbox.getModule('documentCanvas').getView());
+ },
+ documentSet: function() {
+ dirty.documentCanvas = false;
+ },
+
+ currentTextElementSet: function(textElement) {
+ commands.updateCurrentTextElement(textElement);
+ },
+
+ currentNodeElementSet: function(nodeElement) {
+ commands.updateCurrentNodeElement(nodeElement);
+ },
+
+ currentNodeElementChanged: function(nodeElement) {
+ commands.updateCurrentNodeElement(nodeElement);
+ dirty.documentCanvas = true;
+ },
+
+ contentChanged: function() {
+ dirty.documentCanvas = true;
+ },
+
+ nodeHovered: function(canvasNode) {
+ commands.highlightDocumentNode(canvasNode);
+ },
+
+ nodeBlured: function(canvasNode) {
+ commands.dimDocumentNode(canvasNode);
+ }
+ };
+
+ eventHandlers.nodePane = {
+ ready: function() {
+ views.currentNodePaneLayout.appendView(sandbox.getModule('nodePane').getView());
+ },
+
+ nodeElementChange: function(attr, value) {
+ sandbox.getModule('documentCanvas').modifyCurrentNodeElement(attr, value);
+ }
+ };
+
+ eventHandlers.metadataEditor = {
+ ready: function() {
+ sandbox.getModule('metadataEditor').setDocument(sandbox.getModule('data').getDocument());
+ views.visualEditingSidebar.addTab({icon: 'info-sign'}, 'metadataEditor', sandbox.getModule('metadataEditor').getView());
+ },
+ metadataChanged: function(metadata) {
+ dirty.metadataEditor = true;
+ },
+ metadataSet: function() {
+ dirty.metadataEditor = false;
+ },
+ };
+
+ eventHandlers.nodeFamilyTree = {
+ ready: function() {
+ views.currentNodePaneLayout.appendView(sandbox.getModule('nodeFamilyTree').getView());
+ },
+ elementEntered: function(element) {
+ commands.highlightDocumentElement(element, 'nodeFamilyTree');
+ },
+ elementLeft: function(element) {
+ commands.dimDocumentElement(element, 'nodeFamilyTree');
+ },
+ elementClicked: function(element) {
+ commands.jumpToDocumentElement(element);
+ }
+ };
+
+ eventHandlers.documentToolbar = {
+ ready: function() {
+ views.visualEditing.setView('toolbar', sandbox.getModule('documentToolbar').getView());
+ },
+ command: function(cmd, params) {
+ sandbox.getModule('documentCanvas').command(cmd, params);
+ }
+ };
+
+ eventHandlers.nodeBreadCrumbs = {
+ ready: function() {
+ views.visualEditing.setView('statusBar', sandbox.getModule('nodeBreadCrumbs').getView());
+ },
+ elementEntered: function(element) {
+ commands.highlightDocumentElement(element, 'nodeBreadCrumbs');
+ },
+ elementLeft: function(element) {
+ commands.dimDocumentElement(element, 'nodeBreadCrumbs');
+ },
+ elementClicked: function(element) {
+ commands.jumpToDocumentElement(element);
+ }
+ };
+
+ eventHandlers.documentHistory = {
+ ready: function() {
+ sandbox.getModule('documentHistory').addHistory(sandbox.getModule('data').getHistory());
+ views.diffLayout.setView('left', sandbox.getModule('documentHistory').getView());
+ },
+ compare: function(ver1, ver2) {
+ sandbox.getModule('data').fetchDiff(ver1, ver2);
+ },
+ restoreVersion: function(event) {
+ sandbox.getModule('data').restoreVersion(event);
+ },
+ displayVersion: function(event) {
+ window.open('/' + gettext('editor') + '/' + sandbox.getModule('data').getDocumentId() + '?version=' + event.version, _.uniqueId());
+ }
+ };
+
+ eventHandlers.diffViewer = {
+ ready: function() {
+ views.diffLayout.setView('right', sandbox.getModule('diffViewer').getView());
+ }
+ };
+
+ /* api */
+
+ return {
+ start: function() {
+ sandbox.getModule('data').start();
+ },
+ handleEvent: function(moduleName, eventName, args) {
+ if('')
+ wysiwigHandler.handleEvent(moduleName, eventName, args);
+ else if(eventHandlers[moduleName] && eventHandlers[moduleName][eventName]) {
+ eventHandlers[moduleName][eventName].apply(eventHandlers, args);
+ }
+ }
+ };
+};
+
+});
\ No newline at end of file
--- /dev/null
+@import 'mainLayout.less';
+@import 'editingLayout.less';
+@import 'diffLayout.less';
\ No newline at end of file
--- /dev/null
+define(function() {
+
+'use strict';
+
+return function(sandbox) {
+
+ var view = $(sandbox.getTemplate('main')());
+
+ var editor = ace.edit(view.find('#rng-sourceEditor-editor')[0]),
+ session = editor.getSession();
+ editor.setTheme("ace/theme/chrome");
+ session.setMode("ace/mode/xml")
+ session.setUseWrapMode(true);
+
+ $('textarea', view).on('keyup', function() {
+ sandbox.publish('xmlChanged');
+ });
+
+ editor.getSession().on('change', function() {
+ sandbox.publish('xmlChanged');
+ });
+ return {
+ start: function() {
+ sandbox.publish('ready');
+ },
+ getView: function() {
+ return view;
+ },
+ setDocument: function(document) {
+ editor.setValue(document);
+ editor.gotoLine(0);
+ sandbox.publish('documentSet');
+ },
+ getDocument: function() {
+ return editor.getValue();
+ }
+ };
+};
+
+});
\ No newline at end of file
--- /dev/null
+#rng-sourceEditor-editor {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
\ No newline at end of file
--- /dev/null
+define(function() {
+
+'use strict';
+
+return {
+ modules: {},
+ initModules: ['rng'],
+ permissions: {
+ 'skelton': ['getDOM'],
+ 'rng': ['getModule', 'handleEvents', 'getDOM']
+ }
+}
+
+});
\ No newline at end of file
--- /dev/null
+body {
+ padding-top: 5px;
+}
+
+.rng-common-hoveredNode {
+ border-color: red !important;
+ border-style:solid;
+ border-width:1px;
+}
+
--- /dev/null
+@import 'mixins.less';
+@import 'common.less';
+
+@import '../modules/data/data.less';
+@import '../modules/rng/rng.less';
+@import '../modules/documentCanvas/documentCanvas.less';
+@import '../modules/sourceEditor/sourceEditor.less';
+@import '../modules/mainBar/mainBar.less';
+@import '../modules/documentToolbar/documentToolbar.less';
+@import '../modules/documentHistory/documentHistory.less';
+@import '../modules/indicator/indicator.less';
+@import '../modules/nodePane/nodePane.less';
+@import '../modules/nodeFamilyTree/nodeFamilyTree.less';
+@import '../modules/metadataEditor/metadataEditor.less';
+@import '../modules/diffViewer/diffViewer.less';
--- /dev/null
+.rng-mixin-scrollbar {
+ width: 9px;
+}
+
+.rng-mixin-scrollbar-track {
+ -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+}
+
+.rng-mixin-scrollbar-thumb {
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+ background: rgba(73,175,205,0.8);
+ -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5);
+}
+
+.rng-mixin-scrollbar-thumb-window-inactive {
+ background: rgba(73,175,205,0.4);
+}
\ No newline at end of file
--- /dev/null
+define([
+
+], function() {
+
+'use strict';
+
+
+
+return {
+ wlxmlTagNames: {
+ '': '',
+ section: 'sekcja',
+ header: 'nagłówek',
+ div: 'blok',
+ span: 'tekst',
+ aside: 'poboczny'
+ },
+ wlxmlClassNames: {
+ '': '',
+ author: 'autor',
+ title: 'tytuł',
+ cite: 'cytat',
+ 'cite.code': 'cytat.kod',
+ 'cite.code.xml': 'cytat.kod.xml',
+ 'list.items': 'lista',
+ 'list.items.enum': 'lista.numerowana',
+ item: 'element',
+ uri: 'uri',
+ p: 'paragraf',
+ footnote: 'przypis',
+ todo: 'todo',
+ emp: 'wyróżnienie'
+ }
+};
+
+});
\ No newline at end of file
--- /dev/null
+define([
+'libs/text!./templates/main.html',
+'libs/text!./templates/handle.html',
+'libs/underscore',
+'libs/backbone'
+], function(mainTemplate, handleTemplate, _, Backbone) {
+ 'use strict';
+
+ var View = Backbone.View.extend({
+ className: 'rng-view-tabs',
+
+ events: {
+ 'click ul a, i': '_onTabTitleClicked'
+ },
+
+ initialize: function(options) {
+ this.options = options || {};
+ this.template = _.template(mainTemplate);
+ this.handleTemplate = _.template(handleTemplate);
+ this.contents = {};
+ this.selectedTab = null;
+ },
+
+ render: function() {
+ this.$el.html(this.template());
+ this.nodes = {
+ tabBar: this.$('.rng-view-tabs-tabBar'),
+ content: this.$('.rng-view-tabs-content')
+ };
+
+ if(this.options.stacked) {
+ this.nodes.tabBar.addClass('nav-stacked nav-pills').removeClass('nav-tabs');
+ }
+ if(this.options.position === 'right') {
+ this.$el.addClass('tabs-right');
+ this.nodes.content.addClass('tab-content');
+ }
+ return this;
+ },
+
+ addTab: function(title, slug, content) {
+ if(this.contents[slug]) {
+ this.contents[slug].detach();
+ }
+ this.contents[slug] = content;
+
+ var text = (typeof title === 'string') ? title : (title.text || '');
+ var icon = title.icon || null;
+
+ if(!this.tabExists(slug))
+ this.nodes.tabBar.append(this.handleTemplate({text: text, icon: icon, slug: slug}));
+ if(!this.selectedTab)
+ this.selectTab(slug);
+ },
+
+ selectTab: function(slug) {
+ if(slug !== this.selectedTab && this.contents[slug]) {
+ this.trigger('leaving', this.selectedTab);
+
+ if(this.selectedTab) {
+ var toDetach = this.contents[this.selectedTab];
+ if(toDetach.onHide)
+ toDetach.onHide();
+ toDetach.detach();
+ }
+ this.nodes.content.append(this.contents[slug]);
+ if(this.contents[slug].onShow) {
+ this.contents[slug].onShow();
+ }
+ this.nodes.tabBar.find('.active').removeClass('active');
+ this.nodes.tabBar.find('a[href="#'+slug+'"]').parent().addClass('active');
+
+ var prevSlug = this.selectedTab;
+ this.selectedTab = slug;
+ this.trigger('tabSelected', {slug: slug, prevSlug: prevSlug});
+ }
+ },
+
+ getAsView: function() {
+ return this.$el;
+ },
+
+ getCurrentSlug: function() {
+ return this.selectedTab;
+ },
+
+ tabExists: function(slug) {
+ return this.nodes.tabBar.find('a[href="#'+ slug + '"]').length > 0;
+ },
+
+ /* Events */
+
+ _onTabTitleClicked: function(e) {
+ e.preventDefault();
+ var target = $(e.target);
+ if(target.is('i'))
+ target = target.parent();
+ var slug = target.attr('href').substr(1);
+ this.selectTab(slug);
+ }
+ });
+
+
+ return {
+ View: View
+ };
+
+
+});
\ No newline at end of file
--- /dev/null
+<li><a href="#<%= slug %>"><% if(icon) { %><i class="icon-<%= icon %>"></i><% } %><%= text %></a></li>
\ No newline at end of file
--- /dev/null
+<ul class="rng-view-tabs-tabBar nav nav-tabs">
+</ul>
+<div class="rng-view-tabs-content">
+</div>
--- /dev/null
+define(['libs/jquery', 'libs/underscore'], function($ ,_) {
+ 'use strict';
+
+ var Layout = function(template) {
+ var layout = this;
+ this.dom = $(_.template(template)());
+ this.views = {};
+
+ this.dom.onShow = function() {
+ _.values(layout.views).forEach(function(view) {
+ if(view.onShow)
+ view.onShow();
+ });
+ };
+ this.dom.onHide = function() {
+ _.values(layout.views).forEach(function(view) {
+ if(view.onHide)
+ view.onHide();
+ });
+ };
+
+ };
+
+ Layout.prototype.setView = function(place, view) {
+ this.dom.find('[fnpjs-place=' + place + ']').append(view);
+ this.views[place] = view;
+ if(this.dom.is(':visible') && view.onShow) {
+ view.onShow();
+ }
+ };
+
+ Layout.prototype.getAsView = function() {
+ return this.dom;
+ };
+
+ return {Layout: Layout};
+});
\ No newline at end of file
--- /dev/null
+define(['libs/jquery', 'libs/underscore'], function($, _) {
+
+var Runner = function(app, modules) {
+
+ function getModuleInstance(moduleName) {
+ var module = moduleInstances[moduleName] = (moduleInstances[moduleName] || modules[moduleName](new Sandbox(moduleName)));
+ return module;
+ }
+
+ var bootstrappedData = {},
+ options = {},
+ moduleInstances = {},
+ eventListeners = [];
+
+ _.each(_.keys(modules || {}), function(moduleName) {
+ if(_.contains(app.permissions[moduleName] || [], 'handleEvents')) {
+ eventListeners.push(moduleName);
+ }
+ });
+
+
+
+ var Sandbox = function(moduleName) {
+ this.$ = $;
+ this._ = _;
+
+ this.getBootstrappedData = function() {
+ return bootstrappedData[moduleName];
+ };
+
+ this.getTemplate = function(templateName) {
+ return _.template($('[data-template-name="' + moduleName + '.' + templateName + '"]').html().trim());
+ };
+
+ this.publish = function(eventName) {
+ console.log(moduleName + ': ' + eventName);
+ var eventArgs = Array.prototype.slice.call(arguments, 1);
+ _.each(eventListeners, function(listenerModuleName) {
+ var listener = moduleInstances[listenerModuleName];
+ if(listener) {
+ listener.handleEvent(moduleName, eventName, eventArgs);
+ }
+ });
+ };
+
+ var permissions = app.permissions[moduleName];
+
+ this.getModule = _.contains(permissions, 'getModule') ? function(requestedModuleName) {
+ return getModuleInstance(requestedModuleName);
+ } : undefined;
+
+ this.getDOM = _.contains(permissions, 'getDOM') ? function() {
+ return $(options.rootSelector);
+ } : undefined;
+
+ };
+
+
+ this.setBootstrappedData = function(moduleName, data) {
+ bootstrappedData[moduleName] = data;
+ };
+
+ this.start = function(_options) {
+ options = _.extend({
+ rootSelector: 'body'
+ }, _options);
+ app.initModules.forEach(function(moduleName) {
+ getModuleInstance(moduleName).start();
+ });
+ };
+};
+
+return {
+ Runner: Runner
+};
+
+});
\ No newline at end of file
--- /dev/null
+define(['libs/jquery', './layout'], function($, layout) {
+
+ var VBox = function() {};
+
+ VBox.prototype = new layout.Layout('<div class="fnpjs-vbox"></div>');
+ VBox.prototype.appendView = function(view) {
+ var item = $('<div>').addClass('fnpjs-vbox-item').append(view);
+ this.dom.append(item);
+ };
+
+ return {VBox: VBox};
+
+});
\ No newline at end of file
+++ /dev/null
-body {
- padding-top: 5px;
-}
-
-.rng-common-hoveredNode {
- border-color: red !important;
- border-style:solid;
- border-width:1px;
-}
-
+++ /dev/null
-@import 'mixins.less';
-@import 'common.less';
-
-@import '../modules/data/data.less';
-@import '../modules/rng/rng.less';
-@import '../modules/documentCanvas/documentCanvas.less';
-@import '../modules/sourceEditor/sourceEditor.less';
-@import '../modules/mainBar/mainBar.less';
-@import '../modules/documentToolbar/documentToolbar.less';
-@import '../modules/documentHistory/documentHistory.less';
-@import '../modules/indicator/indicator.less';
-@import '../modules/nodePane/nodePane.less';
-@import '../modules/nodeFamilyTree/nodeFamilyTree.less';
-@import '../modules/metadataEditor/metadataEditor.less';
-@import '../modules/diffViewer/diffViewer.less';
+++ /dev/null
-.rng-mixin-scrollbar {
- width: 9px;
-}
-
-.rng-mixin-scrollbar-track {
- -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
- -webkit-border-radius: 10px;
- border-radius: 10px;
-}
-
-.rng-mixin-scrollbar-thumb {
- -webkit-border-radius: 10px;
- border-radius: 10px;
- background: rgba(73,175,205,0.8);
- -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5);
-}
-
-.rng-mixin-scrollbar-thumb-window-inactive {
- background: rgba(73,175,205,0.4);
-}
\ No newline at end of file
});
require({
- baseUrl: '/base/',
+ baseUrl: '/base/src/editor',
deps: tests,
callback: window.__karma__.start,
+
+ paths: {
+ 'fnpjs': '../fnpjs',
+ 'libs': '../../libs'
+ },
+
+ map: {
+ '*':
+ {
+ 'libs/jquery': '../../libs/jquery-1.9.1.min',
+ 'libs/underscore': '../../libs/underscore-min',
+ 'libs/bootstrap': '../../libs/bootstrap/js/bootstrap.min',
+ 'libs/backbone': '../../libs/backbone-min',
+ 'libs/sinon': '../../libs/sinon-1.7.3'
+ }
+ },
+
shim: {
- 'libs/jquery-1.9.1.min': {
+ '../../libs/jquery-1.9.1.min': {
exports: '$',
},
- 'libs/underscore': {
+ '../../libs/underscore-min': {
exports: '_'
},
- 'libs/bootstrap/js/bootstrap.min': {
- deps: ['libs/jquery-1.9.1.min']
+ '../../libs/bootstrap/js/bootstrap.min': {
+ deps: ['libs/jquery']
},
- 'libs/backbone': {
+ '../../libs/backbone-min': {
exports: 'Backbone',
- deps: ['libs/jquery-1.9.1.min', 'libs/underscore']
+ deps: ['libs/jquery', 'libs/underscore']
},
- 'libs/sinon-1.7.3': {
+ '../../libs/sinon-1.7.3': {
exports: 'sinon'
}
- },
- map: {
- '*': {
- 'libs/sinon': 'libs/sinon-1.7.3'
- }
}
});
+++ /dev/null
-define([
-
-], function() {
-
-'use strict';
-
-
-
-return {
- wlxmlTagNames: {
- '': '',
- section: 'sekcja',
- header: 'nagłówek',
- div: 'blok',
- span: 'tekst',
- aside: 'poboczny'
- },
- wlxmlClassNames: {
- '': '',
- author: 'autor',
- title: 'tytuł',
- cite: 'cytat',
- 'cite.code': 'cytat.kod',
- 'cite.code.xml': 'cytat.kod.xml',
- 'list.items': 'lista',
- 'list.items.enum': 'lista.numerowana',
- item: 'element',
- uri: 'uri',
- p: 'paragraf',
- footnote: 'przypis',
- todo: 'todo',
- emp: 'wyróżnienie'
- }
-};
-
-});
\ No newline at end of file
+++ /dev/null
-define([
-'libs/text!./templates/main.html',
-'libs/text!./templates/handle.html',
-'libs/underscore',
-'libs/backbone'
-], function(mainTemplate, handleTemplate, _, Backbone) {
- 'use strict';
-
- var View = Backbone.View.extend({
- className: 'rng-view-tabs',
-
- events: {
- 'click ul a, i': '_onTabTitleClicked'
- },
-
- initialize: function(options) {
- this.options = options || {};
- this.template = _.template(mainTemplate);
- this.handleTemplate = _.template(handleTemplate);
- this.contents = {};
- this.selectedTab = null;
- },
-
- render: function() {
- this.$el.html(this.template());
- this.nodes = {
- tabBar: this.$('.rng-view-tabs-tabBar'),
- content: this.$('.rng-view-tabs-content')
- };
-
- if(this.options.stacked) {
- this.nodes.tabBar.addClass('nav-stacked nav-pills').removeClass('nav-tabs');
- }
- if(this.options.position === 'right') {
- this.$el.addClass('tabs-right');
- this.nodes.content.addClass('tab-content');
- }
- return this;
- },
-
- addTab: function(title, slug, content) {
- if(this.contents[slug]) {
- this.contents[slug].detach();
- }
- this.contents[slug] = content;
-
- var text = (typeof title === 'string') ? title : (title.text || '');
- var icon = title.icon || null;
-
- if(!this.tabExists(slug))
- this.nodes.tabBar.append(this.handleTemplate({text: text, icon: icon, slug: slug}));
- if(!this.selectedTab)
- this.selectTab(slug);
- },
-
- selectTab: function(slug) {
- if(slug !== this.selectedTab && this.contents[slug]) {
- this.trigger('leaving', this.selectedTab);
-
- if(this.selectedTab) {
- var toDetach = this.contents[this.selectedTab];
- if(toDetach.onHide)
- toDetach.onHide();
- toDetach.detach();
- }
- this.nodes.content.append(this.contents[slug]);
- if(this.contents[slug].onShow) {
- this.contents[slug].onShow();
- }
- this.nodes.tabBar.find('.active').removeClass('active');
- this.nodes.tabBar.find('a[href="#'+slug+'"]').parent().addClass('active');
-
- var prevSlug = this.selectedTab;
- this.selectedTab = slug;
- this.trigger('tabSelected', {slug: slug, prevSlug: prevSlug});
- }
- },
-
- getAsView: function() {
- return this.$el;
- },
-
- getCurrentSlug: function() {
- return this.selectedTab;
- },
-
- tabExists: function(slug) {
- return this.nodes.tabBar.find('a[href="#'+ slug + '"]').length > 0;
- },
-
- /* Events */
-
- _onTabTitleClicked: function(e) {
- e.preventDefault();
- var target = $(e.target);
- if(target.is('i'))
- target = target.parent();
- var slug = target.attr('href').substr(1);
- this.selectTab(slug);
- }
- });
-
-
- return {
- View: View
- };
-
-
-});
\ No newline at end of file
+++ /dev/null
-<li><a href="#<%= slug %>"><% if(icon) { %><i class="icon-<%= icon %>"></i><% } %><%= text %></a></li>
\ No newline at end of file
+++ /dev/null
-<ul class="rng-view-tabs-tabBar nav nav-tabs">
-</ul>
-<div class="rng-view-tabs-content">
-</div>