Fix: Removing list with nested lists properly
[fnpeditor.git] / modules / documentCanvas / canvas.js
1 define([
2 'libs/jquery-1.9.1.min',
3 'libs/underscore-min',
4 'modules/documentCanvas/transformations',
5 'modules/documentCanvas/canvasNode',
6 'libs/text!./template.html'
7 ], function($, _, transformations, canvasNode, template) {
8
9 'use strict';
10
11 var Canvas = function(html) {
12     this.dom = $(template);
13     this.content = this.dom.find('#rng-module-documentCanvas-content');
14     this.setHTML(html);
15 };
16
17 Canvas.prototype.setHTML = function(html) {
18     if(html) {
19         this.content.html(html);
20     }
21 };
22
23 Canvas.prototype.getContent = function() {
24     return this.content.contents();
25 };
26
27 Canvas.prototype.findNodes = function(desc) {
28     var selector = '';
29     if(typeof desc === 'string') {
30         selector = desc;
31     }
32     else {
33         if(desc.klass)
34             selector += '[wlxml-class=' + desc.klass + ']';
35         if(desc.tag)
36             selector += '[wlxml-tag=' + desc.tag + ']';
37     }
38     var toret = [];
39     this.content.find(selector).each(function() {
40         toret.push(canvasNode.create($(this)));
41     });
42     return toret;
43 };
44
45 Canvas.prototype.getNodeById = function(id) {
46     return canvasNode.create($(this.content.find('#' +id)));
47 };
48
49 Canvas.prototype.nodeAppend = function(options) {
50     var element; // = $(this.content.find('#' + options.context.id).get(0));
51     if(options.to === 'root') {
52         element = this.content;
53     } else {
54         element = $(this.content.find('#' + options.to.getId()).get(0));
55     }
56     element.append(options.node.dom);
57 };
58
59 Canvas.prototype.nodeInsertAfter = function(options) {
60     var element = $(this.content.find('#' + options.after.getId()).get(0));
61     element.after(options.node.dom);
62 };
63
64 Canvas.prototype.nodeWrap = function(options) {
65     options = _.extend({textNodeIdx: 0}, options);
66     if(typeof options.textNodeIdx === 'number')
67         options.textNodeIdx = [options.textNodeIdx];
68     
69     var container = $(this.content.find('#' + options.inside.getId()).get(0)),
70         containerContent = container.contents(),
71         idx1 = Math.min.apply(Math, options.textNodeIdx),
72         idx2 = Math.max.apply(Math, options.textNodeIdx),
73         textNode1 = $(containerContent.get(idx1)),
74         textNode2 = $(containerContent.get(idx2)),
75         sameNode = textNode1.get(0) === textNode2.get(0),
76         prefixOutside = textNode1.text().substr(0, options.offsetStart),
77         prefixInside = textNode1.text().substr(options.offsetStart),
78         suffixInside = textNode2.text().substr(0, options.offsetEnd),
79         suffixOutside = textNode2.text().substr(options.offsetEnd)
80     ;
81     
82     textNode1.after(options._with.dom);
83     textNode1.detach();
84     
85     options._with.dom.before(prefixOutside);
86     if(sameNode) {
87         var core = textNode1.text().substr(options.offsetStart, options.offsetEnd - options.offsetStart);
88         options._with.setContent(core);
89     } else {
90         textNode2.detach();
91         options._with.dom.append(prefixInside);
92         for(var i = idx1 + 1; i < idx2; i++) {
93             options._with.dom.append(containerContent[i]);
94         }
95         options._with.dom.append(suffixInside);
96     }
97     options._with.dom.after(suffixOutside);
98 };
99
100 Canvas.prototype.nodeSplit = function(options) {
101     options = _.extend({textNodeIdx: 0}, options);
102     
103     var nodeToSplit = $(this.content.find('#' + options.node.getId()).get(0));
104     
105     var nodeContents = nodeToSplit.contents();
106     if(nodeContents.length === 0 || 
107        nodeContents.length - 1 < options.textNodeIdx || 
108        nodeContents.get(options.textNodeIdx).nodeType != 3)
109         return false;
110     
111     var textNode = $(nodeContents.get(options.textNodeIdx));
112
113     var succeedingNodes = [];
114     var passed = false;
115     nodeContents.each(function() {
116         var node = this;
117         if(passed)
118             succeedingNodes.push(node);
119         if(node === textNode.get(0))
120             passed = true;
121     });
122     
123     var prefix = $.trim(textNode.text().substr(0, options.offset));
124     var suffix = $.trim(textNode.text().substr(options.offset));
125     
126     textNode.before(prefix);
127     textNode.remove();
128     
129     var newNode = canvasNode.create({tag: nodeToSplit.attr('wlxml-tag'), klass: nodeToSplit.attr('wlxml-class')});
130     newNode.dom.append(suffix);
131     succeedingNodes.forEach(function(node) {
132         newNode.dom.append(node);
133     });
134     nodeToSplit.after(newNode.dom);
135     return newNode;
136 };
137
138 Canvas.prototype.nodeRemove = function(options) {
139     var toRemove = $(this.content.find('#' + options.node.getId()).get(0));
140     toRemove.remove();
141 };
142
143 Canvas.prototype.listCreate = function(options) {
144     var element1 = $(this.content.find('#' + options.start.getId()).get(0));
145     var element2 = $(this.content.find('#' + options.end.getId()).get(0));
146     if(element1.parent().get(0) !== element2.parent().get(0))
147         return false;
148         
149     var parent = element1.parent();
150     
151     if(parent.contents().index(element1) > parent.contents().index(element2)) {
152         var tmp = element1;
153         element1 = element2;
154         element2 = tmp;
155     }
156     
157     var nodesToWrap = [];
158     
159     var place = 'before';
160     var canvas = this;
161     parent.contents().each(function() {
162         var node = this;
163         if(node === element1.get(0))
164             place = 'inside';
165         if(place === 'inside') {
166             var $node;
167             if(node.nodeType === 3) {
168                 $node = canvasNode.create({tag: 'div', content: $.trim(node.data)}).dom; //canvas._createNode('div').text(node.data);
169                 $(node).remove();
170             }
171             else {
172                 $node = $(node);
173             }
174             $node.attr('wlxml-class', 'item');
175             nodesToWrap.push($node);
176         }
177         if(node === element2.get(0))
178             return false;
179     });
180     
181     var list = canvasNode.create({tag: 'div', klass: 'list-items' + (options.type === 'enum' ? '-enum' : '')}).dom; //this._createNode('div', 'list-items');
182     
183     var parentNode = options.start.parent();
184     
185     var toret;
186     if(parentNode && parentNode.isOfClass('list-items')) {
187         list.wrap('<div wlxml-tag="div" wlxml-class="item" class="canvas-silent-item">');
188         toret = list.parent();
189     } else {
190         toret = list;
191     }
192         
193     
194     element1.before(toret);
195     
196     nodesToWrap.forEach(function(node) {
197         node.remove();
198         list.append(node);
199     });
200 };
201
202 Canvas.prototype.listRemove = function(options) {
203     var pointerElement = $(this.content.find('#' + options.pointer.getId()));
204     var listElement = options.pointer.getClass() === 'list-items' ? pointerElement : 
205         pointerElement.parents('[wlxml-class|="list-items"][wlxml-tag]');
206     
207     var nested = false,
208         nestedLists;
209     if(listElement.length > 1) {
210         listElement = $(listElement[0]);
211         nested = true;
212     }
213     
214     if(nested) {
215         // We are only moving one level up
216         listElement.unwrap();
217     } else {
218         // We are removing the whole list
219         nestedLists = listElement.find('[wlxml-class=item] > [wlxml-class|=list-items]');
220         nestedLists.unwrap();
221         listElement.find('[wlxml-class=item]').each(function() {
222             $(this).removeAttr('wlxml-class');
223         });
224     }
225
226     listElement.children().unwrap();
227
228     var c = this;
229     if(nestedLists) {
230         nestedLists.each(function() {
231             c.listRemove({pointer: canvasNode.create($(this))});
232         });
233     }
234 };
235
236 Canvas.prototype.getPrecedingNode = function(options) {
237     var element = $(this.content.find('#' + options.node.getId()).get(0));
238     var prev = element.prev();
239     if(prev.length === 0)
240         prev = element.parent();
241     return canvasNode.create(prev);
242 };
243
244 Canvas.prototype.nodeInsideList = function(options) {
245     if(options.node) {
246         if(options.node.isOfClass('list-items') || options.node.isOfClass('item'))
247             return true;
248         var pointerElement = $(this.content.find('#' + options.node.getId()));
249         return pointerElement.parents('[wlxml-class=list-items], [wlxml-class=item]').length > 0;
250     }
251     return false;
252 };
253
254
255 return {
256     create: function(desc) { return new Canvas(desc); }
257 };
258
259 });