editor: show comment tip next to blocks containing comments
[fnpeditor.git] / src / editor / modules / documentCanvas / canvas / genericElement.js
1 define(function(require) {
2     
3 'use strict';
4
5 var $ = require('libs/jquery'),
6     _ = require('libs/underscore'),
7     documentElement = require('./documentElement'),
8     utils = require('./utils'),
9     wlxmlUtils = require('utils/wlxml');
10
11 var labelWidget = function(tag, klass) {
12     return $('<span>')
13         .addClass('canvas-widget canvas-widget-label')
14         .text(wlxmlUtils.getTagLabel(tag) + (klass ? ' / ' + wlxmlUtils.getClassLabel(klass) : ''));
15 };
16 void(labelWidget); // for linters; labelWidget is unused on purpose for now
17
18 var DocumentNodeElement = documentElement.DocumentNodeElement;
19
20 var generic = Object.create(DocumentNodeElement.prototype);
21
22 $.extend(generic, {
23     init: function() {
24         DocumentNodeElement.prototype.init.call(this);
25         this._container()
26             .attr('wlxml-tag', this.wlxmlNode.getTagName());
27         this.setWlxmlClass(this.wlxmlNode.getClass());
28         this.wlxmlNode.contents().forEach(function(node) {
29             var el = this.canvas.createElement(node);
30             if(el.dom) {
31                 this._container().append(el.dom);
32             }
33         }.bind(this));
34
35         this.commentTip = $('<div class="comment-tip"><i class="icon-comment"></i></div>');
36         this.addWidget(this.commentTip);
37
38         if(!this.wlxmlNode.hasChild({klass: 'comment'})) {
39             this.commentTip.hide();
40         }
41
42         this.refresh();
43     },
44     
45     refresh: function() {
46         if(this.wlxmlNode.getTagName() === 'span') {
47             if(this.containsBlock()) {
48                 this.displayAsBlock();
49             } else {
50                 this.displayInline();
51             }
52         } else {
53             this.displayAsBlock();
54         }
55     },
56
57     getFirst: function(e1, e2) {
58         var idx1 = this.childIndex(e1),
59             idx2 = this.childIndex(e2);
60         if(e1 === null || e2 === null) {
61             return undefined;
62         }
63         return idx1 <= idx2 ? e1: e2;
64     },
65
66     children: function() {
67         var element = this,
68             toret = [];
69         this._container().contents().each(function() {
70             var childElement = element.canvas.getDocumentElement(this);
71             if(childElement === undefined) {
72                 return true;
73             }
74
75             toret.push(childElement);
76         });
77         return toret;
78     },
79
80     getVerticallyFirstTextElement: function() {
81         var toret;
82         this.children().some(function(child) {
83             if(child instanceof documentElement.DocumentTextElement) {
84                 toret = child;
85                 return true; // break
86             } else {
87                 toret = child.getVerticallyFirstTextElement();
88                 if(toret) {
89                     return true; // break
90                 }
91             }
92         });
93         return toret;
94     },
95
96     onNodeAttrChange: function(event) {
97         if(event.meta.attr === 'class') {
98             this.setWlxmlClass(event.meta.newVal); //
99         }
100     },
101     onNodeAdded: function(event) {
102         if(event.meta.node.isRoot()) {
103             this.canvas.reloadRoot();
104             return;
105         }
106
107         var ptr = event.meta.node.prev(),
108             referenceElement, referenceAction, actionArg;
109             
110         while(ptr && !(referenceElement = utils.getElementForElementRootNode(ptr))) {
111             ptr = ptr.prev();
112         }
113
114         if(referenceElement) {
115             referenceAction = 'after';
116         } else {
117             referenceElement = this;
118             referenceAction = 'prepend';
119         }
120       
121         if(event.meta.move) {
122             /* Let's check if this node had its own canvas element and it's accessible. */
123             actionArg = utils.getElementForElementRootNode(event.meta.node);
124         }
125         if(!actionArg) {
126             actionArg = event.meta.node;
127         }
128
129         referenceElement[referenceAction](actionArg);
130
131         if(event.meta.node.is('comment')) {
132             this.commentTip.show();
133         }
134     },
135     onNodeDetached: function(event) {
136         var isComment = event.meta.node.is('comment');
137         if(event.meta.node.sameNode(this)) {
138             this.detach();
139         } else {
140             this.children().some(function(child) {
141                 if(child.wlxmlNode.sameNode(event.meta.node)) {
142                     child.detach();
143                     return true;
144                 }
145             });
146             if(isComment && !this.wlxmlNode.hasChild({klass: 'comment'})) {
147                 this.commentTip.hide();
148             }
149         }
150     },
151     onNodeTextChange: function(event) {
152         var toSet = event.meta.node.getText();
153         this.children().some(function(child) {
154             if(child.wlxmlNode.sameNode(event.meta.node)) {
155                 if(toSet === '') {
156                     toSet = utils.unicode.ZWS;
157                 }
158                 if(toSet !== child.getText()) {
159                     child.setText(toSet);
160                 }
161                 return true;
162             }
163         });
164     },
165
166     onStateChange: function(changes) {
167         if(_.isBoolean(changes.exposed) && !this.isSpan()) {
168             this._container().toggleClass('highlighted-element', changes.exposed);
169         }
170         if(_.isBoolean(changes.active) && !this.isSpan()) {
171             this._container().toggleClass('current-node-element', changes.active);
172         }
173     },
174
175     ///
176
177     isSpan: function() {
178         return this.wlxmlNode.getTagName() === 'span';
179     },
180     
181     containsBlock: function() {
182         return this.children()
183             .filter(function(child) {
184                 return child instanceof documentElement.DocumentNodeElement;
185             })
186             .some(function(child) {
187                 if(child.isBlock()) {
188                     return true;
189                 } else {
190                     return child.containsBlock();
191                 }
192             });
193     },
194
195     prepend: function(param) {
196         var element;
197         if(param instanceof documentElement.DocumentElement) {
198             element = param;
199         } else {
200             element = this.canvas.createElement(param);
201         }
202         if(element.dom) {
203             this._container().prepend(element.dom);
204             this.refreshPath();
205         }
206         return element;
207     },
208
209     childIndex: function(child) {
210         var children = this.children(),
211             toret = null;
212         children.forEach(function(c, idx) {
213             if(c.sameNode(child)) {
214                 toret = idx;
215                 return false;
216             }
217         });
218         return toret;
219     },
220
221     getWlxmlClass: function() {
222         var klass = this._container().attr('wlxml-class');
223         if(klass) {
224             return klass.replace(/-/g, '.');
225         }
226         return undefined;
227     },
228     setWlxmlClass: function(klass) {
229         if(klass === this.getWlxmlClass()) {
230             return;
231         }
232         if(klass) {
233             this._container().attr('wlxml-class', klass.replace(/\./g, '-'));
234         }
235         else {
236             this._container().removeAttr('wlxml-class');
237         }
238         this.refreshPath();
239     }
240 });
241
242
243 return generic;
244
245
246
247 });