smartxml: transaction rollback fix
[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
87     var selectionFocus = cursor.getSelectionFocus();
88
89     if(selectionStart.element.isInsideList() || selectionEnd.element.isInsideList()) {
90         return;
91     }
92
93     var node1 = parent1.wlxmlNode,
94         node2 = parent2.wlxmlNode,
95         doc = node1.document;
96     
97     if(cursor.isSelecting()) {
98         doc.transaction(function() {
99             doc.createList({node1: node1, node2: node2, klass: params.meta === 'num' ? 'list.enum' : 'list'});
100         }, {
101             success: function() {
102                 canvas.setCurrentElement(selectionFocus.element, {caretTo: selectionFocus.offset});
103             }
104         });
105     } else {
106         var list;
107         if(node1.isInside('list')) {
108             list = node1.getParent('list');
109             if((params.meta === 'num' && list.getClass() === 'list.enum') || params.meta !== 'num' && list.getClass() === 'list') {
110                 list.object.extractAllItems();
111             } else {
112                 list.setClass(params.meta === 'num' ? 'list.enum' : 'list');
113             }
114         }
115     }
116 });
117
118 commands.register('toggle-grid', function(canvas, params) {
119     canvas.doc().dom().parent().toggleClass('grid-on', params.toggle);
120     gridToggled = params.toggle;
121 });
122
123 commands.register('newNodeRequested', function(canvas, params, user) {
124     var cursor = canvas.getCursor(),
125         selectionStart = cursor.getSelectionStart(),
126         selectionEnd = cursor.getSelectionEnd(),
127         wlxmlNode, caretTo, wrapperCanvasElement;
128
129     var insertNode = function(insertion, callback) {
130         var doc = canvas.wlxmlDocument,
131             metadata, creator, dialog;
132
133         var execCallback = function(node) {
134             if(callback) {
135                 callback(node);
136             }
137         };
138
139         if(params.wlxmlTag === 'aside' && params.wlxmlClass === 'comment') {
140             doc.transaction(function() {
141                 var node = insertion();
142                 if(user) {
143                     creator = user.name;
144                     if(user.email) {
145                         creator += ' (' + user.email + ')';
146                     }
147                 } else {
148                     creator = 'anonymous';
149                 }
150
151                 metadata = node.getMetadata();
152                 metadata.add({key: 'creator', value: creator});
153                 metadata.add({key: 'date', value: datetime.currentStrfmt()});
154                 return node;
155             }, {
156                 success: execCallback
157             });
158         } else if(params.wlxmlClass === 'link') {
159             dialog = Dialog.create({
160                 title: gettext('Create link'),
161                 executeButtonText: gettext('Apply'),
162                 cancelButtonText: gettext('Cancel'),
163                 fields: [
164                     {label: gettext('Link'), name: 'href', type: 'input'}
165                 ]
166             });
167             dialog.on('execute', function(event) {
168                 doc.transaction(function() {
169                     var node = insertion();
170                     node.setAttr('href', event.formData.href);
171                     event.success();
172                     return node;
173                 }, {
174                     success: execCallback
175                 });
176             });
177             dialog.show();
178         } else {
179             doc.transaction(function() {
180                 return insertion();
181             }, {success: execCallback});
182         }
183     };
184
185     if(cursor.isSelecting()) {
186         if(cursor.isSelectingSiblings()) {
187             if(cursor.isSelectingWithinElement()) {
188                 wlxmlNode = selectionStart.element.wlxmlNode;
189                 caretTo = selectionStart.offset < selectionEnd.offset ? 'start' : 'end';
190
191                 insertNode(
192                     function() {
193                         return wlxmlNode.wrapWith({tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}, start: selectionStart.offset, end: selectionEnd.offset});
194                     },
195                     function(wrapper) {
196                         wrapperCanvasElement = utils.findCanvasElement(wrapper);
197                         canvas.setCurrentElement(wrapperCanvasElement.children()[0], {caretTo: caretTo});
198                     }
199                 );
200             }
201             else {
202                 wlxmlNode = selectionStart.element.wlxmlNode.parent();
203                 caretTo = selectionStart.element.sameNode(cursor.getSelectionAnchor().element) ? 'end' : 'start';
204
205                 insertNode(
206                     function() {
207                         return wlxmlNode.wrapText({
208                             _with: {tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}},
209                             offsetStart: selectionStart.offset,
210                             offsetEnd: selectionEnd.offset,
211                             textNodeIdx: [wlxmlNode.indexOf(selectionStart.element.wlxmlNode), wlxmlNode.indexOf(selectionEnd.element.wlxmlNode)] //parent.childIndex(selectionEnd.element)]
212                         });
213                     },
214                     function(wrapper) {
215                         wrapperCanvasElement = utils.findCanvasElement(wrapper);
216                         canvas.setCurrentElement(wrapperCanvasElement.children()[caretTo === 0 ? 0 : wrapperCanvasElement.children().length - 1], {caretTo: caretTo});
217                     }
218                 );
219             }
220         } else {
221             var node1 = selectionStart.element.wlxmlNode,
222                 node2 = selectionEnd.element.wlxmlNode,
223                 siblingParents = canvas.wlxmlDocument.getSiblingParents({node1: node1, node2: node2});
224
225             if(siblingParents) {
226                 insertNode(
227                     function() {
228                         return canvas.wlxmlDocument.wrapNodes({
229                             node1: siblingParents.node1,
230                             node2: siblingParents.node2,
231                             _with: {tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}}
232                         });
233                     }
234                 );
235             }
236         }
237     } else if(canvas.getCurrentNodeElement()) {
238         wlxmlNode = canvas.getCurrentNodeElement().wlxmlNode;
239
240         var linkFound = [wlxmlNode].concat(wlxmlNode.parents()).some(function(node) {
241             if(node.getClass() === 'link') {
242                 var dialog = Dialog.create({
243                     title: gettext('Edit link'),
244                     executeButtonText: gettext('Apply'),
245                     cancelButtonText: gettext('Cancel'),
246                     fields: [
247                         {label: gettext('Link'), name: 'href', type: 'input', initialValue: node.getAttr('href')},
248                     ]
249                 });
250                 dialog.on('execute', function(event) {
251                     canvas.wlxmlDocument.transaction(function() {
252                         node.setAttr('href', event.formData.href);
253                         event.success();
254                     });
255                     canvas.wlxmlDocument.endTransaction();
256                 });
257                 dialog.show();
258                 return true;
259             }
260         });
261         if(linkFound) {
262             return;
263         }
264
265         if(params.ctrlKey) {
266             insertNode(
267                 function() {
268                     return wlxmlNode.wrapWith({tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}});
269                 },
270                 function(wrapper) {
271                     canvas.setCurrentElement(utils.findCanvasElement(wrapper));
272                 }
273             );
274         } else {
275             insertNode(
276                 function() {
277                     var node = wlxmlNode.after({tagName: params.wlxmlTag, attrs: {'class': params.wlxmlClass}});
278                     node.append({text:''});
279                     return node;
280                 }, function(wrapper) {
281                     canvas.setCurrentElement(utils.findCanvasElement(wrapper));
282                 }
283             );
284         }
285     }
286 });
287
288 commands.register('footnote', function(canvas, params) {
289     void(params);
290     var cursor = canvas.getCursor(),
291         position = cursor.getPosition(),
292         asideNode, asideElement, node;
293         
294
295     if(cursor.isSelectingWithinElement()) {
296         asideNode = position.element.wlxmlNode.wrapWith({tagName: 'aside', attrs:{'class': 'footnote'}, start: cursor.getSelectionStart().offset, end: cursor.getSelectionEnd().offset});
297     } else {
298         node = position.element.wlxmlNode;
299         node.document.transaction(function() {
300             asideNode = node.divideWithElementNode({tagName: 'aside', attrs:{'class': 'footnote'}}, {offset: position.offset});
301             asideNode.append({text: ''});
302         });
303     }
304
305     asideElement = utils.findCanvasElement(asideNode);
306     asideElement.toggle(true);
307     canvas.setCurrentElement(asideElement);
308 });
309
310 commands.register('take-away-node', function(canvas) {
311     var position = canvas.getCursor().getPosition(),
312         element = position.element,
313         nodeElement = element ? element.parent() : canvas.getCurrentNodeElement();
314
315     if(!nodeElement || !(nodeElement.parent())) {
316         return;
317     }
318
319     var range = nodeElement.wlxmlNode.unwrapContent();
320
321     if(element) {
322         var elementIsFirstChild = nodeElement.childIndex(element);
323         if(element.bound()) {
324             canvas.setCurrentElement(element, {caretTo: position.offset});
325         } else {
326             if(elementIsFirstChild) {
327                 canvas.setCurrentElement(utils.findCanvasElement(range.element1), {caretTo: 'end'});
328             } else {
329                 canvas.setCurrentElement(utils.findCanvasElement(range.element2), {caretTo: 'end'});
330             }
331         }
332     } else {
333         canvas.setCurrentElement(utils.findCanvasElement(range.element1), {caretTo: 'start'});
334     }
335
336 });
337
338
339 return {
340     run: function(name, params, canvas, user) {
341         return commands.run(name, params, canvas, user);
342     }
343 };
344
345 });