Merge in gutter comments
authorAleksander Łukasz <aleksander.lukasz@nowoczesnapolska.org.pl>
Thu, 29 May 2014 07:38:58 +0000 (09:38 +0200)
committerAleksander Łukasz <aleksander.lukasz@nowoczesnapolska.org.pl>
Thu, 29 May 2014 07:38:58 +0000 (09:38 +0200)
40 files changed:
src/editor/modules/documentCanvas/canvas/canvas.html [new file with mode: 0644]
src/editor/modules/documentCanvas/canvas/canvas.js
src/editor/modules/documentCanvas/canvas/canvas.less [new file with mode: 0644]
src/editor/modules/documentCanvas/canvas/canvas.test.js
src/editor/modules/documentCanvas/canvas/comments/comment.html [new file with mode: 0644]
src/editor/modules/documentCanvas/canvas/comments/comments.html [new file with mode: 0644]
src/editor/modules/documentCanvas/canvas/comments/comments.js [new file with mode: 0644]
src/editor/modules/documentCanvas/canvas/comments/comments.less [new file with mode: 0644]
src/editor/modules/documentCanvas/canvas/documentElement.js
src/editor/modules/documentCanvas/canvas/elementsRegister.js
src/editor/modules/documentCanvas/canvas/genericElement.js
src/editor/modules/documentCanvas/canvas/genericElement.less
src/editor/modules/documentCanvas/canvas/gutter.js [new file with mode: 0644]
src/editor/modules/documentCanvas/canvas/gutter.less [new file with mode: 0644]
src/editor/modules/documentCanvas/canvas/gutterBox.html [new file with mode: 0644]
src/editor/modules/documentCanvas/canvas/nullElement.js [new file with mode: 0644]
src/editor/modules/documentCanvas/canvas/utils.js
src/editor/modules/documentCanvas/canvas/wlxmlListener.js
src/editor/modules/documentCanvas/documentCanvas.js
src/editor/modules/documentCanvas/documentCanvas.less
src/editor/modules/documentCanvas/template.html
src/editor/modules/mainBar/mainBar.js
src/editor/modules/mainBar/mainBar.less
src/editor/modules/mainBar/template.html
src/editor/modules/rng/documentSummary.html
src/editor/modules/rng/documentSummary.js
src/editor/modules/rng/editingLayout.html
src/editor/modules/rng/editingLayout.less
src/editor/modules/rng/mainLayout.less
src/editor/modules/rng/rng.js
src/editor/plugins/core/canvasElements.js
src/editor/plugins/core/core.js
src/editor/plugins/core/links/linkElement.js
src/editor/styles/mixins.less
src/smartxml/core.js
src/smartxml/smartxml.js
src/smartxml/smartxml.test.js
src/wlxml/extensions/comments/comments.js [new file with mode: 0644]
src/wlxml/wlxml.js
src/wlxml/wlxml.test.js

diff --git a/src/editor/modules/documentCanvas/canvas/canvas.html b/src/editor/modules/documentCanvas/canvas/canvas.html
new file mode 100644 (file)
index 0000000..d798fd2
--- /dev/null
@@ -0,0 +1,5 @@
+<div class="view-table">
+    <div class="view-row">
+        <div class="root-wrapper" contenteditable="true"></div>
+    </div>
+</div>
\ No newline at end of file
index a057e05..05c109c 100644 (file)
@@ -9,7 +9,10 @@ define([
 'modules/documentCanvas/canvas/wlxmlListener',
 'modules/documentCanvas/canvas/elementsRegister',
 'modules/documentCanvas/canvas/genericElement',
-], function($, _, Backbone, logging, documentElement, keyboard, utils, wlxmlListener, ElementsRegister, genericElement) {
+'modules/documentCanvas/canvas/nullElement',
+'modules/documentCanvas/canvas/gutter',
+'libs/text!./canvas.html'
+], function($, _, Backbone, logging, documentElement, keyboard, utils, wlxmlListener, ElementsRegister, genericElement, nullElement, gutter, canvasTemplate) {
     
 'use strict';
 /* global document:false, window:false, Node:false, gettext */
@@ -57,8 +60,9 @@ $.extend(TextHandler.prototype, {
 });
 
 
-var Canvas = function(wlxmlDocument, elements) {
-    this.elementsRegister = new ElementsRegister(documentElement.DocumentNodeElement);
+var Canvas = function(wlxmlDocument, elements, metadata) {
+    this.metadata = metadata || {};
+    this.elementsRegister = new ElementsRegister(documentElement.DocumentNodeElement, nullElement);
 
     elements = [
         {tag: 'section', klass: null, prototype: genericElement},
@@ -72,7 +76,15 @@ var Canvas = function(wlxmlDocument, elements) {
         this.elementsRegister.register(elementDesc);
     }.bind(this));
     this.eventBus = _.extend({}, Backbone.Events);
-    this.wrapper = $('<div>').addClass('canvas-wrapper').attr('contenteditable', true);
+    
+    this.dom = $(canvasTemplate);
+    this.rootWrapper = this.dom.find('.root-wrapper');
+    
+
+    this.gutter = gutter.create();
+    this.gutterView = new gutter.GutterView(this.gutter);
+    this.dom.find('.view-row').append(this.gutterView.dom);
+    
     this.wlxmlListener = wlxmlListener.create(this);
     this.loadWlxmlDocument(wlxmlDocument);
     this.setupEventHandling();
@@ -81,6 +93,10 @@ var Canvas = function(wlxmlDocument, elements) {
 
 $.extend(Canvas.prototype, Backbone.Events, {
 
+    getElementOffset: function(element) {
+        return element.dom.offset().top - this.dom.offset().top;
+    },
+
     loadWlxmlDocument: function(wlxmlDocument) {
         if(!wlxmlDocument) {
             return false;
@@ -122,28 +138,30 @@ $.extend(Canvas.prototype, Backbone.Events, {
     },
 
     reloadRoot: function() {
+        if(this.rootElement) {
+            this.rootElement.detach();
+        }
         this.rootElement = this.createElement(this.wlxmlDocument.root);
-        this.wrapper.empty();
-        this.wrapper.append(this.rootElement.dom);
+        this.rootWrapper.append(this.rootElement.dom);
     },
 
     setupEventHandling: function() {
         var canvas = this;
 
-        this.wrapper.on('keyup keydown keypress', function(e) {
+        this.rootWrapper.on('keyup keydown keypress', function(e) {
             keyboard.handleKey(e, canvas);
         });
 
-        this.wrapper.on('mouseup', function() {
+        this.rootWrapper.on('mouseup', function() {
             canvas.triggerSelectionChanged();
         });
 
         var mouseDown;
-        this.wrapper.on('mousedown', '[document-node-element], [document-text-element]', function(e) {
+        this.rootWrapper.on('mousedown', '[document-node-element], [document-text-element]', function(e) {
             mouseDown = e.target;
         });
 
-        this.wrapper.on('click', '[document-node-element], [document-text-element]', function(e) {
+        this.rootWrapper.on('click', '[document-node-element], [document-text-element]', function(e) {
             e.stopPropagation();
             if(e.originalEvent.detail === 3) {
                 e.preventDefault();
@@ -155,7 +173,7 @@ $.extend(Canvas.prototype, Backbone.Events, {
             }
         });
 
-        this.wrapper.on('paste', function(e) {
+        this.rootWrapper.on('paste', function(e) {
             e.preventDefault();
 
             var clipboardData = e.originalEvent.clipboardData;
@@ -191,7 +209,7 @@ $.extend(Canvas.prototype, Backbone.Events, {
                         mutation.target.data = mutation.target.data.replace(utils.unicode.ZWS, '');
                         canvas._moveCaretToTextElement(canvas.getDocumentElement(mutation.target), 'end');
                     }
-                    observer.observe(canvas.wrapper[0], config);
+                    observer.observe(canvas.dom[0], config);
 
                     var textElement = canvas.getDocumentElement(mutation.target),
                         toSet = mutation.target.data !== utils.unicode.ZWS ? mutation.target.data : '';
@@ -205,11 +223,15 @@ $.extend(Canvas.prototype, Backbone.Events, {
             });
         });
         var config = { attributes: false, childList: false, characterData: true, subtree: true, characterDataOldValue: true};
-        observer.observe(this.wrapper[0], config);
+        observer.observe(this.rootWrapper[0], config);
 
 
-        this.wrapper.on('mouseover', '[document-node-element], [document-text-element]', function(e) {
-            var el = canvas.getDocumentElement(e.currentTarget);
+        var hoverHandler = function(e) {
+            var el = canvas.getDocumentElement(e.currentTarget),
+                expose = {
+                    mouseover: true,
+                    mouseout: false
+                };
             if(!el) {
                 return;
             }
@@ -217,19 +239,11 @@ $.extend(Canvas.prototype, Backbone.Events, {
             if(el instanceof documentElement.DocumentTextElement) {
                 el = el.parent();
             }
-            el.toggleLabel(true);
-        });
-        this.wrapper.on('mouseout', '[document-node-element], [document-text-element]', function(e) {
-            var el = canvas.getDocumentElement(e.currentTarget);
-            if(!el) {
-                return;
-            }
-            e.stopPropagation();
-            if(el instanceof documentElement.DocumentTextElement) {
-                el = el.parent();
-            }
-            el.toggleLabel(false);
-        });
+            el.updateState({exposed:expose[e.type]});
+        };
+
+        this.rootWrapper.on('mouseover', '[document-node-element], [document-text-element]', hoverHandler);
+        this.rootWrapper.on('mouseout', '[document-node-element], [document-text-element]', hoverHandler);
 
         this.eventBus.on('elementToggled', function(toggle, element) {
             if(!toggle) {
@@ -239,7 +253,7 @@ $.extend(Canvas.prototype, Backbone.Events, {
     },
 
     view: function() {
-        return this.wrapper;
+        return this.dom;
     },
 
     doc: function() {
@@ -248,7 +262,7 @@ $.extend(Canvas.prototype, Backbone.Events, {
 
     toggleElementHighlight: function(node, toggle) {
         var element = utils.getElementForNode(node);
-        element.toggleHighlight(toggle);
+        element.updateState({exposed: toggle});
     },
 
     getCursor: function() {
@@ -257,14 +271,11 @@ $.extend(Canvas.prototype, Backbone.Events, {
 
     
     getCurrentNodeElement: function() {
-        var htmlElement = this.wrapper.find('.current-node-element').parent()[0];
-        if(htmlElement) {
-            return this.getDocumentElement(htmlElement);
-        }
+        return this.currentNodeElement;
     },
 
     getCurrentTextElement: function() {
-        var htmlElement = this.wrapper.find('.current-text-element')[0];
+        var htmlElement = this.rootWrapper.find('.current-text-element')[0];
         if(htmlElement) {
             return this.getDocumentElement(htmlElement);
         }
@@ -285,7 +296,7 @@ $.extend(Canvas.prototype, Backbone.Events, {
     },
 
     contains: function(element) {
-        return element.dom.parents().index(this.wrapper) !== -1;
+        return element && element.dom && element.dom.parents().index(this.rootWrapper) !== -1;
     },
 
     triggerSelectionChanged: function() {
@@ -293,15 +304,10 @@ $.extend(Canvas.prototype, Backbone.Events, {
         var s = this.getSelection(),
             f = s.toDocumentFragment();
         if(f && f instanceof f.RangeFragment) {
-                var $current = this.wrapper.find('.current-node-element');
-                var current = $current && this.getDocumentElement($current.parent()[0]);
-                
-                if($current) {
-                    $current.removeClass('current-node-element');
-                }
-                if(current) {
-                    current.markAsCurrent(false);
-                }
+            if(this.currentNodeElement) {
+                this.currentNodeElement.updateState({active: false});
+                this.currentNodeElement = null;
+            }
         }
     },
 
@@ -347,18 +353,14 @@ $.extend(Canvas.prototype, Backbone.Events, {
         }.bind(this);
         var _markAsCurrent = function(element) {
             if(element instanceof documentElement.DocumentTextElement) {
-                this.wrapper.find('.current-text-element').removeClass('current-text-element');
+                this.rootWrapper.find('.current-text-element').removeClass('current-text-element');
                 element.dom.addClass('current-text-element');
             } else {
-                var $current = this.wrapper.find('.current-node-element');
-                var current = this.getDocumentElement($current.parent()[0]);
-                $current.removeClass('current-node-element');
-
-                if(current) {
-                    current.markAsCurrent(false);
+                if(this.currentNodeElement) {
+                    this.currentNodeElement.updateState({active: false});
                 }
-                element._container().addClass('current-node-element');
-                element.markAsCurrent(true);
+                element.updateState({active: true});
+                this.currentNodeElement = element;
             }
         }.bind(this);
 
@@ -370,7 +372,7 @@ $.extend(Canvas.prototype, Backbone.Events, {
             currentNodeElement = this.getCurrentNodeElement();
 
         if(currentTextElement && !(currentTextElement.sameNode(textElementToLand))) {
-            this.wrapper.find('.current-text-element').removeClass('current-text-element');
+            this.rootWrapper.find('.current-text-element').removeClass('current-text-element');
         }
 
         if(textElementToLand) {
@@ -409,7 +411,7 @@ $.extend(Canvas.prototype, Backbone.Events, {
 
         selection.removeAllRanges();
         selection.addRange(range);
-        this.wrapper.focus(); // FF requires this for caret to be put where range colllapses, Chrome doesn't.
+        this.rootWrapper.focus(); // FF requires this for caret to be put where range colllapses, Chrome doesn't.
     },
 
     setCursorPosition: function(position) {
@@ -419,11 +421,11 @@ $.extend(Canvas.prototype, Backbone.Events, {
     },
 
     toggleGrid: function() {
-        this.wrapper.toggleClass('grid-on');
+        this.rootWrapper.toggleClass('grid-on');
         this.trigger('changed');
     },
     isGridToggled: function() {
-        return this.wrapper.hasClass('grid-on');
+        return this.rootWrapper.hasClass('grid-on');
     }
 });
 
@@ -615,8 +617,8 @@ $.extend(Cursor.prototype, {
 });
 
 return {
-    fromXMLDocument: function(wlxmlDocument, elements) {
-        return new Canvas(wlxmlDocument, elements);
+    fromXMLDocument: function(wlxmlDocument, elements, metadata) {
+        return new Canvas(wlxmlDocument, elements, metadata);
     }
 };
 
diff --git a/src/editor/modules/documentCanvas/canvas/canvas.less b/src/editor/modules/documentCanvas/canvas/canvas.less
new file mode 100644 (file)
index 0000000..2c6edc2
--- /dev/null
@@ -0,0 +1,9 @@
+.view-table {
+    display: table;
+    width: calc(~'100% - 100px');
+    margin: 10px 0 20px 100px;
+
+    .view-row {
+        display: table-row;
+    }
+}
index 0654170..be4a8b9 100644 (file)
@@ -293,7 +293,11 @@ describe('Custom elements based on wlxml class attribute', function() {
                 onNodeAdded: function(event) {
                     void(event);
                     this.refresh2();
-                }
+                },
+                onNodeTextChange: function(event) {
+                    this.header.text(event.meta.node.getText());
+                },
+                children: function() { return []; }
         });
 
         var c = getCanvasFromXML('<section><div class="testClass"><a></a></div></section>', [
@@ -309,6 +313,14 @@ describe('Custom elements based on wlxml class attribute', function() {
         node.append({tagName: 'div'});
 
         expect(header.text()).to.equal('2', 'added div');
+
+        var textNode = node.append({text: 'test'});
+
+        expect(header.text()).to.equal('3', 'added text node');
+        
+        textNode.setText('test2');
+
+        expect(header.text()).to.equal('test2', 'text node change handled');
     });
 
     describe('Handling unknown class', function() {
diff --git a/src/editor/modules/documentCanvas/canvas/comments/comment.html b/src/editor/modules/documentCanvas/canvas/comments/comment.html
new file mode 100644 (file)
index 0000000..3e9fb28
--- /dev/null
@@ -0,0 +1,29 @@
+<div class="comment">
+    <div class="header">
+        <span class="author"><%= author %></span>
+        <span class="date"><%= date %></span>
+    </div>
+    <div style="clear: both"></div>
+    <div class="content">
+        <%= content %>
+    </div>
+    <div class="edit">
+        <textarea rows="1"></textarea>
+        <div>
+            <button class="btn btn-info btn-mini edit-save-btn" disabled><%= gettext('Save') %></button>
+            <button class="btn btn-mini edit-cancel-btn"><%= gettext('Cancel') %></button>
+            <div style="clear:both;"></div>
+        </div>
+    </div>
+    <div class="toolbox">
+        <a href="#" class="edit-start-btn"><%= gettext('edit') %></a> -
+        <a href="#" class="remove-btn"><%= gettext('remove') %></a>
+    </div>
+    <div class="deleteDialog">
+        <div><%= gettext('Delete this comment?') %></div>
+        <div>
+            <button class="btn btn-mini deleteDialog-confirm"><%= gettext('Delete') %></button>
+            <button class="btn btn-mini deleteDialog-cancel"><%= gettext('Cancel') %></button>
+        </div>
+    </div>
+</div>
diff --git a/src/editor/modules/documentCanvas/canvas/comments/comments.html b/src/editor/modules/documentCanvas/canvas/comments/comments.html
new file mode 100644 (file)
index 0000000..7ca0760
--- /dev/null
@@ -0,0 +1,9 @@
+<div class="comments">
+    <div class="list"></div>
+    <div class="newComment">
+        <textarea rows="1"></textarea>
+        <button class="btn btn-info btn-mini btnAdd" disabled><%= gettext('Comment') %></button>
+        <button class="btn btn-mini btnCancel"><%= gettext('Cancel') %></button>
+        <div style="clear:both;"></div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/src/editor/modules/documentCanvas/canvas/comments/comments.js b/src/editor/modules/documentCanvas/canvas/comments/comments.js
new file mode 100644 (file)
index 0000000..3076f52
--- /dev/null
@@ -0,0 +1,219 @@
+define(function(require) {
+    
+'use strict';
+/* globals gettext */
+
+
+var $ = require('libs/jquery'),
+    _ = require('libs/underscore'),
+    datetime = require('fnpjs/datetime'),
+    commentsTemplate = require('libs/text!./comments.html'),
+    commentTemplate = require('libs/text!./comment.html');
+
+
+var makeAutoResizable = function(textarea) {
+    textarea.on('input', function() {
+        resize(textarea);
+    });
+};
+
+var resize = function(textarea) {
+    if(textarea.prop('scrollHeight') > textarea.prop('clientHeight')) {
+        textarea.height(textarea.prop('scrollHeight'));
+    }
+};
+
+
+var CommentsView = function(node, user) {
+    this.node = node;
+    this.user = user;
+    this.dom = $(_.template(commentsTemplate)());
+    this.list = this.dom.find('.list');
+    this.textarea = this.dom.find('textarea');
+    this.addButton = this.dom.find('button.btnAdd');
+    this.cancelButton = this.dom.find('button.btnCancel');
+
+    this.textarea.on('input', function() {
+        this.addButton.attr('disabled', this.textarea.val() === '');
+    }.bind(this));
+    makeAutoResizable(this.textarea);
+
+    this.addButton.hide();
+    this.cancelButton.hide();
+    this.textarea.on('focus', function() {
+        this.addButton.show();
+        this.cancelButton.show();
+    }.bind(this));
+
+    this.addButton.on('click', function() {
+        if(!this.node) {
+            return;
+        }
+
+        this.node.document.transaction(function() {
+            var commentNode = this.node.document.createDocumentNode({tagName: 'aside', attrs: {'class': 'comment'}}),
+                metadata = commentNode.getMetadata(),
+                creator;
+
+            if(this.user) {
+                creator = this.user.name;
+                if(this.user.email) {
+                    creator += ' (' + this.user.email + ')';
+                }
+            } else {
+                creator = 'anonymous';
+            }
+
+            metadata.add({key: 'creator', value: creator});
+            metadata.add({key: 'date', value: datetime.currentStrfmt()});
+            commentNode.append({text: this.textarea.val()});
+
+            this.node.append(commentNode);
+        }.bind(this), {
+            metadata: {
+                description: gettext('Add comment')
+            },
+            success: function() {
+                this.textarea.val('');
+            }.bind(this)
+        });
+
+    }.bind(this));
+
+    this.cancelButton.on('click', function() {
+        this.addButton.hide();
+        this.cancelButton.hide();
+        this.textarea.val('');
+    }.bind(this));
+
+    this.render();
+    this.onDeactivated();
+
+};
+
+_.extend(CommentsView.prototype, {
+    render: function() {
+        this.list.empty();
+        this.textarea.attr('placeholder', gettext('Comment'));
+
+        this.node.contents()
+            .filter(function(child) {
+                return child.is({tag: 'aside', klass: 'comment'});
+            })
+            .forEach(function(commentNode) {
+                var commentView = new CommentView(commentNode);
+                this.list.append(commentView.dom);
+                this.textarea.attr('placeholder', gettext('Respond') + '...');
+            }.bind(this));
+    },
+    onActivated: function() {
+            this.dom.find('.newComment').toggle(true);
+    },
+    onDeactivated: function() {
+      this.dom.find('.newComment').toggle(false);
+      this.addButton.hide();
+      this.cancelButton.hide();
+    },
+});
+
+
+var CommentView = function(commentNode) {
+    this.node = commentNode;
+
+    var metaData = this.node.getMetadata(),
+        author, date;
+
+    metaData.some(function(row) {
+        author = row.getValue();
+        if(author) {
+            author = author.split(' ')[0];
+        }
+        return true;
+    }, 'creator');
+    
+    metaData.some(function(row) {
+        date = row.getValue();
+        if(/[0-9][0-9]:[0-9][0-9]:[0-9][0-9]$/g.test(date)) {
+            date = date.split(':');
+            date.pop();
+            date = date.join(':');
+        }
+        return true;
+    }, 'date');
+
+    this.dom = $(_.template(commentTemplate)({
+        author: author ||'?',
+        date: date || '?',
+        content: this.node.object.getText() || '?'
+    }));
+
+    this.contentElement = this.dom.find('.content');
+    this.editElement = this.dom.find('.edit');
+    this.deleteDialogElement = this.dom.find('.deleteDialog');
+
+    this.dom.find('.remove-btn').on('click', function() {
+        this.deleteDialogElement.show();
+    }.bind(this));
+
+    this.dom.find('.deleteDialog-confirm').on('click', function() {
+        this.node.document.transaction(function() {
+            this.node.detach();
+        }.bind(this), {
+            metadata: {
+                description: gettext('Remove comment')
+            }
+        });
+    }.bind(this));
+
+    this.dom.find('.deleteDialog-cancel').on('click', function() {
+        this.deleteDialogElement.hide();
+    }.bind(this));
+
+    this.dom.find('.edit-start-btn').on('click', function() {
+        this.startEditing();
+    }.bind(this));
+
+    this.dom.find('.edit-save-btn').on('click', function() {
+        this.saveEditing();
+    }.bind(this));
+
+    this.dom.find('.edit-cancel-btn').on('click', function() {
+        this.cancelEditing();
+    }.bind(this));
+
+    this.textarea = this.editElement.find('textarea');
+    this.textarea.on('input', function() {
+        this.dom.find('.edit-save-btn').attr('disabled', this.textarea.val() === '');
+    }.bind(this));
+    makeAutoResizable(this.textarea);
+};
+
+$.extend(CommentView.prototype, {
+    startEditing: function() {
+        this.contentElement.hide();
+        this.editElement.show();
+        this.textarea.val(this.node.object.getText());
+        resize(this.textarea);
+        this.textarea.focus();
+    },
+    saveEditing: function() {
+        var newContent = this.editElement.find('textarea').val();
+        this.node.document.transaction(function() {
+            this.node.object.setText(newContent);
+        }.bind(this), {
+            metadata: {
+                description: gettext('Edit comment')
+            }
+        });
+    },
+    cancelEditing: function() {
+        this.contentElement.show();
+        this.editElement.find('textarea').val('');
+        this.editElement.hide();
+    },
+});
+
+
+return CommentsView;
+
+});
\ No newline at end of file
diff --git a/src/editor/modules/documentCanvas/canvas/comments/comments.less b/src/editor/modules/documentCanvas/canvas/comments/comments.less
new file mode 100644 (file)
index 0000000..688d3f8
--- /dev/null
@@ -0,0 +1,69 @@
+.comments {
+    @borderColor: darken(#FFFCB7, 45%);
+
+    font-size: 12px;
+
+    .comment {
+        position: relative;
+        
+        border-color: @borderColor;
+        border-style: solid;
+        border-width: 1px 0;
+        margin-top: -1px;
+        padding-bottom: 10px;
+
+        &:first-child {
+            margin-top:0;
+        }
+
+        .header {
+            padding: 0 3px;
+            font-size: 10px;
+            
+            .author {
+                
+            }
+            .date {
+                float: right;
+            }
+        }
+
+        .content {
+            padding: 5px;
+        }
+
+        .edit {
+            display: none;
+        }
+
+        .deleteDialog {
+            display: none;
+            position: absolute;
+            top: 0;
+            bottom: 0;
+            left: 0;
+            right: 0;
+            padding-top: 15px;
+            color: white;
+            background-color: rgba(0,0,0,0.7);
+            text-align: center;
+        }
+    }
+
+    .newComment {
+        margin-top: 10px;
+    }
+
+    textarea {
+        display: block;
+        width: calc(~'100% - 4px');
+        padding: 2px 2px;
+        margin: 0 0 10px 0;
+        font-size: 12px;
+        resize: vertical;
+        outline: none !important;
+        box-shadow: none;
+
+    }
+
+}
\ No newline at end of file
index 45592ef..d40ca9b 100644 (file)
@@ -11,6 +11,10 @@ define([
 var DocumentElement = function(wlxmlNode, canvas) {
     this.wlxmlNode = wlxmlNode;
     this.canvas = canvas;
+    this.state = {
+        exposed: false,
+        active: false
+    };
 
     this.dom = this.createDOM();
     this.dom.data('canvas-element', this);
@@ -26,6 +30,32 @@ $.extend(DocumentElement.prototype, {
     refresh: function() {
         // noop
     },
+    updateState: function(toUpdate) {
+        var changes = {};
+        _.keys(toUpdate)
+            .filter(function(key) {
+                return this.state.hasOwnProperty(key);
+            }.bind(this))
+            .forEach(function(key) {
+                if(this.state !== toUpdate[key]) {
+                    this.state[key] = changes[key] = toUpdate[key];
+                }
+            }.bind(this));
+        if(_.isFunction(this.onStateChange)) {
+            this.onStateChange(changes);
+            if(_.isBoolean(changes.active)) {
+                if(changes.active) {
+                    var ptr = this;
+                    while(ptr && ptr.wlxmlNode.getTagName() === 'span') {
+                        ptr = ptr.parent();
+                    }
+                    if(ptr && ptr.gutterGroup) {
+                        ptr.gutterGroup.show();
+                    }
+                }
+            }
+        }
+    },
     parent: function() {
         var parents = this.dom.parents('[document-node-element]');
         if(parents.length) {
@@ -71,8 +101,10 @@ var manipulate = function(e, params, action) {
     } else {
         element = e.canvas.createElement(params);
     }
-    e.dom[action](element.dom);
-    e.refreshPath();
+    if(element.dom) {
+        e.dom[action](element.dom);
+        e.refreshPath();
+    }
     return element;
 };
 
@@ -88,6 +120,16 @@ $.extend(DocumentNodeElement.prototype, {
     clearWidgets: function() {
         this.dom.children('.canvas-widgets').empty();
     },
+    addToGutter: function(view) {
+        if(!this.gutterGroup) {
+            this.gutterGroup = this.canvas.gutter.createViewGroup({
+                offsetHint: function() {
+                    return this.canvas.getElementOffset(this);
+                }.bind(this)
+            }, this);
+        }
+        this.gutterGroup.addView(view);
+    },
     handle: function(event) {
         var method = 'on' + event.type[0].toUpperCase() + event.type.substr(1);
         if(this[method]) {
@@ -109,13 +151,26 @@ $.extend(DocumentNodeElement.prototype, {
     _container: function() {
         return this.dom.children('[document-element-content]');
     },
-    detach: function() {
-        var parents = this.parents();
-        this.dom.detach();
-        if(parents[0]) {
-            parents[0].refreshPath();
+    detach: function(isChild) {
+        var parents;
+
+        if(this.gutterGroup) {
+            this.gutterGroup.remove();
         }
-         return this;
+        if(_.isFunction(this.children)) {
+            this.children().forEach(function(child) {
+                child.detach(true);
+            });
+        }
+
+        if(!isChild) {
+            parents = this.parents();
+            this.dom.detach();
+            if(parents[0]) {
+                parents[0].refreshPath();
+            }
+        }
+        return this;
     },
     before: function(params) {
         return manipulate(this, params, 'before');
@@ -125,19 +180,6 @@ $.extend(DocumentNodeElement.prototype, {
         return manipulate(this, params, 'after');
     },
 
-    toggleLabel: function(toggle) {
-        var displayCss = toggle ? 'inline-block' : 'none';
-        var label = this.dom.children('.canvas-widgets').find('.canvas-widget-label');
-        label.css('display', displayCss);
-        this.toggleHighlight(toggle);
-    },
-
-    markAsCurrent: function() {},
-
-    toggleHighlight: function(toggle) {
-        this._container().toggleClass('highlighted-element', toggle);
-    },
-
     isBlock: function() {
         return this.dom.css('display') === 'block';
     },
@@ -185,8 +227,10 @@ $.extend(DocumentTextElement.prototype, {
             .text(this.wlxmlNode.getText() || utils.unicode.ZWS);
         return dom;
     },
-    detach: function() {
-        this.dom.detach();
+    detach: function(isChild) {
+        if(!isChild) {
+            this.dom.detach();
+        }
         return this;
     },
     setText: function(text) {
@@ -197,6 +241,9 @@ $.extend(DocumentTextElement.prototype, {
             this.dom.contents()[0].data = text;
         }
     },
+    handle: function(event) {
+        this.setText(event.meta.node.getText());
+    },
     getText: function(options) {
         options = _.extend({raw: false}, options || {});
         var toret = this.dom.text();
@@ -219,10 +266,12 @@ $.extend(DocumentTextElement.prototype, {
         } else {
             element = this.canvas.createElement(params);
         }
-        this.dom.wrap('<div>');
-        this.dom.parent().after(element.dom);
-        this.dom.unwrap();
-        this.refreshPath();
+        if(element.dom) {
+            this.dom.wrap('<div>');
+            this.dom.parent().after(element.dom);
+            this.dom.unwrap();
+            this.refreshPath();
+        }
         return element;
     },
     before: function(params) {
@@ -235,16 +284,15 @@ $.extend(DocumentTextElement.prototype, {
         } else {
             element = this.canvas.createElement(params);
         }
-        this.dom.wrap('<div>');
-        this.dom.parent().before(element.dom);
-        this.dom.unwrap();
-        this.refreshPath();
+        if(element.dom) {
+            this.dom.wrap('<div>');
+            this.dom.parent().before(element.dom);
+            this.dom.unwrap();
+            this.refreshPath();
+        }
         return element;
     },
 
-    toggleHighlight: function() {
-        // do nothing for now
-    },
     children: function() {
         return [];
     }
index 24a700a..9a44514 100644 (file)
@@ -5,9 +5,10 @@ var _ = require('libs/underscore'),
     wlxml = require('wlxml/wlxml');
 
 
-var ElementsRegister = function(BaseType) {
+var ElementsRegister = function(BaseType, NullType) {
     this._register = {};
     this.BaseType = BaseType;
+    this.NullType = NullType;
 };
 
 _.extend(ElementsRegister.prototype, {
@@ -21,7 +22,7 @@ _.extend(ElementsRegister.prototype, {
     },
     register: function(params) {
         params.klass = params.klass || '';
-        params.prototype = params.prototype || Object.create({});
+        params.prototype = params.prototype || this.NullType;
 
         this._register[params.tag] = this._register[params.tag] || {};
         this._register[params.tag][params.klass] = this.createCanvasElementType(params.prototype);
index 44615f5..878e140 100644 (file)
@@ -3,9 +3,11 @@ define(function(require) {
 'use strict';
 
 var $ = require('libs/jquery'),
+    _ = require('libs/underscore'),
     documentElement = require('./documentElement'),
     utils = require('./utils'),
-    wlxmlUtils = require('utils/wlxml');
+    wlxmlUtils = require('utils/wlxml'),
+    CommentsView = require('./comments/comments');
 
 var labelWidget = function(tag, klass) {
     return $('<span>')
@@ -25,8 +27,21 @@ $.extend(generic, {
             .attr('wlxml-tag', this.wlxmlNode.getTagName());
         this.setWlxmlClass(this.wlxmlNode.getClass());
         this.wlxmlNode.contents().forEach(function(node) {
-            this._container().append(this.canvas.createElement(node).dom);
+            var el = this.canvas.createElement(node);
+            if(el.dom) {
+                this._container().append(el.dom);
+            }
         }.bind(this));
+
+        this.commentsView = new CommentsView(this.wlxmlNode, this.canvas.metadata.user);
+        this.addToGutter(this.commentsView);
+        this.commentTip = $('<div class="comment-tip"><i class="icon-comment"></i></div>');
+        this.addWidget(this.commentTip);
+
+        if(!this.wlxmlNode.hasChild({klass: 'comment'})) {
+            this.commentTip.hide();
+        }
+
         this.refresh();
     },
     
@@ -92,31 +107,37 @@ $.extend(generic, {
             return;
         }
 
-        var nodeIndex = event.meta.node.getIndex(),
+        var ptr = event.meta.node.prev(),
             referenceElement, referenceAction, actionArg;
+            
+        while(ptr && !(referenceElement = utils.getElementForElementRootNode(ptr))) {
+            ptr = ptr.prev();
+        }
 
-        if(nodeIndex === 0) {
+        if(referenceElement) {
+            referenceAction = 'after';
+        } else {
             referenceElement = this;
             referenceAction = 'prepend';
-        } else {
-            referenceElement = this.children()[nodeIndex-1];
-            referenceAction = 'after';
         }
       
         if(event.meta.move) {
             /* Let's check if this node had its own canvas element and it's accessible. */
             actionArg = utils.getElementForElementRootNode(event.meta.node);
-            if(actionArg && actionArg.sameNode(referenceElement)) {
-                referenceElement = this.children()[nodeIndex];
-            }
         }
         if(!actionArg) {
             actionArg = event.meta.node;
         }
 
         referenceElement[referenceAction](actionArg);
+
+        if(event.meta.node.is('comment')) {
+            this.commentTip.show();
+            this.commentsView.render();
+        }
     },
     onNodeDetached: function(event) {
+        var isComment = event.meta.node.is('comment');
         if(event.meta.node.sameNode(this)) {
             this.detach();
         } else {
@@ -126,12 +147,19 @@ $.extend(generic, {
                     return true;
                 }
             });
+            if(isComment && !this.wlxmlNode.hasChild({klass: 'comment'})) {
+                this.commentTip.hide();
+            }
+            this.commentsView.render();
         }
     },
     onNodeTextChange: function(event) {
-        var toSet = event.meta.node.getText();
-        this.children().some(function(child) {
-            if(child.wlxmlNode.sameNode(event.meta.node)) {
+        var node = event.meta.node,
+            toSet = node.getText(),
+            handled;
+        
+        handled = this.children().some(function(child) {
+            if(child.wlxmlNode.sameNode(node)) {
                 if(toSet === '') {
                     toSet = utils.unicode.ZWS;
                 }
@@ -141,10 +169,26 @@ $.extend(generic, {
                 return true;
             }
         });
+
+        if(!handled && node.parent() && node.parent().is('comment') && this.wlxmlNode.sameNode(node.parent().parent())) {
+            this.commentsView.render();
+        }
     },
 
+    onStateChange: function(changes) {
+        if(_.isBoolean(changes.exposed) && !this.isSpan()) {
+            this._container().toggleClass('highlighted-element', changes.exposed);
+        }
+        if(_.isBoolean(changes.active) && !this.isSpan()) {
+            this._container().toggleClass('current-node-element', changes.active);
+        }
+    },
 
     ///
+
+    isSpan: function() {
+        return this.wlxmlNode.getTagName() === 'span';
+    },
     
     containsBlock: function() {
         return this.children()
@@ -167,8 +211,10 @@ $.extend(generic, {
         } else {
             element = this.canvas.createElement(param);
         }
-        this._container().prepend(element.dom);
-        this.refreshPath();
+        if(element.dom) {
+            this._container().prepend(element.dom);
+            this.refreshPath();
+        }
         return element;
     },
 
index c935a79..dcc23f4 100644 (file)
     font-family: monospace;
     z-index:9999;
     white-space: nowrap;
+}
+
+.comment-tip {
+    position:absolute;
+    left:-15px;
+    top:3px;
+    opacity: 0.25;
+}
+
+.current-node-element > .canvas-widgets > .comment-tip {
+    opacity: 1;
 }
\ No newline at end of file
diff --git a/src/editor/modules/documentCanvas/canvas/gutter.js b/src/editor/modules/documentCanvas/canvas/gutter.js
new file mode 100644 (file)
index 0000000..472998a
--- /dev/null
@@ -0,0 +1,117 @@
+define(function(require) {
+    
+'use strict';
+
+var $ = require('libs/jquery'),
+    _ = require('libs/underscore'),
+    Backbone = require('libs/backbone'),
+    gutterBoxTemplate = require('libs/text!./gutterBox.html');
+
+
+var GutterView = function(gutter) {
+    gutter.on('show', function(group) {
+        if(this.groupView) {
+            this.groupView.remove();
+        }
+        this.groupView = new GutterGroupView(this, group);
+        this.dom.append(this.groupView.dom);
+        this.groupView.dom.css({top: group.getOffsetHint()});
+        this.groupView.show();
+    }, this);
+    this.dom = $('<div class="gutter"></div>');
+};
+
+
+var GutterGroupView = function(gutterView, group) {
+    this.gutterView = gutterView;
+    this.group = group;
+    this.views  = [];
+    
+    this.dom = $(gutterBoxTemplate);
+
+    this.dom.on('click', function() {
+        if(!this.dom.hasClass('focused')) {
+            var canvas = this.group.meta.canvas;
+            canvas.setCurrentElement(this.group.meta);
+        }
+    }.bind(this));
+    
+    this.group.views.forEach(function(view) {
+        this.onViewAdded(view);
+    }.bind(this));
+    
+    this.group.on('viewAdded', this.onViewAdded, this);
+    this.group.on('focusToggled', this.onFocusToggled, this);
+    this.group.on('removed', this.remove, this);
+};
+$.extend(GutterGroupView.prototype, {
+    remove: function() {
+        this.group.off('viewAdded', this.onViewAdded);
+        this.group.off('focusToggled', this.onFocusToggled);
+        this.group.off('removed', this.removed);
+        this.dom.detach();
+    },
+    onViewAdded: function(view) {
+        this.views.push(view);
+        this.dom.append(view.dom);
+    },
+    show: function() {
+        this.dom.addClass('focused');
+        this.views.forEach(function(view) {
+            if(view.onActivated) {
+                view.onActivated();
+            }
+        });
+    }
+});
+
+
+
+/// model
+
+var ViewGroup = function(params, gutter, meta) {
+    this.gutter = gutter;
+    this.params = params;
+    this.meta = meta;
+    this.view = $(gutterBoxTemplate);
+    this.views = [];
+};
+$.extend(ViewGroup.prototype, Backbone.Events, {
+    getOffsetHint: function() {
+        return _.isFunction(this.params.offsetHint) ? this.params.offsetHint() : this.params.offsetHint;
+    },
+    addView: function(view) {
+        this.views.push(view);
+        this.trigger('viewAdded', view);
+    },
+    show: function() {
+        this.gutter.show(this);
+    },
+    remove: function() {
+        this.trigger('removed');
+    }
+});
+
+
+var Gutter = function() {
+};
+
+_.extend(Gutter.prototype, Backbone.Events, {
+    createViewGroup: function(params, meta) {
+        return new ViewGroup(params, this, meta);
+    },
+    show: function(group) {
+        this.trigger('show', group);
+    },
+});
+
+
+return {
+    create: function() {
+        return new Gutter();
+    },
+    GutterView: GutterView,
+    GutterGroupView: GutterGroupView
+};
+
+});
\ No newline at end of file
diff --git a/src/editor/modules/documentCanvas/canvas/gutter.less b/src/editor/modules/documentCanvas/canvas/gutter.less
new file mode 100644 (file)
index 0000000..17178c7
--- /dev/null
@@ -0,0 +1,14 @@
+.gutter {
+    display: table-cell;
+    width: calc(~'100% - 800px');
+    min-width: 250px;
+    padding: 0 25px;
+}
+
+.gutter-box {
+    border: 1px solid darken(#ddd, 10%);
+    padding: 5px 10px;
+    position: relative;
+    background-color: darken(#FFFCB7, 15%);
+}
+
diff --git a/src/editor/modules/documentCanvas/canvas/gutterBox.html b/src/editor/modules/documentCanvas/canvas/gutterBox.html
new file mode 100644 (file)
index 0000000..7f53e72
--- /dev/null
@@ -0,0 +1 @@
+<div class="gutter-box"></div>
diff --git a/src/editor/modules/documentCanvas/canvas/nullElement.js b/src/editor/modules/documentCanvas/canvas/nullElement.js
new file mode 100644 (file)
index 0000000..b57a62a
--- /dev/null
@@ -0,0 +1,16 @@
+define(function(require) {
+    
+'use strict';
+var documentElement = require('./documentElement');
+
+
+var NullElement = Object.create(documentElement.DocumentNodeElement.prototype);
+
+NullElement.init = function() {
+    this.dom = null;
+    this.wlxmlNode.setData('canvasElement', undefined);
+};
+
+return NullElement;
+
+});
\ No newline at end of file
index 1df3f82..4e262a0 100644 (file)
@@ -58,7 +58,7 @@ var _getElementForTextNode = function(textNode, withParent) {
             return true;
         }
     });
-    return toret;
+    return toret || parentElement;
 };
 
 var getElementForDetachedNode = function(node, originalParent) {
index 02dc657..d107612 100644 (file)
@@ -75,7 +75,7 @@ var handlers = {
     },
     nodeTextChange: function(event) {
         var element = utils.getElementForNode(event.meta.node);
-        element.setText(event.meta.node.getText());
+        element.handle(event);
     },
 
     metadataChanged: _metadataEventHandler,
index 8742aef..7e70f7f 100644 (file)
@@ -20,7 +20,9 @@ return function(sandbox) {
         canvasElements = canvasElements.concat(plugin.canvasElements || []);
     });
 
-    var canvas = canvas3.fromXMLDocument(null, canvasElements);
+    var canvas = canvas3.fromXMLDocument(null, canvasElements, {
+        user: sandbox.getConfig().user
+    });
     var canvasWrapper = $(template);
     var shownAlready = false;
     var scrollbarPosition = 0,
@@ -70,7 +72,7 @@ return function(sandbox) {
         },
         setDocument: function(wlxmlDocument) {
             canvas.loadWlxmlDocument(wlxmlDocument);
-            canvasWrapper.find('#rng-module-documentCanvas-content').empty().append(canvas.view());
+            canvasWrapper.find('#rng-module-documentCanvas-contentWrapper').empty().append(canvas.view());
         },
         highlightElement: function(node) {
             canvas.toggleElementHighlight(node, true);
index bd39b6a..879d78d 100644 (file)
@@ -1,26 +1,45 @@
 @import 'nodes.less';
+@import 'canvas/canvas.less';
 @import 'canvas/documentElement.less';
 @import 'canvas/genericElement.less';
+@import 'canvas/gutter.less';
+@import 'canvas/comments/comments.less';
 
 #rng-module-documentCanvas {
    height: 100%;
 }
 
-#rng-module-documentCanvas-mainArea {
-   height: 100%;
-   margin-bottom: 20px;
-}
-
 #rng-module-documentCanvas-contentWrapper {
-    border-color: #ddd;
-    border-style: solid;
-    border-width: 1px;
+    background-color: #ddd;
     float:left;
     width: 100%;
     height: 100%;
     overflow-y: scroll;
-    padding: 0 10px;
     
+    &:before {
+        content: "";
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: -15px;
+        right: -15px;
+        box-shadow: inset 0 20px 12px -20px rgba(0,0,0,0.6);
+        pointer-events:none;
+        z-index: 1;
+    }
+
+    &:after {
+        content: "";
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: -15px;
+        right: -15px;
+        box-shadow: inset 0px -20px 12px -20px rgba(0,0,0,0.6);
+        pointer-events:none;
+        z-index: 1;
+    }
+
     &::-webkit-scrollbar {
         .rng-mixin-scrollbar;
     }
         .rng-mixin-scrollbar-thumb-window-inactive;
     }
     
-    .canvas-wrapper {
+    .root-wrapper {
+        display: table-cell;
+        vertical-align: top;
+        width: 600px;
         outline: 0px solid transparent;
+        padding: 0 100px;
+        background-color: white;
+        box-shadow: 0 0 20px rgba(0, 0, 0, 0.2), inset 0 0 10px rgba(0, 0, 0, 0.1);
     }
 
     .current-text-element {
index 63dc331..7a32c16 100644 (file)
@@ -1,7 +1,4 @@
 <div id="rng-module-documentCanvas">
-    <div id="rng-module-documentCanvas-mainArea">
-        <div id="rng-module-documentCanvas-contentWrapper">
-            <div id="rng-module-documentCanvas-content"></div>
-        </div>
+    <div id="rng-module-documentCanvas-contentWrapper">
     </div>
 </div>
\ No newline at end of file
index 256111f..e5cf522 100644 (file)
@@ -40,6 +40,9 @@ return function(sandbox) {
                 trigger.data('originalContent', trigger.html());
                 trigger.text(disabledText);
             }
+        },
+        setSummaryView: function(summaryView) {
+            view.find('.bottom').prepend(summaryView);
         }
     };
 
index 8fb99a5..7a2e49b 100644 (file)
         border-style: solid;
         margin: 0 5px 0 0;
         padding: 0 5px 0 0;
-        &:last-child {
-            margin-right: 0;
-            padding-right: 0;
-        }
+
     }
 
     ul {
         list-style-type: none;
+        display: inline-block;
+        color: #8F8A8A;
+        margin: 0;
+        &:last-child {
+            li:last-child {
+                margin-right: 0;
+                padding-right: 0;
+            }
+        }
     }
 
     .top {
index 5320525..59f0708 100644 (file)
@@ -3,8 +3,10 @@
         <%= userName %> (<a href="/"><%= gettext('Exit') %></a>)
     </div>
     <div style="clear:both;"></div>
-    <ul class="bottom">
-        <li><a href="#" data-cmd="drop-draft" data-disabled-text="<%= gettext('no draft exists') %>"><%= gettext('drop a working draft') %></a></li>
-        <li><button class="btn btn-mini btn-info" data-cmd="save"><%= gettext('Save') %></button></li>
-    </ul>
+    <div class="bottom">
+        <ul>
+            <li><a href="#" data-cmd="drop-draft" data-disabled-text="<%= gettext('no draft exists') %>"><%= gettext('drop a working draft') %></a></li>
+            <li><button class="btn btn-mini btn-info" data-cmd="save"><%= gettext('Save') %></button></li>
+        </ul>
+    </div>
 </div>
\ No newline at end of file
index 95c7552..8d5fcbd 100644 (file)
@@ -1,14 +1,6 @@
-<div>
-    <h1 class="title"><%= title %></h1>
-    <table>
-        <% properties.forEach(function(propertyDesc) { %>
-            <tr>
-                <td><%= propertyDesc.label %></td>
-                <td><%= propertyValues[propertyDesc.name] %></td>
-        <% }); %>
-        <tr>
-            <td><%= gettext('Draft Saved') %></td>
-            <td class="draft"></td>
-        </tr>
-    </table>
-</div>
\ No newline at end of file
+<li><%= gettext('Draft Saved') %>: <span class="draft"></span></li>
+<% properties.forEach(function(propertyDesc) { %>
+    <li>
+        <%= propertyDesc.label %>: <%= propertyValues[propertyDesc.name] %>
+    </li>
+<% }); %>
index fe593c3..8e6927d 100644 (file)
@@ -8,7 +8,7 @@ var $ = require('libs/jquery'),
 
 
 var view = {
-    dom: $('<div class="documentSummary"></div>'),
+    dom: $('<ul></ul>'),
     init: function(config, doc) {
         this.config = config;
         this.doc = doc;
index 54a29aa..9fb37e6 100644 (file)
@@ -1,7 +1,4 @@
 <div class="fnp-module-rng-editingLayout">
     <div fnpjs-place="toolbar"></div>
     <div class="rng-module-rng2-left" fnpjs-place="leftColumn"></div>
-    <div class="rng-module-rng2-right" fnpjs-place="rightColumn"></div>
-    <div style="clear:both;"></div>
-    <div class="rng-module-rng2-statusBar" fnpjs-place="statusBar"></div>
 </div>
\ No newline at end of file
index 0f5254a..fe46e6d 100644 (file)
@@ -3,54 +3,6 @@
     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;
@@ -58,6 +10,8 @@
 
 .fnp-module-rng-editingLayout {
 
+    margin-left: 60px;
+
     [fnpjs-place="statusBar"] {
         position: absolute;
         bottom: 0;
         
     }
     
-    [fnpjs-place="leftColumn"], [fnpjs-place="rightColumn"] {
+    [fnpjs-place="leftColumn"] {
         position: absolute;
         top: 30px; //
-        bottom: 50px; //
-    }
-    
-    [fnpjs-place="leftColumn"] {
+        bottom: 0px; //
         left:0;
-        right: 360px;
-    }
-    
-    [fnpjs-place="rightColumn"] {
-        right: 0px;
-        width: 290px;
-        
+        right: 0;
     }
-
-
 }
\ No newline at end of file
index c1a7e8e..859b581 100644 (file)
@@ -2,8 +2,8 @@
     position: fixed;
     top: 5px;
     bottom: 5px;
-    left: 80px;
-    right: 80px;
+    left: 0px;
+    right: 0px;
     
     [fnpjs-place="messages"] {
         position: absolute;
@@ -16,6 +16,7 @@
         float: right;
         position: relative;
         z-index: 2;
+        margin-right: 60px;
     }
     
     [fnpjs-place="mainView"] {
@@ -29,6 +30,7 @@
         > .rng-view-tabs {
             position: relative;
             height: 100%;
+            padding-left: 60px;
 
             > .rng-view-tabs-content {
                 position: absolute;
index 3274067..032fea4 100644 (file)
@@ -2,13 +2,12 @@ define([
 './documentSummary',
 'libs/underscore',
 'fnpjs/layout',
-'fnpjs/vbox',
 'fnpjs/logging/logging',
 'views/tabs/tabs',
 'libs/text!./mainLayout.html',
 'libs/text!./editingLayout.html',
 'libs/text!./diffLayout.html',
-], function(documentSummary, _, layout, vbox, logging, tabs, mainLayoutTemplate, visualEditingLayoutTemplate, diffLayoutTemplate) {
+], function(documentSummary, _, layout, logging, tabs, mainLayoutTemplate, visualEditingLayoutTemplate, diffLayoutTemplate) {
 
 'use strict';
 
@@ -35,10 +34,8 @@ return function(sandbox) {
             if(fragment && fragment.node) {
                 elementParent = fragment.node.getNearestElementNode();
                 sandbox.getModule('nodeBreadCrumbs').setNodeElement(elementParent);
-                sandbox.getModule('metadataEditor').setNodeElement(elementParent);
             } else {
                 sandbox.getModule('nodeBreadCrumbs').setNodeElement(null);
-                sandbox.getModule('metadataEditor').setNodeElement(null);
             }
         },
     };
@@ -48,20 +45,15 @@ return function(sandbox) {
         mainLayout: new layout.Layout(mainLayoutTemplate),
         mainTabs: (new tabs.View()).render(),
         visualEditing: new layout.Layout(visualEditingLayoutTemplate),
-        visualEditingSidebar: (new tabs.View({stacked: true})).render(),
-        currentNodePaneLayout: new vbox.VBox(),
         diffLayout: new layout.Layout(diffLayoutTemplate)
     };
     
-    views.visualEditing.setView('rightColumn', views.visualEditingSidebar.getAsView());
     addMainTab(gettext('Editor'), 'editor', views.visualEditing.getAsView());
     addMainTab(gettext('Source'), 'sourceEditor',  '');
     addMainTab(gettext('History'), 'history', views.diffLayout.getAsView());
     
     sandbox.getDOM().append(views.mainLayout.getAsView());
     
-    views.visualEditingSidebar.addTab({icon: 'pencil'}, 'edit', views.currentNodePaneLayout.getAsView());
-
     var wlxmlDocument, documentIsDirty;
     
     /* Events handling */
@@ -84,12 +76,12 @@ return function(sandbox) {
             documentSummary.init(sandbox.getConfig().documentSummaryView, wlxmlDocument);
             documentSummary.render();
             documentSummary.setDraftField(usingDraft ? (draftTimestamp || '???') : '-');
-            views.currentNodePaneLayout.appendView(documentSummary.dom);
+            sandbox.getModule('mainBar').setSummaryView(documentSummary.dom);
 
             sandbox.getModule('mainBar').setCommandEnabled('drop-draft', usingDraft);
             sandbox.getModule('mainBar').setCommandEnabled('save', usingDraft);
 
-            _.each(['sourceEditor', 'documentCanvas', 'documentToolbar', 'metadataEditor', 'nodeBreadCrumbs', 'mainBar', 'indicator', 'documentHistory', 'diffViewer', 'statusBar'], function(moduleName) {
+            _.each(['sourceEditor', 'documentCanvas', 'documentToolbar', 'mainBar', 'indicator', 'documentHistory', 'diffViewer', 'statusBar'], function(moduleName) {
                 sandbox.getModule(moduleName).start();
             });
             
@@ -195,13 +187,6 @@ return function(sandbox) {
         }
     };
     
-    eventHandlers.metadataEditor = {
-        ready: function() {
-            sandbox.getModule('metadataEditor').setDocument(sandbox.getModule('data').getDocument());
-            views.visualEditingSidebar.addTab({icon: 'info-sign'}, 'metadataEditor', sandbox.getModule('metadataEditor').getView());
-        }
-    };
-    
     eventHandlers.documentToolbar = {
         ready: function() {
             views.visualEditing.setView('toolbar', sandbox.getModule('documentToolbar').getView());
index 229cd47..0d7cffa 100644 (file)
@@ -111,7 +111,7 @@ $.extend(footnote, {
 
 
 return [
-    {tag: 'aside', klass: 'comment', prototype: comment},
+    {tag: 'aside', klass: 'comment', prototype: null},
     {tag: 'aside', klass: 'footnote', prototype: footnote},
     {tag: 'span', klass: 'link', prototype: linkElement}
 ];
index bbc27ac..c12405b 100644 (file)
@@ -26,6 +26,14 @@ plugin.documentExtension.textNode.transformations = {
                     return true; // break
                 }
             });
+            newNodes.second.contents()
+                .filter(function(child) {
+                    return child.object.describesParent;
+                })
+                .forEach(function(child) {
+                    //child.detach();
+                    newNodes.first.append(child);
+                });
             return _.extend(newNodes, {emptyText: emptyText});
         },
         getChangeRoot: function() {
index ceefee1..61d2abc 100644 (file)
@@ -26,8 +26,11 @@ _.extend(linkElement, {
         this.box.hide();
         this.addWidget(this.box);
     },
-    markAsCurrent: function(toggle) {
-        this.box.toggle(toggle);
+    onStateChange: function(changes) {
+        genericElement.onStateChange.call(this, changes);
+        if(_.isBoolean(changes.active)) {
+            this.box.toggle(changes.active);
+        }
     },
     onNodeAttrChange: function(event) {
         if(event.meta.attr === 'href') {
index 3a14f31..28effc8 100644 (file)
@@ -1,5 +1,5 @@
 .rng-mixin-scrollbar {
-    width: 9px;
+    width: 12px;
 }
 
 .rng-mixin-scrollbar-track {
index 007468f..fdce751 100644 (file)
@@ -187,15 +187,21 @@ var elementNodeTransformations = {
             return;
         }
 
+        this.contents()
+            .filter(function(child) {
+                return child.getProperty('describesParent');
+            }.bind(this))
+            .forEach(function(child) {
+                child.detach();
+            });
+
         var myContents = this.contents(),
             myIdx = parent.indexOf(this);
 
-
         if(myContents.length === 0) {
             return this.detach();
         }
 
-
         var childrenLength = this.contents().length,
             first = true,
             shiftRange = false;
@@ -381,7 +387,9 @@ var documentTransformations = {
         }
 
         for(var i = idx1; i <= idx2; i++) {
-            wrapper.append(parentContents[i].detach());
+            if(!parentContents[i].getProperty('describesParent')) {
+                wrapper.append(parentContents[i].detach());
+            }
         }
 
         insertingTarget[insertingMethod](wrapper);
@@ -426,7 +434,9 @@ var documentTransformations = {
                 wrapperElement.append({text: prefixInside});
             }
             for(var i = idx1 + 1; i < idx2; i++) {
-                wrapperElement.append(contentsInside[i]);
+                if(!contentsInside[i].getProperty('describesParent')) {
+                    wrapperElement.append(contentsInside[i]);
+                }
             }
             if(suffixInside.length > 0) {
                 wrapperElement.append({text: suffixInside});
index cfae7c7..8039916 100644 (file)
@@ -17,12 +17,21 @@ var DocumentNode = function(nativeNode, document) {
         throw new Error('undefined document for a node');
     }
     this.document = document;
+    this.object = {};
     this._setNativeNode(nativeNode);
 
 };
 
 $.extend(DocumentNode.prototype, {
 
+    getProperty: function(propName) {
+        var toret = this.object[propName];
+        if(toret && _.isFunction(toret)) {
+            toret = toret.call(this);
+        }
+        return toret;
+    },
+
     transform: function(Transformation, args) {
         var transformation = new Transformation(this.document, this, args);
         return this.document.transform(transformation);
index 5430278..b7676e2 100644 (file)
@@ -578,6 +578,24 @@ describe('smartxml', function() {
             expect(node.contents()[2].getText()).to.equal(' a cat!');
         });
 
+        it('removes parent-describing sibling nodes of unwrapped node', function() {
+            var doc = getDocumentFromXML('<root><div><a></a><x></x><a></a></div></root>'),
+                div = doc.root.contents()[0],
+                x = div.contents()[1];
+
+            doc.registerExtension({documentNode: {methods: {
+                object: {
+                    describesParent: function() {
+                        return this.getTagName() === 'x';
+                    }
+                }
+            }}});
+
+            div.unwrapContent();
+            expect(doc.root.contents().length).to.equal(2);
+            expect(x.isInDocument()).to.be.false;
+        });
+
         it('unwrap single element node from its parent', function() {
             var doc = getDocumentFromXML('<div><a><b></b></a></div>'),
                 div = doc.root,
@@ -630,6 +648,28 @@ describe('smartxml', function() {
                 expect(wrapperContents[1].contents().length).to.equal(1);
                 expect(wrapperContents[1].contents()[0].getText()).to.equal('small');
             });
+
+            it('keeps parent-describing nodes in place', function() {
+                var doc = getDocumentFromXML('<root>Alice<x></x> has a cat</root>'),
+                    root = doc.root,
+                    x = root.contents()[1];
+
+                doc.registerExtension({documentNode: {methods: {
+                    object: {
+                        describesParent: function() {
+                            return this.getTagName() === 'x';
+                        }
+                    }
+                }}});
+
+                root.wrapText({
+                    _with: {tagName: 'span', attrs: {'attr1': 'value1'}},
+                    offsetStart: 1,
+                    offsetEnd: 4,
+                    textNodeIdx: [0,2]
+                });
+                expect(x.parent().sameNode(root)).to.be.true;
+            });
         });
 
         describe('Wrapping Nodes', function() {
@@ -678,6 +718,29 @@ describe('smartxml', function() {
                 expect(headerChildren[0].sameNode(div2)).to.equal(true, 'first node wrapped');
                 expect(headerChildren[1].sameNode(div3)).to.equal(true, 'second node wrapped');
             });
+
+            it('keeps parent-describing nodes in place', function() {
+                var section = elementNodeFromXML('<section>Alice<x></x><div>a cat</div></section>'),
+                    aliceText = section.contents()[0],
+                    x = section.contents()[1],
+                    lastDiv = section.contents()[2];
+
+                section.document.registerExtension({documentNode: {methods: {
+                    object: {
+                        describesParent: function() {
+                            return this.getTagName() === 'x';
+                        }
+                    }
+                }}});
+
+                section.document.wrapNodes({
+                        node1: aliceText,
+                        node2: lastDiv,
+                        _with: {tagName: 'header'}
+                    });
+
+                expect(x.parent().sameNode(section)).to.be.true;
+            });
         });
 
     });
diff --git a/src/wlxml/extensions/comments/comments.js b/src/wlxml/extensions/comments/comments.js
new file mode 100644 (file)
index 0000000..521bad8
--- /dev/null
@@ -0,0 +1,36 @@
+define(function() {
+    
+'use strict';
+
+var extension = {wlxmlClass: {comment: {
+    methods: {
+        describesParent: true,
+        getText: function() {
+            var text = '';
+            this.contents()
+                .filter(function(node) {
+                    /* globals Node */
+                    return node && node.nodeType === Node.TEXT_NODE;
+                })
+                .forEach(function(node) {
+                    text = text + node.getText();
+                });
+            return text;
+        },
+        setText: function(text) {
+            var contents = this.contents();
+            if(contents.length === 1 && contents[0].nodeType === Node.TEXT_NODE) {
+                contents[0].setText(text);
+            } else {
+                contents.forEach(function(node) {
+                    node.detach();
+                });
+                this.append({text: text});
+            }
+        }
+    }
+}}};
+
+return extension;
+
+});
\ No newline at end of file
index 0505116..f370223 100644 (file)
@@ -3,8 +3,9 @@ define([
     'libs/underscore',
     'smartxml/smartxml',
     'smartxml/transformations',
-    'wlxml/extensions/metadata/metadata'
-], function($, _, smartxml, transformations, metadataExtension) {
+    'wlxml/extensions/metadata/metadata',
+    'wlxml/extensions/comments/comments'
+], function($, _, smartxml, transformations, metadataExtension, commentExtension) {
     
 'use strict';
 
@@ -58,7 +59,9 @@ var installObject = function(instance, klass) {
     });
     instance.object = Object.create(_.extend({}, methods, transformations));
     _.keys(methods).concat(_.keys(transformations)).forEach(function(key) {
-        instance.object[key] = _.bind(instance.object[key], instance);
+        if(_.isFunction(instance.object[key])) {
+            instance.object[key] = _.bind(instance.object[key], instance);
+        }
     });
 };
 
@@ -88,6 +91,11 @@ $.extend(WLXMLElementNode.prototype, WLXMLDocumentNodeMethods, smartxml.ElementN
         return (_.isUndefined(query.klass) || this.getClass().substr(0, query.klass.length) === query.klass) &&
                (_.isUndefined(query.tagName) || this.getTagName() === query.tagName);
     },
+    hasChild: function(query) {
+        return this.contents().some(function(child) {
+            return child.is(query);
+        }.bind(this));
+    },
     getMetaAttributes: function() {
         var toret = new AttributesList(),
             classParts = [''].concat(this.getClass().split('.')),
@@ -206,12 +214,14 @@ var WLXMLTextNode = function() {
     smartxml.TextNode.apply(this, arguments);
 };
 WLXMLTextNode.prototype = Object.create(smartxml.TextNode.prototype);
-$.extend(WLXMLTextNode.prototype, WLXMLDocumentNodeMethods);
+$.extend(WLXMLTextNode.prototype, WLXMLDocumentNodeMethods, {
+    is: function() { return false; }
+});
 
 var WLXMLDocument = function(xml, options) {
     this.classMethods = {};
     this.classTransformations = {};
-    smartxml.Document.call(this, xml, [metadataExtension]);
+    smartxml.Document.call(this, xml, [metadataExtension, commentExtension]);
     this.options = options;
 };
 
index 9904154..af0e23b 100644 (file)
@@ -286,6 +286,15 @@ describe('WLXMLDocument', function() {
             expect(testClassNode.object.testMethod().sameNode(testClassNode)).to.equal(true, '1');
         });
 
+        it('allows adding non-function properties to an ElementNode of specific class', function() {
+            extension = {wlxmlClass: {test_class: {methods: {
+                testProp: 123
+            }}}};
+            doc.registerExtension(extension);
+            testClassNode = doc.root.contents()[1];
+            expect(testClassNode.object.testProp).to.equal(123);
+        });
+
         it('allows adding transformation to an ElementNode of specific class', function() {
             extension = {wlxmlClass: {test_class: {transformations: {
                 testTransformation: function() { return this; },