From: Aleksander Łukasz Date: Fri, 13 Sep 2013 12:37:32 +0000 (+0200) Subject: Refactoring: cleaning directories structure X-Git-Url: https://git.mdrn.pl/fnpeditor.git/commitdiff_plain/efe36f4f1b5df351eeb4d40a54c3900cf9a7079b?ds=sidebyside;hp=412e60ded1457ec0f408e2234c9dd60122929bac Refactoring: cleaning directories structure --- diff --git a/Gruntfile.js b/Gruntfile.js index 8b7469e..55ee1c8 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -4,8 +4,8 @@ module.exports = function(grunt) { requirejs: { compile: { options: { - baseUrl: '', - mainConfigFile: 'entrypoint.js', + baseUrl: 'src/editor', + mainConfigFile: 'src/editor/entrypoint.js', out: 'build/rng.js', name: 'entrypoint', include: ['libs/require'] @@ -19,12 +19,12 @@ module.exports = function(grunt) { 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' } diff --git a/entrypoint.js b/entrypoint.js deleted file mode 100644 index 251151e..0000000 --- a/entrypoint.js +++ /dev/null @@ -1,50 +0,0 @@ -(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 diff --git a/fnpjs/layout.js b/fnpjs/layout.js deleted file mode 100644 index c1d110e..0000000 --- a/fnpjs/layout.js +++ /dev/null @@ -1,37 +0,0 @@ -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 diff --git a/fnpjs/runner.js b/fnpjs/runner.js deleted file mode 100644 index 66e0b68..0000000 --- a/fnpjs/runner.js +++ /dev/null @@ -1,77 +0,0 @@ -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 diff --git a/fnpjs/vbox.js b/fnpjs/vbox.js deleted file mode 100644 index dfb5a9f..0000000 --- a/fnpjs/vbox.js +++ /dev/null @@ -1,13 +0,0 @@ -define(['libs/jquery', './layout'], function($, layout) { - - var VBox = function() {}; - - VBox.prototype = new layout.Layout('
'); - VBox.prototype.appendView = function(view) { - var item = $('
').addClass('fnpjs-vbox-item').append(view); - this.dom.append(item); - }; - - return {VBox: VBox}; - -}); \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js index ea3a375..e498786 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -8,14 +8,8 @@ files = [ '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', ]; diff --git a/modules.js b/modules.js deleted file mode 100644 index bb4d439..0000000 --- a/modules.js +++ /dev/null @@ -1,28 +0,0 @@ -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 diff --git a/modules/data/data.js b/modules/data/data.js deleted file mode 100644 index 9b2f163..0000000 --- a/modules/data/data.js +++ /dev/null @@ -1,133 +0,0 @@ -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 = '\n\ - \n\ - \n\ -
\n\ - '; - } - - - 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 diff --git a/modules/data/data.less b/modules/data/data.less deleted file mode 100644 index 1beb090..0000000 --- a/modules/data/data.less +++ /dev/null @@ -1 +0,0 @@ -@import 'saveDialog.less'; \ No newline at end of file diff --git a/modules/data/saveDialog.html b/modules/data/saveDialog.html deleted file mode 100644 index 0846910..0000000 --- a/modules/data/saveDialog.html +++ /dev/null @@ -1,14 +0,0 @@ - \ No newline at end of file diff --git a/modules/data/saveDialog.js b/modules/data/saveDialog.js deleted file mode 100644 index 90832e6..0000000 --- a/modules/data/saveDialog.js +++ /dev/null @@ -1,56 +0,0 @@ -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 diff --git a/modules/data/saveDialog.less b/modules/data/saveDialog.less deleted file mode 100644 index e863e93..0000000 --- a/modules/data/saveDialog.less +++ /dev/null @@ -1,19 +0,0 @@ -.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 diff --git a/modules/diffViewer/diff.html b/modules/diffViewer/diff.html deleted file mode 100644 index cbf9b54..0000000 --- a/modules/diffViewer/diff.html +++ /dev/null @@ -1 +0,0 @@ -
\ No newline at end of file diff --git a/modules/diffViewer/diffViewer.js b/modules/diffViewer/diffViewer.js deleted file mode 100644 index e792545..0000000 --- a/modules/diffViewer/diffViewer.js +++ /dev/null @@ -1,38 +0,0 @@ -define([ -'libs/jquery', -'libs/underscore', -'views/tabs/tabs', -'libs/text!./diff.html' -], function($, _, tabs, diffTemplateSrc) { - -'use strict'; - -return function(sandbox) { - - var dom = $('
').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 diff --git a/modules/diffViewer/diffViewer.less b/modules/diffViewer/diffViewer.less deleted file mode 100644 index 34ab240..0000000 --- a/modules/diffViewer/diffViewer.less +++ /dev/null @@ -1,77 +0,0 @@ -.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 diff --git a/modules/documentCanvas/canvas/canvas.js b/modules/documentCanvas/canvas/canvas.js deleted file mode 100644 index d8db2bc..0000000 --- a/modules/documentCanvas/canvas/canvas.js +++ /dev/null @@ -1,657 +0,0 @@ -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 = $('
').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 = $('
'), - 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 diff --git a/modules/documentCanvas/canvas/canvas.test.js b/modules/documentCanvas/canvas/canvas.test.js deleted file mode 100644 index 2dc24eb..0000000 --- a/modules/documentCanvas/canvas/canvas.test.js +++ /dev/null @@ -1,1553 +0,0 @@ -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 = 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 + '>').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('
').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('
').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('
Alice
').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('
'); - expect(c.doc().getWlxmlTag()).to.equal('section'); - }); - - describe('root element', function() { - it('has no parent', function() { - var c = canvas.fromXML('
'); - expect(c.doc().parent()).to.be.null; - }); - }); - - describe('DocumentTextElement', function() { - it('can have its content set', function() { - var c = canvas.fromXML('
Alice
'), - 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('
'), - 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 = 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 = 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('
'), - 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('
'), - 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('
'), - 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('
'), - 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('
Alice
'), - 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('
'); - 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('
'), - 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('
'), - children = c.doc().children(); - expect(children.length).to.equal(1); - expect(children[0]).to.be.instanceOf(documentElement.DocumentNodeElement); - - c = canvas.fromXML('
'), - 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('
Alice
'), - 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('
'), - 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('
Alice
'), - 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('
\ -
\ -
A
\ -
B
\ -
\ -
'), - 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('
Alice has a cat
'), - 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('
Alice has a cat
'), - 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('
'); - 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 = 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('
'); - 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('
'); - 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('
'); - 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('
Alice has a cat
'); - 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(' Alice has a cat '); - 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('
Alice has a cat
'); - 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('
Alice has a cat
'); - 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('
authorAlice
has
a cat
'), - 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('\ -
\ -
\ -
\ - Alice\ -
\ -
\ - Some text\ -
'), - 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('
'), - 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('
'), - 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('
'), - 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('
'), - 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('
'), - 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('
'), - 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('
Alice
'), - 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('
Alice
'), - 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('
Alice has a cat
'), - 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('
Alice has a cat
'), - 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('
Alice has a cat
'), - 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('
Alice
has
a cat
'), - 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('
Some header
'), - 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('
Some header
'), - 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('
Some header
'), - 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('\ -
\ -
\ - A fancy and nice header\ -
\ -
'), - 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('
'), - 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('
Alice
'), - 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('
Alice has a cat
'), - 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('
Alice has a cat
'), - 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('
Alice has a small cat
'), - 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('
Alice
has
a cat
'), - 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('
div>
'), - 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('
Alice
has
a cat
'), - 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('
Alice has a cat
'), - 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('
Sometext
'), - 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('
Alice hasa cat
'), - 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('
Alice
has a cat!
!!
'), - 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('
Alice has a cat
'), - 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('\ -
\ - Alice\ -
has
\ - a\ -
cat
\ -
'), - 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('\ -
\ -
\ -
A
\ -
B
\ -
C
\ -
D
\ -
\ -
'), - 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('\ -
\ -
\ -
0
\ -
1
\ -
2
\ -
3
\ -
\ -
'), - 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('\ -
\ -
\ -
0
\ -
1
\ -
2
\ -
\ -
'), - 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('\ -
\ -
\ -
0
\ -
1
\ -
2
\ -
\ -
'), - 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('\ -
\ -
\ -
some item
\ -
some item 2
\ -
\ -
'), - 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('\ -
\ -
\ -
0
\ -
\ -
\ -
1.1
\ -
1.2
\ -
1.3
\ -
\ -
\ -
2
\ -
\ -
'), - 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('\ -
\ -
\ -
0
\ -
\ -
\ -
1.1
\ -
1.2
\ -
1.3
\ -
\ -
\ -
2
\ -
\ -
'), - 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('\ -
\ -
\ -
0
\ -
\ -
\ -
1.1
\ -
1.2
\ -
1.3
\ -
\ -
\ -
2
\ -
\ -
'), - 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('\ -
\ -
\ -
0
\ -
\ -
\ -
1.1
\ -
1.2
\ -
\ -
\ -
2
\ -
\ -
'), - 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('\ -
\ -
\ -
0
\ -
\ -
\ -
1.1
\ -
1.2
\ -
\ -
\ -
2
\ -
\ -
'), - 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('
Alice has a cat
'), - 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('
Alice has a big cat
'), - 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('
Alice has a big cat
'), - 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 = '
Alice
has
a cat!
', - 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 = '
', - $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('
'); - c.doc().setWlxmlMetaAttr('uri', ''); - expect($(c.toXML()).attr('meta-uri')).to.equal(undefined, 'overriding attribute with zero length string'); - - c = canvas.fromXML('
'); - 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 = '
< >
', - c = canvas.fromXML(xmlIn), - xmlOut = c.toXML(); - expect(xmlOut).to.equal(xmlIn); - }); - it('keeps entities intact when they form html/xml', function() { - var xmlIn = '
<abc>
', - 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 = '
', - 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 = '
\n\n\n
\n\n\n
\n\n\n
', - 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 = '
\n\n\n\n\n\n\n\n\n
', - 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 = '
abc
some div
abc
', - c = canvas.fromXML(xmlIn), - xmlOut = c.toXML(); - - expect(xmlOut).to.equal(xmlIn); - }); - - it('nests new children block elements', function() { - var c = canvas.fromXML('
'); - - c.doc().append({tag: 'header'}); - - var xmlOut = c.toXML(); - expect(xmlOut.split('\n ')[0]).to.equal('
', 'nesting start ok'); - expect(xmlOut.split('\n').slice(-1)[0]).to.equal('
', 'nesting end ok'); - - }); - - it('doesn\'t nest new children inline elements', function() { - var c = canvas.fromXML('
'); - - c.doc().append({tag: 'span'}); - - var xmlOut = c.toXML(); - expect(xmlOut).to.equal('
'); - }); - - it('keeps original white space at the end of text', function() { - - var xmlIn = '
Some text ended with white space \ - \ - Some text some text\ - \ -
', - 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 = '
\ -
header1
\ - Some text surrounded by white space\ -
header2
\ -
', - 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 = '
\ -
header
\ - \ - Some text surrounded by white space\ - \ -
', - c = canvas.fromXML(xmlIn); - - var xmlOut = c.toXML(); - expect(xmlOut).to.equal(xmlIn); - }); - - it('keeps white space after detaching text element', function() { - var xmlIn = '
header
\n\ - \n\ - text1\n\ - \n\ -
', - expectedXmlOut = '
header
\n\ - \n\ - \n\ - \n\ -
', - 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 diff --git a/modules/documentCanvas/canvas/documentElement.js b/modules/documentCanvas/canvas/documentElement.js deleted file mode 100644 index 0627bc1..0000000 --- a/modules/documentCanvas/canvas/documentElement.js +++ /dev/null @@ -1,669 +0,0 @@ -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 = $('
') - .attr('document-node-element', ''), - widgetsContainer = $('
') - .addClass('canvas-widgets') - .attr('contenteditable', false), - container = $('
') - .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 = $('
'); - 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 $('
') - .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('
'); - 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('
'); - 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 diff --git a/modules/documentCanvas/canvas/keyboard.js b/modules/documentCanvas/canvas/keyboard.js deleted file mode 100644 index 1830017..0000000 --- a/modules/documentCanvas/canvas/keyboard.js +++ /dev/null @@ -1,197 +0,0 @@ -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 diff --git a/modules/documentCanvas/canvas/utils.js b/modules/documentCanvas/canvas/utils.js deleted file mode 100644 index 0eb19d0..0000000 --- a/modules/documentCanvas/canvas/utils.js +++ /dev/null @@ -1,29 +0,0 @@ -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' - } -}; - -}); diff --git a/modules/documentCanvas/canvas/utils.test.js b/modules/documentCanvas/canvas/utils.test.js deleted file mode 100644 index c93e74b..0000000 --- a/modules/documentCanvas/canvas/utils.test.js +++ /dev/null @@ -1,70 +0,0 @@ -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', - '\ - \ -
\ - \ - ' - ], - ['returns nearest sibling if applicable', - '
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
' - ], - ['looks inside siblings children', - '
\ -
\ -
\ -
\ - \ -
\ -
\ -
\ - \ -
\ -
\ -
\ -
\ -
\ -
' - ] - - - ]; - - 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'); - }); - }); - -}); - -}); diff --git a/modules/documentCanvas/canvas/widgets.js b/modules/documentCanvas/canvas/widgets.js deleted file mode 100644 index 408d2c0..0000000 --- a/modules/documentCanvas/canvas/widgets.js +++ /dev/null @@ -1,41 +0,0 @@ -define([ -'libs/jquery', -'utils/wlxml' -], function($, wlxmlUtils) { - -'use strict'; - -return { - labelWidget: function(tag, klass) { - return $('') - .addClass('canvas-widget canvas-widget-label') - .text(wlxmlUtils.wlxmlTagNames[tag] + (klass ? ' / ' + wlxmlUtils.wlxmlClassNames[klass] : '')); - }, - - footnoteHandler: function(clickHandler) { - var mydom = $('') - .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 = $('x') - .addClass('canvas-widget canvas-widget-hide-button'); - mydom.click(function(e) { - e.stopPropagation(); - clickHandler(); - }); - return mydom; - } - -}; - -}); \ No newline at end of file diff --git a/modules/documentCanvas/canvas/widgets.less b/modules/documentCanvas/canvas/widgets.less deleted file mode 100644 index 0f43c89..0000000 --- a/modules/documentCanvas/canvas/widgets.less +++ /dev/null @@ -1,60 +0,0 @@ -[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 diff --git a/modules/documentCanvas/canvas/wlxmlManagers.js b/modules/documentCanvas/canvas/wlxmlManagers.js deleted file mode 100644 index d566686..0000000 --- a/modules/documentCanvas/canvas/wlxmlManagers.js +++ /dev/null @@ -1,147 +0,0 @@ -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 diff --git a/modules/documentCanvas/classAttributes.js b/modules/documentCanvas/classAttributes.js deleted file mode 100644 index 06c9485..0000000 --- a/modules/documentCanvas/classAttributes.js +++ /dev/null @@ -1,64 +0,0 @@ -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 diff --git a/modules/documentCanvas/commands.js b/modules/documentCanvas/commands.js deleted file mode 100644 index 043d6b7..0000000 --- a/modules/documentCanvas/commands.js +++ /dev/null @@ -1,170 +0,0 @@ -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 diff --git a/modules/documentCanvas/documentCanvas.js b/modules/documentCanvas/documentCanvas.js deleted file mode 100644 index 3ebab2e..0000000 --- a/modules/documentCanvas/documentCanvas.js +++ /dev/null @@ -1,74 +0,0 @@ -// 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 diff --git a/modules/documentCanvas/documentCanvas.less b/modules/documentCanvas/documentCanvas.less deleted file mode 100644 index e17dce4..0000000 --- a/modules/documentCanvas/documentCanvas.less +++ /dev/null @@ -1,82 +0,0 @@ -@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 diff --git a/modules/documentCanvas/nodes.less b/modules/documentCanvas/nodes.less deleted file mode 100644 index d02e872..0000000 --- a/modules/documentCanvas/nodes.less +++ /dev/null @@ -1,137 +0,0 @@ -[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 diff --git a/modules/documentCanvas/template.html b/modules/documentCanvas/template.html deleted file mode 100644 index 63dc331..0000000 --- a/modules/documentCanvas/template.html +++ /dev/null @@ -1,7 +0,0 @@ -
-
-
-
-
-
-
\ No newline at end of file diff --git a/modules/documentCanvas/tests/classAttributes.test.js b/modules/documentCanvas/tests/classAttributes.test.js deleted file mode 100644 index 49e0be0..0000000 --- a/modules/documentCanvas/tests/classAttributes.test.js +++ /dev/null @@ -1,58 +0,0 @@ -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 diff --git a/modules/documentHistory/documentHistory.js b/modules/documentHistory/documentHistory.js deleted file mode 100644 index f595594..0000000 --- a/modules/documentHistory/documentHistory.js +++ /dev/null @@ -1,146 +0,0 @@ -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 diff --git a/modules/documentHistory/documentHistory.less b/modules/documentHistory/documentHistory.less deleted file mode 100644 index 608c9df..0000000 --- a/modules/documentHistory/documentHistory.less +++ /dev/null @@ -1,62 +0,0 @@ -.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; - } - } -} - diff --git a/modules/documentHistory/restoreDialog.js b/modules/documentHistory/restoreDialog.js deleted file mode 100644 index 2bf16f6..0000000 --- a/modules/documentHistory/restoreDialog.js +++ /dev/null @@ -1,55 +0,0 @@ -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 diff --git a/modules/documentHistory/templates/item.html b/modules/documentHistory/templates/item.html deleted file mode 100644 index 1629226..0000000 --- a/modules/documentHistory/templates/item.html +++ /dev/null @@ -1,6 +0,0 @@ -
-
<%= version %>
-
<%= date %>
-
<%= author %>
-
<%= description %>
-
\ No newline at end of file diff --git a/modules/documentHistory/templates/main.html b/modules/documentHistory/templates/main.html deleted file mode 100644 index 83cd627..0000000 --- a/modules/documentHistory/templates/main.html +++ /dev/null @@ -1,11 +0,0 @@ -
-
-
- - - -
-
-
-
-
\ No newline at end of file diff --git a/modules/documentHistory/templates/restoreDialog.html b/modules/documentHistory/templates/restoreDialog.html deleted file mode 100644 index ded0a11..0000000 --- a/modules/documentHistory/templates/restoreDialog.html +++ /dev/null @@ -1,14 +0,0 @@ - \ No newline at end of file diff --git a/modules/documentToolbar/documentToolbar.js b/modules/documentToolbar/documentToolbar.js deleted file mode 100644 index 7fbf6c1..0000000 --- a/modules/documentToolbar/documentToolbar.js +++ /dev/null @@ -1,56 +0,0 @@ -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 diff --git a/modules/documentToolbar/documentToolbar.less b/modules/documentToolbar/documentToolbar.less deleted file mode 100644 index 559afde..0000000 --- a/modules/documentToolbar/documentToolbar.less +++ /dev/null @@ -1,28 +0,0 @@ -.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; - } - -} - diff --git a/modules/documentToolbar/template.html b/modules/documentToolbar/template.html deleted file mode 100644 index 7ba2bc5..0000000 --- a/modules/documentToolbar/template.html +++ /dev/null @@ -1,37 +0,0 @@ -
-
- - - - - - - -
-
- -
-
- - - -
-
- -
-
- -
- -
-
\ No newline at end of file diff --git a/modules/indicator/indicator.js b/modules/indicator/indicator.js deleted file mode 100644 index 827a4b7..0000000 --- a/modules/indicator/indicator.js +++ /dev/null @@ -1,35 +0,0 @@ -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('' + msg + '').show(); - }, - clearMessage: function(report) { - view.dom.empty(); - if(report && report.message) { - view.dom.html('' + report.message + '').show().fadeOut(4000); - } - } - - }; - -}; - -}); diff --git a/modules/indicator/indicator.less b/modules/indicator/indicator.less deleted file mode 100644 index 48f6e3b..0000000 --- a/modules/indicator/indicator.less +++ /dev/null @@ -1,14 +0,0 @@ -.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 diff --git a/modules/indicator/template.html b/modules/indicator/template.html deleted file mode 100644 index e4e449b..0000000 --- a/modules/indicator/template.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
\ No newline at end of file diff --git a/modules/mainBar/mainBar.js b/modules/mainBar/mainBar.js deleted file mode 100644 index ecf6812..0000000 --- a/modules/mainBar/mainBar.js +++ /dev/null @@ -1,29 +0,0 @@ -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 diff --git a/modules/mainBar/mainBar.less b/modules/mainBar/mainBar.less deleted file mode 100644 index 0e2a899..0000000 --- a/modules/mainBar/mainBar.less +++ /dev/null @@ -1,21 +0,0 @@ -/*#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 diff --git a/modules/mainBar/template.html b/modules/mainBar/template.html deleted file mode 100644 index 3c43ee6..0000000 --- a/modules/mainBar/template.html +++ /dev/null @@ -1,7 +0,0 @@ -
- -
\ No newline at end of file diff --git a/modules/metadataEditor/metadataEditor.js b/modules/metadataEditor/metadataEditor.js deleted file mode 100644 index 94ad85b..0000000 --- a/modules/metadataEditor/metadataEditor.js +++ /dev/null @@ -1,115 +0,0 @@ -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.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 = $('
'); - toret.append($(document)); - var meta = $('\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 diff --git a/modules/metadataEditor/metadataEditor.less b/modules/metadataEditor/metadataEditor.less deleted file mode 100644 index 42bf5c0..0000000 --- a/modules/metadataEditor/metadataEditor.less +++ /dev/null @@ -1,32 +0,0 @@ -.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 diff --git a/modules/metadataEditor/templates/item.html b/modules/metadataEditor/templates/item.html deleted file mode 100644 index 3920f68..0000000 --- a/modules/metadataEditor/templates/item.html +++ /dev/null @@ -1,5 +0,0 @@ - -
<%= key %>
-
<%= value %>
- - \ No newline at end of file diff --git a/modules/metadataEditor/templates/main.html b/modules/metadataEditor/templates/main.html deleted file mode 100644 index 5826f82..0000000 --- a/modules/metadataEditor/templates/main.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
- Meta dane - -
- -
-
\ No newline at end of file diff --git a/modules/metadataEditor/transformations.js b/modules/metadataEditor/transformations.js deleted file mode 100644 index a09ee38..0000000 --- a/modules/metadataEditor/transformations.js +++ /dev/null @@ -1,24 +0,0 @@ -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 = $('\n'); - _.each(_.keys(metadata), function(key) { - meta.append('\n\t' + metadata[key] + ''); - }); - meta.append('\n'); - return vkbeautify.xml(meta.html()); - } - }; - -}); \ No newline at end of file diff --git a/modules/nodeBreadCrumbs/nodeBreadCrumbs.js b/modules/nodeBreadCrumbs/nodeBreadCrumbs.js deleted file mode 100644 index 320a1e8..0000000 --- a/modules/nodeBreadCrumbs/nodeBreadCrumbs.js +++ /dev/null @@ -1,65 +0,0 @@ -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: $('
' + template({node:null, parents: null}) + '
'), - 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 diff --git a/modules/nodeBreadCrumbs/template.html b/modules/nodeBreadCrumbs/template.html deleted file mode 100644 index afe44de..0000000 --- a/modules/nodeBreadCrumbs/template.html +++ /dev/null @@ -1,10 +0,0 @@ -
- -
\ No newline at end of file diff --git a/modules/nodeFamilyTree/nodeFamilyTree.js b/modules/nodeFamilyTree/nodeFamilyTree.js deleted file mode 100644 index 45f47c0..0000000 --- a/modules/nodeFamilyTree/nodeFamilyTree.js +++ /dev/null @@ -1,105 +0,0 @@ -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: $('
' + template({children: null, parent: null}) + '
'), - 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 = ''; - 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 diff --git a/modules/nodeFamilyTree/nodeFamilyTree.less b/modules/nodeFamilyTree/nodeFamilyTree.less deleted file mode 100644 index 6b4dc77..0000000 --- a/modules/nodeFamilyTree/nodeFamilyTree.less +++ /dev/null @@ -1,38 +0,0 @@ -.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 diff --git a/modules/nodeFamilyTree/template.html b/modules/nodeFamilyTree/template.html deleted file mode 100644 index 9bccfc3..0000000 --- a/modules/nodeFamilyTree/template.html +++ /dev/null @@ -1,19 +0,0 @@ -
- - - - - - - - - - \ No newline at end of file diff --git a/modules/nodePane/metaWidget/metaWidget.js b/modules/nodePane/metaWidget/metaWidget.js deleted file mode 100644 index 14ba7b6..0000000 --- a/modules/nodePane/metaWidget/metaWidget.js +++ /dev/null @@ -1,43 +0,0 @@ -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 = $('
'); - 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 diff --git a/modules/nodePane/metaWidget/metaWidget.test.js b/modules/nodePane/metaWidget/metaWidget.test.js deleted file mode 100644 index 05b75dc..0000000 --- a/modules/nodePane/metaWidget/metaWidget.test.js +++ /dev/null @@ -1,35 +0,0 @@ -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 = $('
'); - 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 diff --git a/modules/nodePane/metaWidget/stringField.html b/modules/nodePane/metaWidget/stringField.html deleted file mode 100644 index a5156a2..0000000 --- a/modules/nodePane/metaWidget/stringField.html +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/modules/nodePane/nodePane.js b/modules/nodePane/nodePane.js deleted file mode 100644 index f3fb68a..0000000 --- a/modules/nodePane/nodePane.js +++ /dev/null @@ -1,44 +0,0 @@ -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 diff --git a/modules/nodePane/nodePane.less b/modules/nodePane/nodePane.less deleted file mode 100644 index b7e4012..0000000 --- a/modules/nodePane/nodePane.less +++ /dev/null @@ -1,14 +0,0 @@ -.rng-module-nodePane { - label { - width: 50px; - display: inline-block; - } - select { - width: 100px; - } - - input { - width: 80px; - padding: 0 10px; - } -} \ No newline at end of file diff --git a/modules/nodePane/template.html b/modules/nodePane/template.html deleted file mode 100644 index b9a1254..0000000 --- a/modules/nodePane/template.html +++ /dev/null @@ -1,25 +0,0 @@ -
-
- <%= gettext('Current node') %> -
- - -
-
- - -
-
-
-
-
\ No newline at end of file diff --git a/modules/rng/diffLayout.html b/modules/rng/diffLayout.html deleted file mode 100644 index 8b03929..0000000 --- a/modules/rng/diffLayout.html +++ /dev/null @@ -1,4 +0,0 @@ -
-
-
-
\ No newline at end of file diff --git a/modules/rng/diffLayout.less b/modules/rng/diffLayout.less deleted file mode 100644 index 0f51948..0000000 --- a/modules/rng/diffLayout.less +++ /dev/null @@ -1,20 +0,0 @@ -.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 diff --git a/modules/rng/editingLayout.html b/modules/rng/editingLayout.html deleted file mode 100644 index 54a29aa..0000000 --- a/modules/rng/editingLayout.html +++ /dev/null @@ -1,7 +0,0 @@ -
-
-
-
-
-
-
\ No newline at end of file diff --git a/modules/rng/editingLayout.less b/modules/rng/editingLayout.less deleted file mode 100644 index 19d165f..0000000 --- a/modules/rng/editingLayout.less +++ /dev/null @@ -1,88 +0,0 @@ -.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 diff --git a/modules/rng/mainLayout.html b/modules/rng/mainLayout.html deleted file mode 100644 index 0cccb9c..0000000 --- a/modules/rng/mainLayout.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
-
-
-
-
-
-
\ No newline at end of file diff --git a/modules/rng/mainLayout.less b/modules/rng/mainLayout.less deleted file mode 100644 index ac28f02..0000000 --- a/modules/rng/mainLayout.less +++ /dev/null @@ -1,43 +0,0 @@ -#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 diff --git a/modules/rng/rng.js b/modules/rng/rng.js deleted file mode 100644 index 59652a7..0000000 --- a/modules/rng/rng.js +++ /dev/null @@ -1,326 +0,0 @@ -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 diff --git a/modules/rng/rng.less b/modules/rng/rng.less deleted file mode 100644 index 196be1f..0000000 --- a/modules/rng/rng.less +++ /dev/null @@ -1,3 +0,0 @@ -@import 'mainLayout.less'; -@import 'editingLayout.less'; -@import 'diffLayout.less'; \ No newline at end of file diff --git a/modules/sourceEditor/sourceEditor.js b/modules/sourceEditor/sourceEditor.js deleted file mode 100644 index e88e5e1..0000000 --- a/modules/sourceEditor/sourceEditor.js +++ /dev/null @@ -1,40 +0,0 @@ -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 diff --git a/modules/sourceEditor/sourceEditor.less b/modules/sourceEditor/sourceEditor.less deleted file mode 100644 index 003ee6e..0000000 --- a/modules/sourceEditor/sourceEditor.less +++ /dev/null @@ -1,7 +0,0 @@ -#rng-sourceEditor-editor { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; -} \ No newline at end of file diff --git a/rng.js b/rng.js deleted file mode 100644 index 04a8d4f..0000000 --- a/rng.js +++ /dev/null @@ -1,14 +0,0 @@ -define(function() { - -'use strict'; - -return { - modules: {}, - initModules: ['rng'], - permissions: { - 'skelton': ['getDOM'], - 'rng': ['getModule', 'handleEvents', 'getDOM'] - } -} - -}); \ No newline at end of file diff --git a/src/editor/entrypoint.js b/src/editor/entrypoint.js new file mode 100644 index 0000000..5e546ef --- /dev/null +++ b/src/editor/entrypoint.js @@ -0,0 +1,56 @@ +(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 diff --git a/src/editor/modules.js b/src/editor/modules.js new file mode 100644 index 0000000..bb4d439 --- /dev/null +++ b/src/editor/modules.js @@ -0,0 +1,28 @@ +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 diff --git a/src/editor/modules/data/data.js b/src/editor/modules/data/data.js new file mode 100644 index 0000000..9b2f163 --- /dev/null +++ b/src/editor/modules/data/data.js @@ -0,0 +1,133 @@ +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 = '\n\ + \n\ + \n\ +
\n\ + '; + } + + + 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 diff --git a/src/editor/modules/data/data.less b/src/editor/modules/data/data.less new file mode 100644 index 0000000..1beb090 --- /dev/null +++ b/src/editor/modules/data/data.less @@ -0,0 +1 @@ +@import 'saveDialog.less'; \ No newline at end of file diff --git a/src/editor/modules/data/saveDialog.html b/src/editor/modules/data/saveDialog.html new file mode 100644 index 0000000..0846910 --- /dev/null +++ b/src/editor/modules/data/saveDialog.html @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/src/editor/modules/data/saveDialog.js b/src/editor/modules/data/saveDialog.js new file mode 100644 index 0000000..90832e6 --- /dev/null +++ b/src/editor/modules/data/saveDialog.js @@ -0,0 +1,56 @@ +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 diff --git a/src/editor/modules/data/saveDialog.less b/src/editor/modules/data/saveDialog.less new file mode 100644 index 0000000..e863e93 --- /dev/null +++ b/src/editor/modules/data/saveDialog.less @@ -0,0 +1,19 @@ +.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 diff --git a/src/editor/modules/diffViewer/diff.html b/src/editor/modules/diffViewer/diff.html new file mode 100644 index 0000000..cbf9b54 --- /dev/null +++ b/src/editor/modules/diffViewer/diff.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/editor/modules/diffViewer/diffViewer.js b/src/editor/modules/diffViewer/diffViewer.js new file mode 100644 index 0000000..e792545 --- /dev/null +++ b/src/editor/modules/diffViewer/diffViewer.js @@ -0,0 +1,38 @@ +define([ +'libs/jquery', +'libs/underscore', +'views/tabs/tabs', +'libs/text!./diff.html' +], function($, _, tabs, diffTemplateSrc) { + +'use strict'; + +return function(sandbox) { + + var dom = $('
').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 diff --git a/src/editor/modules/diffViewer/diffViewer.less b/src/editor/modules/diffViewer/diffViewer.less new file mode 100644 index 0000000..34ab240 --- /dev/null +++ b/src/editor/modules/diffViewer/diffViewer.less @@ -0,0 +1,77 @@ +.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 diff --git a/src/editor/modules/documentCanvas/canvas/canvas.js b/src/editor/modules/documentCanvas/canvas/canvas.js new file mode 100644 index 0000000..d8db2bc --- /dev/null +++ b/src/editor/modules/documentCanvas/canvas/canvas.js @@ -0,0 +1,657 @@ +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 = $('
').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 = $('
'), + 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 diff --git a/src/editor/modules/documentCanvas/canvas/canvas.test.js b/src/editor/modules/documentCanvas/canvas/canvas.test.js new file mode 100644 index 0000000..2dc24eb --- /dev/null +++ b/src/editor/modules/documentCanvas/canvas/canvas.test.js @@ -0,0 +1,1553 @@ +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 = 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 + '>').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('
').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('
').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('
Alice
').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('
'); + expect(c.doc().getWlxmlTag()).to.equal('section'); + }); + + describe('root element', function() { + it('has no parent', function() { + var c = canvas.fromXML('
'); + expect(c.doc().parent()).to.be.null; + }); + }); + + describe('DocumentTextElement', function() { + it('can have its content set', function() { + var c = canvas.fromXML('
Alice
'), + 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('
'), + 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 = 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 = 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('
'), + 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('
'), + 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('
'), + 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('
'), + 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('
Alice
'), + 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('
'); + 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('
'), + 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('
'), + children = c.doc().children(); + expect(children.length).to.equal(1); + expect(children[0]).to.be.instanceOf(documentElement.DocumentNodeElement); + + c = canvas.fromXML('
'), + 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('
Alice
'), + 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('
'), + 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('
Alice
'), + 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('
\ +
\ +
A
\ +
B
\ +
\ +
'), + 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('
Alice has a cat
'), + 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('
Alice has a cat
'), + 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('
'); + 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 = 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('
'); + 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('
'); + 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('
'); + 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('
Alice has a cat
'); + 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(' Alice has a cat '); + 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('
Alice has a cat
'); + 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('
Alice has a cat
'); + 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('
authorAlice
has
a cat
'), + 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('\ +
\ +
\ +
\ + Alice\ +
\ +
\ + Some text\ +
'), + 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('
'), + 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('
'), + 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('
'), + 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('
'), + 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('
'), + 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('
'), + 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('
Alice
'), + 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('
Alice
'), + 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('
Alice has a cat
'), + 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('
Alice has a cat
'), + 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('
Alice has a cat
'), + 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('
Alice
has
a cat
'), + 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('
Some header
'), + 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('
Some header
'), + 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('
Some header
'), + 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('\ +
\ +
\ + A fancy and nice header\ +
\ +
'), + 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('
'), + 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('
Alice
'), + 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('
Alice has a cat
'), + 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('
Alice has a cat
'), + 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('
Alice has a small cat
'), + 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('
Alice
has
a cat
'), + 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('
div>
'), + 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('
Alice
has
a cat
'), + 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('
Alice has a cat
'), + 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('
Sometext
'), + 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('
Alice hasa cat
'), + 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('
Alice
has a cat!
!!
'), + 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('
Alice has a cat
'), + 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('\ +
\ + Alice\ +
has
\ + a\ +
cat
\ +
'), + 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('\ +
\ +
\ +
A
\ +
B
\ +
C
\ +
D
\ +
\ +
'), + 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('\ +
\ +
\ +
0
\ +
1
\ +
2
\ +
3
\ +
\ +
'), + 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('\ +
\ +
\ +
0
\ +
1
\ +
2
\ +
\ +
'), + 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('\ +
\ +
\ +
0
\ +
1
\ +
2
\ +
\ +
'), + 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('\ +
\ +
\ +
some item
\ +
some item 2
\ +
\ +
'), + 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('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
1.3
\ +
\ +
\ +
2
\ +
\ +
'), + 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('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
1.3
\ +
\ +
\ +
2
\ +
\ +
'), + 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('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
1.3
\ +
\ +
\ +
2
\ +
\ +
'), + 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('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
\ +
\ +
2
\ +
\ +
'), + 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('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
\ +
\ +
2
\ +
\ +
'), + 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('
Alice has a cat
'), + 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('
Alice has a big cat
'), + 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('
Alice has a big cat
'), + 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 = '
Alice
has
a cat!
', + 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 = '
', + $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('
'); + c.doc().setWlxmlMetaAttr('uri', ''); + expect($(c.toXML()).attr('meta-uri')).to.equal(undefined, 'overriding attribute with zero length string'); + + c = canvas.fromXML('
'); + 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 = '
< >
', + c = canvas.fromXML(xmlIn), + xmlOut = c.toXML(); + expect(xmlOut).to.equal(xmlIn); + }); + it('keeps entities intact when they form html/xml', function() { + var xmlIn = '
<abc>
', + 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 = '
', + 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 = '
\n\n\n
\n\n\n
\n\n\n
', + 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 = '
\n\n\n\n\n\n\n\n\n
', + 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 = '
abc
some div
abc
', + c = canvas.fromXML(xmlIn), + xmlOut = c.toXML(); + + expect(xmlOut).to.equal(xmlIn); + }); + + it('nests new children block elements', function() { + var c = canvas.fromXML('
'); + + c.doc().append({tag: 'header'}); + + var xmlOut = c.toXML(); + expect(xmlOut.split('\n ')[0]).to.equal('
', 'nesting start ok'); + expect(xmlOut.split('\n').slice(-1)[0]).to.equal('
', 'nesting end ok'); + + }); + + it('doesn\'t nest new children inline elements', function() { + var c = canvas.fromXML('
'); + + c.doc().append({tag: 'span'}); + + var xmlOut = c.toXML(); + expect(xmlOut).to.equal('
'); + }); + + it('keeps original white space at the end of text', function() { + + var xmlIn = '
Some text ended with white space \ + \ + Some text some text\ + \ +
', + 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 = '
\ +
header1
\ + Some text surrounded by white space\ +
header2
\ +
', + 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 = '
\ +
header
\ + \ + Some text surrounded by white space\ + \ +
', + c = canvas.fromXML(xmlIn); + + var xmlOut = c.toXML(); + expect(xmlOut).to.equal(xmlIn); + }); + + it('keeps white space after detaching text element', function() { + var xmlIn = '
header
\n\ + \n\ + text1\n\ + \n\ +
', + expectedXmlOut = '
header
\n\ + \n\ + \n\ + \n\ +
', + 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 diff --git a/src/editor/modules/documentCanvas/canvas/documentElement.js b/src/editor/modules/documentCanvas/canvas/documentElement.js new file mode 100644 index 0000000..0627bc1 --- /dev/null +++ b/src/editor/modules/documentCanvas/canvas/documentElement.js @@ -0,0 +1,669 @@ +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 = $('
') + .attr('document-node-element', ''), + widgetsContainer = $('
') + .addClass('canvas-widgets') + .attr('contenteditable', false), + container = $('
') + .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 = $('
'); + 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 $('
') + .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('
'); + 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('
'); + 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 diff --git a/src/editor/modules/documentCanvas/canvas/keyboard.js b/src/editor/modules/documentCanvas/canvas/keyboard.js new file mode 100644 index 0000000..1830017 --- /dev/null +++ b/src/editor/modules/documentCanvas/canvas/keyboard.js @@ -0,0 +1,197 @@ +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 diff --git a/src/editor/modules/documentCanvas/canvas/utils.js b/src/editor/modules/documentCanvas/canvas/utils.js new file mode 100644 index 0000000..0eb19d0 --- /dev/null +++ b/src/editor/modules/documentCanvas/canvas/utils.js @@ -0,0 +1,29 @@ +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' + } +}; + +}); diff --git a/src/editor/modules/documentCanvas/canvas/utils.test.js b/src/editor/modules/documentCanvas/canvas/utils.test.js new file mode 100644 index 0000000..c93e74b --- /dev/null +++ b/src/editor/modules/documentCanvas/canvas/utils.test.js @@ -0,0 +1,70 @@ +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', + '\ + \ +
\ + \ + ' + ], + ['returns nearest sibling if applicable', + '
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
' + ], + ['looks inside siblings children', + '
\ +
\ +
\ +
\ + \ +
\ +
\ +
\ + \ +
\ +
\ +
\ +
\ +
\ +
' + ] + + + ]; + + 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'); + }); + }); + +}); + +}); diff --git a/src/editor/modules/documentCanvas/canvas/widgets.js b/src/editor/modules/documentCanvas/canvas/widgets.js new file mode 100644 index 0000000..408d2c0 --- /dev/null +++ b/src/editor/modules/documentCanvas/canvas/widgets.js @@ -0,0 +1,41 @@ +define([ +'libs/jquery', +'utils/wlxml' +], function($, wlxmlUtils) { + +'use strict'; + +return { + labelWidget: function(tag, klass) { + return $('') + .addClass('canvas-widget canvas-widget-label') + .text(wlxmlUtils.wlxmlTagNames[tag] + (klass ? ' / ' + wlxmlUtils.wlxmlClassNames[klass] : '')); + }, + + footnoteHandler: function(clickHandler) { + var mydom = $('') + .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 = $('x') + .addClass('canvas-widget canvas-widget-hide-button'); + mydom.click(function(e) { + e.stopPropagation(); + clickHandler(); + }); + return mydom; + } + +}; + +}); \ No newline at end of file diff --git a/src/editor/modules/documentCanvas/canvas/widgets.less b/src/editor/modules/documentCanvas/canvas/widgets.less new file mode 100644 index 0000000..0f43c89 --- /dev/null +++ b/src/editor/modules/documentCanvas/canvas/widgets.less @@ -0,0 +1,60 @@ +[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 diff --git a/src/editor/modules/documentCanvas/canvas/wlxmlManagers.js b/src/editor/modules/documentCanvas/canvas/wlxmlManagers.js new file mode 100644 index 0000000..d566686 --- /dev/null +++ b/src/editor/modules/documentCanvas/canvas/wlxmlManagers.js @@ -0,0 +1,147 @@ +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 diff --git a/src/editor/modules/documentCanvas/classAttributes.js b/src/editor/modules/documentCanvas/classAttributes.js new file mode 100644 index 0000000..06c9485 --- /dev/null +++ b/src/editor/modules/documentCanvas/classAttributes.js @@ -0,0 +1,64 @@ +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 diff --git a/src/editor/modules/documentCanvas/commands.js b/src/editor/modules/documentCanvas/commands.js new file mode 100644 index 0000000..043d6b7 --- /dev/null +++ b/src/editor/modules/documentCanvas/commands.js @@ -0,0 +1,170 @@ +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 diff --git a/src/editor/modules/documentCanvas/documentCanvas.js b/src/editor/modules/documentCanvas/documentCanvas.js new file mode 100644 index 0000000..3ebab2e --- /dev/null +++ b/src/editor/modules/documentCanvas/documentCanvas.js @@ -0,0 +1,74 @@ +// 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 diff --git a/src/editor/modules/documentCanvas/documentCanvas.less b/src/editor/modules/documentCanvas/documentCanvas.less new file mode 100644 index 0000000..e17dce4 --- /dev/null +++ b/src/editor/modules/documentCanvas/documentCanvas.less @@ -0,0 +1,82 @@ +@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 diff --git a/src/editor/modules/documentCanvas/nodes.less b/src/editor/modules/documentCanvas/nodes.less new file mode 100644 index 0000000..d02e872 --- /dev/null +++ b/src/editor/modules/documentCanvas/nodes.less @@ -0,0 +1,137 @@ +[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 diff --git a/src/editor/modules/documentCanvas/template.html b/src/editor/modules/documentCanvas/template.html new file mode 100644 index 0000000..63dc331 --- /dev/null +++ b/src/editor/modules/documentCanvas/template.html @@ -0,0 +1,7 @@ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/editor/modules/documentCanvas/tests/classAttributes.test.js b/src/editor/modules/documentCanvas/tests/classAttributes.test.js new file mode 100644 index 0000000..49e0be0 --- /dev/null +++ b/src/editor/modules/documentCanvas/tests/classAttributes.test.js @@ -0,0 +1,58 @@ +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 diff --git a/src/editor/modules/documentHistory/documentHistory.js b/src/editor/modules/documentHistory/documentHistory.js new file mode 100644 index 0000000..f595594 --- /dev/null +++ b/src/editor/modules/documentHistory/documentHistory.js @@ -0,0 +1,146 @@ +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 diff --git a/src/editor/modules/documentHistory/documentHistory.less b/src/editor/modules/documentHistory/documentHistory.less new file mode 100644 index 0000000..608c9df --- /dev/null +++ b/src/editor/modules/documentHistory/documentHistory.less @@ -0,0 +1,62 @@ +.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; + } + } +} + diff --git a/src/editor/modules/documentHistory/restoreDialog.js b/src/editor/modules/documentHistory/restoreDialog.js new file mode 100644 index 0000000..2bf16f6 --- /dev/null +++ b/src/editor/modules/documentHistory/restoreDialog.js @@ -0,0 +1,55 @@ +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 diff --git a/src/editor/modules/documentHistory/templates/item.html b/src/editor/modules/documentHistory/templates/item.html new file mode 100644 index 0000000..1629226 --- /dev/null +++ b/src/editor/modules/documentHistory/templates/item.html @@ -0,0 +1,6 @@ +
+
<%= version %>
+
<%= date %>
+
<%= author %>
+
<%= description %>
+
\ No newline at end of file diff --git a/src/editor/modules/documentHistory/templates/main.html b/src/editor/modules/documentHistory/templates/main.html new file mode 100644 index 0000000..83cd627 --- /dev/null +++ b/src/editor/modules/documentHistory/templates/main.html @@ -0,0 +1,11 @@ +
+
+
+ + + +
+
+
+
+
\ No newline at end of file diff --git a/src/editor/modules/documentHistory/templates/restoreDialog.html b/src/editor/modules/documentHistory/templates/restoreDialog.html new file mode 100644 index 0000000..ded0a11 --- /dev/null +++ b/src/editor/modules/documentHistory/templates/restoreDialog.html @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/src/editor/modules/documentToolbar/documentToolbar.js b/src/editor/modules/documentToolbar/documentToolbar.js new file mode 100644 index 0000000..7fbf6c1 --- /dev/null +++ b/src/editor/modules/documentToolbar/documentToolbar.js @@ -0,0 +1,56 @@ +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 diff --git a/src/editor/modules/documentToolbar/documentToolbar.less b/src/editor/modules/documentToolbar/documentToolbar.less new file mode 100644 index 0000000..559afde --- /dev/null +++ b/src/editor/modules/documentToolbar/documentToolbar.less @@ -0,0 +1,28 @@ +.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; + } + +} + diff --git a/src/editor/modules/documentToolbar/template.html b/src/editor/modules/documentToolbar/template.html new file mode 100644 index 0000000..7ba2bc5 --- /dev/null +++ b/src/editor/modules/documentToolbar/template.html @@ -0,0 +1,37 @@ +
+
+ + + + + + + +
+
+ +
+
+ + + +
+
+ +
+
+ +
+ +
+
\ No newline at end of file diff --git a/src/editor/modules/indicator/indicator.js b/src/editor/modules/indicator/indicator.js new file mode 100644 index 0000000..827a4b7 --- /dev/null +++ b/src/editor/modules/indicator/indicator.js @@ -0,0 +1,35 @@ +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('' + msg + '').show(); + }, + clearMessage: function(report) { + view.dom.empty(); + if(report && report.message) { + view.dom.html('' + report.message + '').show().fadeOut(4000); + } + } + + }; + +}; + +}); diff --git a/src/editor/modules/indicator/indicator.less b/src/editor/modules/indicator/indicator.less new file mode 100644 index 0000000..48f6e3b --- /dev/null +++ b/src/editor/modules/indicator/indicator.less @@ -0,0 +1,14 @@ +.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 diff --git a/src/editor/modules/indicator/template.html b/src/editor/modules/indicator/template.html new file mode 100644 index 0000000..e4e449b --- /dev/null +++ b/src/editor/modules/indicator/template.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/src/editor/modules/mainBar/mainBar.js b/src/editor/modules/mainBar/mainBar.js new file mode 100644 index 0000000..ecf6812 --- /dev/null +++ b/src/editor/modules/mainBar/mainBar.js @@ -0,0 +1,29 @@ +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 diff --git a/src/editor/modules/mainBar/mainBar.less b/src/editor/modules/mainBar/mainBar.less new file mode 100644 index 0000000..0e2a899 --- /dev/null +++ b/src/editor/modules/mainBar/mainBar.less @@ -0,0 +1,21 @@ +/*#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 diff --git a/src/editor/modules/mainBar/template.html b/src/editor/modules/mainBar/template.html new file mode 100644 index 0000000..3c43ee6 --- /dev/null +++ b/src/editor/modules/mainBar/template.html @@ -0,0 +1,7 @@ +
+ +
\ No newline at end of file diff --git a/src/editor/modules/metadataEditor/metadataEditor.js b/src/editor/modules/metadataEditor/metadataEditor.js new file mode 100644 index 0000000..94ad85b --- /dev/null +++ b/src/editor/modules/metadataEditor/metadataEditor.js @@ -0,0 +1,115 @@ +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.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 = $('
'); + toret.append($(document)); + var meta = $('\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 diff --git a/src/editor/modules/metadataEditor/metadataEditor.less b/src/editor/modules/metadataEditor/metadataEditor.less new file mode 100644 index 0000000..42bf5c0 --- /dev/null +++ b/src/editor/modules/metadataEditor/metadataEditor.less @@ -0,0 +1,32 @@ +.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 diff --git a/src/editor/modules/metadataEditor/templates/item.html b/src/editor/modules/metadataEditor/templates/item.html new file mode 100644 index 0000000..3920f68 --- /dev/null +++ b/src/editor/modules/metadataEditor/templates/item.html @@ -0,0 +1,5 @@ +
+ + + + \ No newline at end of file diff --git a/src/editor/modules/metadataEditor/templates/main.html b/src/editor/modules/metadataEditor/templates/main.html new file mode 100644 index 0000000..5826f82 --- /dev/null +++ b/src/editor/modules/metadataEditor/templates/main.html @@ -0,0 +1,8 @@ +
+
+ Meta dane +
powyżej<% if(parent) { %><%= parent.repr %><% } else { %>-<% } %>
poniżej -
    - <% if(!children || children.length === 0) { %>-<% } else { %> - <% children.forEach(function(child) { %> -
  • <% if(child.bold) { %><% } %><%= child.repr %><% if(child.bold) { %><% } %>
  • - <% }); %> - <% } %> -
-
<%= key %>
<%= value %>
+
+ + +
\ No newline at end of file diff --git a/src/editor/modules/metadataEditor/transformations.js b/src/editor/modules/metadataEditor/transformations.js new file mode 100644 index 0000000..a09ee38 --- /dev/null +++ b/src/editor/modules/metadataEditor/transformations.js @@ -0,0 +1,24 @@ +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 = $('\n'); + _.each(_.keys(metadata), function(key) { + meta.append('\n\t' + metadata[key] + ''); + }); + meta.append('\n'); + return vkbeautify.xml(meta.html()); + } + }; + +}); \ No newline at end of file diff --git a/src/editor/modules/nodeBreadCrumbs/nodeBreadCrumbs.js b/src/editor/modules/nodeBreadCrumbs/nodeBreadCrumbs.js new file mode 100644 index 0000000..320a1e8 --- /dev/null +++ b/src/editor/modules/nodeBreadCrumbs/nodeBreadCrumbs.js @@ -0,0 +1,65 @@ +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: $('
' + template({node:null, parents: null}) + '
'), + 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 diff --git a/src/editor/modules/nodeBreadCrumbs/template.html b/src/editor/modules/nodeBreadCrumbs/template.html new file mode 100644 index 0000000..afe44de --- /dev/null +++ b/src/editor/modules/nodeBreadCrumbs/template.html @@ -0,0 +1,10 @@ +
+ +
\ No newline at end of file diff --git a/src/editor/modules/nodeFamilyTree/nodeFamilyTree.js b/src/editor/modules/nodeFamilyTree/nodeFamilyTree.js new file mode 100644 index 0000000..45f47c0 --- /dev/null +++ b/src/editor/modules/nodeFamilyTree/nodeFamilyTree.js @@ -0,0 +1,105 @@ +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: $('
' + template({children: null, parent: null}) + '
'), + 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 = ''; + 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 diff --git a/src/editor/modules/nodeFamilyTree/nodeFamilyTree.less b/src/editor/modules/nodeFamilyTree/nodeFamilyTree.less new file mode 100644 index 0000000..6b4dc77 --- /dev/null +++ b/src/editor/modules/nodeFamilyTree/nodeFamilyTree.less @@ -0,0 +1,38 @@ +.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 diff --git a/src/editor/modules/nodeFamilyTree/template.html b/src/editor/modules/nodeFamilyTree/template.html new file mode 100644 index 0000000..9bccfc3 --- /dev/null +++ b/src/editor/modules/nodeFamilyTree/template.html @@ -0,0 +1,19 @@ +
+ + + + + + + + + + \ No newline at end of file diff --git a/src/editor/modules/nodePane/metaWidget/metaWidget.js b/src/editor/modules/nodePane/metaWidget/metaWidget.js new file mode 100644 index 0000000..14ba7b6 --- /dev/null +++ b/src/editor/modules/nodePane/metaWidget/metaWidget.js @@ -0,0 +1,43 @@ +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 = $('
'); + 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 diff --git a/src/editor/modules/nodePane/metaWidget/metaWidget.test.js b/src/editor/modules/nodePane/metaWidget/metaWidget.test.js new file mode 100644 index 0000000..05b75dc --- /dev/null +++ b/src/editor/modules/nodePane/metaWidget/metaWidget.test.js @@ -0,0 +1,35 @@ +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 = $('
'); + 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 diff --git a/src/editor/modules/nodePane/metaWidget/stringField.html b/src/editor/modules/nodePane/metaWidget/stringField.html new file mode 100644 index 0000000..a5156a2 --- /dev/null +++ b/src/editor/modules/nodePane/metaWidget/stringField.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/editor/modules/nodePane/nodePane.js b/src/editor/modules/nodePane/nodePane.js new file mode 100644 index 0000000..f3fb68a --- /dev/null +++ b/src/editor/modules/nodePane/nodePane.js @@ -0,0 +1,44 @@ +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 diff --git a/src/editor/modules/nodePane/nodePane.less b/src/editor/modules/nodePane/nodePane.less new file mode 100644 index 0000000..b7e4012 --- /dev/null +++ b/src/editor/modules/nodePane/nodePane.less @@ -0,0 +1,14 @@ +.rng-module-nodePane { + label { + width: 50px; + display: inline-block; + } + select { + width: 100px; + } + + input { + width: 80px; + padding: 0 10px; + } +} \ No newline at end of file diff --git a/src/editor/modules/nodePane/template.html b/src/editor/modules/nodePane/template.html new file mode 100644 index 0000000..b9a1254 --- /dev/null +++ b/src/editor/modules/nodePane/template.html @@ -0,0 +1,25 @@ +
+
+ <%= gettext('Current node') %> +
+ + +
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/src/editor/modules/rng/diffLayout.html b/src/editor/modules/rng/diffLayout.html new file mode 100644 index 0000000..8b03929 --- /dev/null +++ b/src/editor/modules/rng/diffLayout.html @@ -0,0 +1,4 @@ +
+
+
+
\ No newline at end of file diff --git a/src/editor/modules/rng/diffLayout.less b/src/editor/modules/rng/diffLayout.less new file mode 100644 index 0000000..0f51948 --- /dev/null +++ b/src/editor/modules/rng/diffLayout.less @@ -0,0 +1,20 @@ +.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 diff --git a/src/editor/modules/rng/editingLayout.html b/src/editor/modules/rng/editingLayout.html new file mode 100644 index 0000000..54a29aa --- /dev/null +++ b/src/editor/modules/rng/editingLayout.html @@ -0,0 +1,7 @@ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/editor/modules/rng/editingLayout.less b/src/editor/modules/rng/editingLayout.less new file mode 100644 index 0000000..19d165f --- /dev/null +++ b/src/editor/modules/rng/editingLayout.less @@ -0,0 +1,88 @@ +.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 diff --git a/src/editor/modules/rng/mainLayout.html b/src/editor/modules/rng/mainLayout.html new file mode 100644 index 0000000..0cccb9c --- /dev/null +++ b/src/editor/modules/rng/mainLayout.html @@ -0,0 +1,8 @@ +
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/editor/modules/rng/mainLayout.less b/src/editor/modules/rng/mainLayout.less new file mode 100644 index 0000000..ac28f02 --- /dev/null +++ b/src/editor/modules/rng/mainLayout.less @@ -0,0 +1,43 @@ +#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 diff --git a/src/editor/modules/rng/rng.js b/src/editor/modules/rng/rng.js new file mode 100644 index 0000000..59652a7 --- /dev/null +++ b/src/editor/modules/rng/rng.js @@ -0,0 +1,326 @@ +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 diff --git a/src/editor/modules/rng/rng.less b/src/editor/modules/rng/rng.less new file mode 100644 index 0000000..196be1f --- /dev/null +++ b/src/editor/modules/rng/rng.less @@ -0,0 +1,3 @@ +@import 'mainLayout.less'; +@import 'editingLayout.less'; +@import 'diffLayout.less'; \ No newline at end of file diff --git a/src/editor/modules/sourceEditor/sourceEditor.js b/src/editor/modules/sourceEditor/sourceEditor.js new file mode 100644 index 0000000..e88e5e1 --- /dev/null +++ b/src/editor/modules/sourceEditor/sourceEditor.js @@ -0,0 +1,40 @@ +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 diff --git a/src/editor/modules/sourceEditor/sourceEditor.less b/src/editor/modules/sourceEditor/sourceEditor.less new file mode 100644 index 0000000..003ee6e --- /dev/null +++ b/src/editor/modules/sourceEditor/sourceEditor.less @@ -0,0 +1,7 @@ +#rng-sourceEditor-editor { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} \ No newline at end of file diff --git a/src/editor/rng.js b/src/editor/rng.js new file mode 100644 index 0000000..04a8d4f --- /dev/null +++ b/src/editor/rng.js @@ -0,0 +1,14 @@ +define(function() { + +'use strict'; + +return { + modules: {}, + initModules: ['rng'], + permissions: { + 'skelton': ['getDOM'], + 'rng': ['getModule', 'handleEvents', 'getDOM'] + } +} + +}); \ No newline at end of file diff --git a/src/editor/styles/common.less b/src/editor/styles/common.less new file mode 100644 index 0000000..00e1525 --- /dev/null +++ b/src/editor/styles/common.less @@ -0,0 +1,10 @@ +body { + padding-top: 5px; +} + +.rng-common-hoveredNode { + border-color: red !important; + border-style:solid; + border-width:1px; +} + diff --git a/src/editor/styles/main.less b/src/editor/styles/main.less new file mode 100644 index 0000000..48fcda9 --- /dev/null +++ b/src/editor/styles/main.less @@ -0,0 +1,15 @@ +@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'; diff --git a/src/editor/styles/mixins.less b/src/editor/styles/mixins.less new file mode 100644 index 0000000..3a14f31 --- /dev/null +++ b/src/editor/styles/mixins.less @@ -0,0 +1,20 @@ +.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 diff --git a/src/editor/utils/wlxml.js b/src/editor/utils/wlxml.js new file mode 100644 index 0000000..5f1b84d --- /dev/null +++ b/src/editor/utils/wlxml.js @@ -0,0 +1,36 @@ +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 diff --git a/src/editor/views/tabs/tabs.js b/src/editor/views/tabs/tabs.js new file mode 100644 index 0000000..b910b92 --- /dev/null +++ b/src/editor/views/tabs/tabs.js @@ -0,0 +1,109 @@ +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 diff --git a/src/editor/views/tabs/templates/handle.html b/src/editor/views/tabs/templates/handle.html new file mode 100644 index 0000000..b828ce4 --- /dev/null +++ b/src/editor/views/tabs/templates/handle.html @@ -0,0 +1 @@ +
  • <% if(icon) { %><% } %><%= text %>
  • \ No newline at end of file diff --git a/src/editor/views/tabs/templates/main.html b/src/editor/views/tabs/templates/main.html new file mode 100644 index 0000000..66efc99 --- /dev/null +++ b/src/editor/views/tabs/templates/main.html @@ -0,0 +1,4 @@ + +
    +
    diff --git a/src/fnpjs/layout.js b/src/fnpjs/layout.js new file mode 100644 index 0000000..c1d110e --- /dev/null +++ b/src/fnpjs/layout.js @@ -0,0 +1,37 @@ +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 diff --git a/src/fnpjs/runner.js b/src/fnpjs/runner.js new file mode 100644 index 0000000..66e0b68 --- /dev/null +++ b/src/fnpjs/runner.js @@ -0,0 +1,77 @@ +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 diff --git a/src/fnpjs/vbox.js b/src/fnpjs/vbox.js new file mode 100644 index 0000000..dfb5a9f --- /dev/null +++ b/src/fnpjs/vbox.js @@ -0,0 +1,13 @@ +define(['libs/jquery', './layout'], function($, layout) { + + var VBox = function() {}; + + VBox.prototype = new layout.Layout('
    '); + VBox.prototype.appendView = function(view) { + var item = $('
    ').addClass('fnpjs-vbox-item').append(view); + this.dom.append(item); + }; + + return {VBox: VBox}; + +}); \ No newline at end of file diff --git a/styles/common.less b/styles/common.less deleted file mode 100644 index 00e1525..0000000 --- a/styles/common.less +++ /dev/null @@ -1,10 +0,0 @@ -body { - padding-top: 5px; -} - -.rng-common-hoveredNode { - border-color: red !important; - border-style:solid; - border-width:1px; -} - diff --git a/styles/main.less b/styles/main.less deleted file mode 100644 index 48fcda9..0000000 --- a/styles/main.less +++ /dev/null @@ -1,15 +0,0 @@ -@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'; diff --git a/styles/mixins.less b/styles/mixins.less deleted file mode 100644 index 3a14f31..0000000 --- a/styles/mixins.less +++ /dev/null @@ -1,20 +0,0 @@ -.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 diff --git a/tests/main.js b/tests/main.js index 16154b7..87eb6b2 100644 --- a/tests/main.js +++ b/tests/main.js @@ -7,31 +7,43 @@ }); 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' - } } }); diff --git a/utils/wlxml.js b/utils/wlxml.js deleted file mode 100644 index 5f1b84d..0000000 --- a/utils/wlxml.js +++ /dev/null @@ -1,36 +0,0 @@ -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 diff --git a/views/tabs/tabs.js b/views/tabs/tabs.js deleted file mode 100644 index b910b92..0000000 --- a/views/tabs/tabs.js +++ /dev/null @@ -1,109 +0,0 @@ -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 diff --git a/views/tabs/templates/handle.html b/views/tabs/templates/handle.html deleted file mode 100644 index b828ce4..0000000 --- a/views/tabs/templates/handle.html +++ /dev/null @@ -1 +0,0 @@ -
  • <% if(icon) { %><% } %><%= text %>
  • \ No newline at end of file diff --git a/views/tabs/templates/main.html b/views/tabs/templates/main.html deleted file mode 100644 index 66efc99..0000000 --- a/views/tabs/templates/main.html +++ /dev/null @@ -1,4 +0,0 @@ - -
    -
    powyżej<% if(parent) { %><%= parent.repr %><% } else { %>-<% } %>
    poniżej +
      + <% if(!children || children.length === 0) { %>-<% } else { %> + <% children.forEach(function(child) { %> +
    • <% if(child.bold) { %><% } %><%= child.repr %><% if(child.bold) { %><% } %>
    • + <% }); %> + <% } %> +
    +