wlxml: extending isInside/getParent/is to support tag name
[fnpeditor.git] / src / editor / modules / documentCanvas / commands.js
1 define([
2 './canvas/utils',
3 'views/dialog/dialog',
4 'fnpjs/datetime'
5 ], function(utils, Dialog, datetime) {
6     
7 'use strict';
8 /* globals gettext */
9
10
11 var gridToggled = false;
12
13 var commands = {
14     _cmds: {},
15     register: function(name, command) {
16         this._cmds[name] = command;
17     },
18
19     run: function(name, params, canvas, user) {
20         return this._cmds[name](canvas, params, user);
21     }
22 };
23
24 commands.register('undo', function(canvas) {
25     var doc = canvas.wlxmlDocument;
26
27     doc.undo();
28 });
29
30 commands.register('redo', function(canvas) {
31     var doc = canvas.wlxmlDocument;
32
33     doc.redo();
34 });
35
36 commands.register('remove-node', function(canvas) {
37     canvas.getCurrentNodeElement().wlxmlNode.detach();
38 });
39
40 commands.register('unwrap-node', function(canvas) {
41     var cursor = canvas.getCursor(),
42         selectionStart = cursor.getSelectionStart(),
43         selectionEnd = cursor.getSelectionEnd(),
44         parent1 = selectionStart.element.parent() || undefined,
45         parent2 = selectionEnd.element.parent() || undefined;
46
47     var selectionAnchor = cursor.getSelectionAnchor(),
48         node1 = parent1.wlxmlNode,
49         node2 = parent2.wlxmlNode,
50         doc = node1.document;
51     if(doc.areItemsOfSameList({node1: node1, node2: node2})) {
52         doc.extractItems({item1: node1, item2: node2});
53         canvas.setCurrentElement(selectionAnchor.element, {caretTo: selectionAnchor.offset});
54     } else if(!cursor.isSelecting()) {
55         var nodeToUnwrap = cursor.getPosition().element.wlxmlNode,
56             parentNode = nodeToUnwrap.unwrap();
57         if(parentNode) {
58             canvas.setCurrentElement(utils.findCanvasElement(parentNode));
59         }
60     }
61 });
62
63 commands.register('wrap-node', function(canvas) {
64     var cursor = canvas.getCursor(),
65         selectionStart = cursor.getSelectionStart(),
66         selectionEnd = cursor.getSelectionEnd(),
67         parent1 = selectionStart.element.parent() || undefined,
68         parent2 = selectionEnd.element.parent() || undefined;
69
70     var node1 = parent1.wlxmlNode,
71         node2 = parent2.wlxmlNode,
72         doc = node1.document;
73
74     if(doc.areItemsOfSameList({node1: node1, node2: node2})) {
75         doc.createList({node1: node1, node2: node2});
76     }
77 });
78
79 commands.register('list', function(canvas, params) {
80     void(params);
81     var cursor = canvas.getCursor(),
82         selectionStart = cursor.getSelectionStart(),
83         selectionEnd = cursor.getSelectionEnd(),
84         parent1 = selectionStart.element.parent() || undefined,
85         parent2 = selectionEnd.element.parent() || undefined,
86         selectionFocus = cursor.getSelectionFocus(),
87         node1 = parent1.wlxmlNode,
88         node2 = parent2.wlxmlNode,
89         doc = node1.document;
90     
91     if(cursor.isSelecting()) {
92         doc.transaction(function() {
93             doc.createList({node1: node1, node2: node2, klass: params.meta === 'num' ? 'list.enum' : 'list'});
94         }, {
95             success: function() {
96                 canvas.setCurrentElement(selectionFocus.element, {caretTo: selectionFocus.offset});
97             }
98         });
99     } else {
100         var list;
101         if(node1.isInside('list')) {
102             list = node1.getParent('list');
103             if((params.meta === 'num' && list.getClass() === 'list.enum') || params.meta !== 'num' && list.getClass() === 'list') {
104                 list.object.extractAllItems();
105             } else {
106                 list.setClass(params.meta === 'num' ? 'list.enum' : 'list');
107             }
108         }
109     }
110 });
111
112 commands.register('toggle-grid', function(canvas, params) {
113     canvas.doc().dom().parent().toggleClass('grid-on', params.toggle);
114     gridToggled = params.toggle;
115 });
116
117 commands.register('newNodeRequested', function(canvas, params, user) {
118     var cursor = canvas.getCursor(),
119         selectionStart = cursor.getSelectionStart(),
120         selectionEnd = cursor.getSelectionEnd(),
121         wlxmlNode, caretTo, wrapperCanvasElement;
122
123     var insertNode = function(insertion, callback) {
124         var doc = canvas.wlxmlDocument,
125             metadata, creator, dialog;
126
127         var execCallback = function(node) {
128             if(callback) {
129                 callback(node);
130             }
131         };
132
133         if(params.wlxmlTag === 'aside' && params.wlxmlClass === 'comment') {
134             doc.transaction(function() {
135                 var node = insertion();
136                 if(user) {
137                     creator = user.name;
138                     if(user.email) {
139                         creator += ' (' + user.email + ')';
140                     }
141                 } else {
142                     creator = 'anonymous';
143                 }
144
145                 metadata = node.getMetadata();
146                 metadata.add({key: 'creator', value: creator});
147                 metadata.add({key: 'date', value: datetime.currentStrfmt()});
148                 return node;
149             }, {
150                 success: execCallback
151             });
152         } else if(params.wlxmlClass === 'link') {
153             dialog = Dialog.create({
154                 title: gettext('Create link'),
155                 executeButtonText: gettext('Apply'),
156                 cancelButtonText: gettext('Cancel'),
157                 fields: [
158                     {label: gettext('Link'), name: 'href', type: 'input'}
159                 ]
160             });
161             dialog.on('execute', function(event) {
162                 doc.transaction(function() {
163                     var node = insertion();
164                     node.setAttr('href', event.formData.href);
165                     event.success();
166                     return node;
167                 }, {
168                     success: execCallback
169                 });
170             });
171             dialog.show();
172         } else {
173             doc.transaction(function() {
174                 return insertion();
175             }, {success: execCallback});
176         }
177     };
178
179     if(cursor.isSelecting()) {
180         if(cursor.isSelectingSiblings()) {
181             if(cursor.isSelectingWithinElement()) {
182                 wlxmlNode = selectionStart.element.wlxmlNode;
183                 caretTo = selectionStart.offset < selectionEnd.offset ? 'start' : 'end';
184
185                 insertNode(
186                     function() {
187                         return wlxmlNode.wrapWith({tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}, start: selectionStart.offset, end: selectionEnd.offset});
188                     },
189                     function(wrapper) {
190                         wrapperCanvasElement = utils.findCanvasElement(wrapper);
191                         canvas.setCurrentElement(wrapperCanvasElement.children()[0], {caretTo: caretTo});
192                     }
193                 );
194             }
195             else {
196                 wlxmlNode = selectionStart.element.wlxmlNode.parent();
197                 caretTo = selectionStart.element.sameNode(cursor.getSelectionAnchor().element) ? 'end' : 'start';
198
199                 insertNode(
200                     function() {
201                         return wlxmlNode.wrapText({
202                             _with: {tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}},
203                             offsetStart: selectionStart.offset,
204                             offsetEnd: selectionEnd.offset,
205                             textNodeIdx: [wlxmlNode.indexOf(selectionStart.element.wlxmlNode), wlxmlNode.indexOf(selectionEnd.element.wlxmlNode)] //parent.childIndex(selectionEnd.element)]
206                         });
207                     },
208                     function(wrapper) {
209                         wrapperCanvasElement = utils.findCanvasElement(wrapper);
210                         canvas.setCurrentElement(wrapperCanvasElement.children()[caretTo === 0 ? 0 : wrapperCanvasElement.children().length - 1], {caretTo: caretTo});
211                     }
212                 );
213             }
214         } else {
215             var node1 = selectionStart.element.wlxmlNode,
216                 node2 = selectionEnd.element.wlxmlNode,
217                 siblingParents = canvas.wlxmlDocument.getSiblingParents({node1: node1, node2: node2});
218
219             if(siblingParents) {
220                 insertNode(
221                     function() {
222                         return canvas.wlxmlDocument.wrapNodes({
223                             node1: siblingParents.node1,
224                             node2: siblingParents.node2,
225                             _with: {tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}}
226                         });
227                     }
228                 );
229             }
230         }
231     } else if(canvas.getCurrentNodeElement()) {
232         wlxmlNode = canvas.getCurrentNodeElement().wlxmlNode;
233
234         var linkFound = [wlxmlNode].concat(wlxmlNode.parents()).some(function(node) {
235             if(node.getClass() === 'link') {
236                 var dialog = Dialog.create({
237                     title: gettext('Edit link'),
238                     executeButtonText: gettext('Apply'),
239                     cancelButtonText: gettext('Cancel'),
240                     fields: [
241                         {label: gettext('Link'), name: 'href', type: 'input', initialValue: node.getAttr('href')},
242                     ]
243                 });
244                 dialog.on('execute', function(event) {
245                     canvas.wlxmlDocument.transaction(function() {
246                         node.setAttr('href', event.formData.href);
247                         event.success();
248                     });
249                 });
250                 dialog.show();
251                 return true;
252             }
253         });
254         if(linkFound) {
255             return;
256         }
257
258         if(params.ctrlKey) {
259             insertNode(
260                 function() {
261                     return wlxmlNode.wrapWith({tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}});
262                 },
263                 function(wrapper) {
264                     canvas.setCurrentElement(utils.findCanvasElement(wrapper));
265                 }
266             );
267         } else {
268             insertNode(
269                 function() {
270                     var node = wlxmlNode.after({tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}});
271                     node.append({text:''});
272                     return node;
273                 }, function(wrapper) {
274                     canvas.setCurrentElement(utils.findCanvasElement(wrapper));
275                 }
276             );
277         }
278     }
279 });
280
281 commands.register('footnote', function(canvas, params) {
282     void(params);
283     var cursor = canvas.getCursor(),
284         position = cursor.getPosition(),
285         asideNode, asideElement, node;
286         
287
288     if(cursor.isSelectingWithinElement()) {
289         asideNode = position.element.wlxmlNode.wrapWith({tagName: 'aside', attrs:{'class': 'footnote'}, start: cursor.getSelectionStart().offset, end: cursor.getSelectionEnd().offset});
290     } else {
291         node = position.element.wlxmlNode;
292         node.document.transaction(function() {
293             asideNode = node.divideWithElementNode({tagName: 'aside', attrs:{'class': 'footnote'}}, {offset: position.offset});
294             asideNode.append({text: ''});
295         });
296     }
297
298     asideElement = utils.findCanvasElement(asideNode);
299     asideElement.toggle(true);
300     canvas.setCurrentElement(asideElement);
301 });
302
303 commands.register('take-away-node', function(canvas) {
304     var position = canvas.getCursor().getPosition(),
305         element = position.element,
306         nodeElement = element ? element.parent() : canvas.getCurrentNodeElement();
307
308     if(!nodeElement || !(nodeElement.parent())) {
309         return;
310     }
311
312     var range = nodeElement.wlxmlNode.unwrapContent();
313
314     if(element) {
315         var elementIsFirstChild = nodeElement.childIndex(element);
316         if(element.bound()) {
317             canvas.setCurrentElement(element, {caretTo: position.offset});
318         } else {
319             if(elementIsFirstChild) {
320                 canvas.setCurrentElement(utils.findCanvasElement(range.element1), {caretTo: 'end'});
321             } else {
322                 canvas.setCurrentElement(utils.findCanvasElement(range.element2), {caretTo: 'end'});
323             }
324         }
325     } else {
326         canvas.setCurrentElement(utils.findCanvasElement(range.element1), {caretTo: 'start'});
327     }
328
329 });
330
331
332 return {
333     run: function(name, params, canvas, user) {
334         return commands.run(name, params, canvas, user);
335     }
336 };
337
338 });