Fixing splitting at the very beginning of a text element
[fnpeditor.git] / modules / documentCanvas / canvasManager.js
1 define([
2 'libs/jquery-1.9.1.min',
3 './canvasNode'
4 ], function($, canvasNode) {
5
6 'use strict';
7
8 var getCursorPosition = function() {
9     var selection = window.getSelection();
10     var anchorNode = $(selection.anchorNode);
11     var parent = anchorNode.parent();
12     return {
13         textNode: anchorNode,
14         textNodeOffset: selection.anchorOffset,
15         textNodeIndex: parent.contents().index(anchorNode),
16         parentNode: parent,
17         focusNode: $(selection.focusNode).parent(),
18         isAtEnd: selection.anchorOffset === anchorNode.text().length
19     };
20 };
21
22 var Manager = function(canvas, sandbox) {
23     this.canvas = canvas;
24     this.sandbox = sandbox;
25     this.shownAlready = false;
26     this.gridToggled = false;
27     this.scrollbarPosition = 0;
28     this.currentNode = null;
29     var manager = this;
30         
31     canvas.doc().dom().find('#rng-module-documentCanvas-content').on('keyup', function() {
32         manager.sandbox.publish('contentChanged');
33     });
34
35     canvas.doc().dom().on('mouseover', '[wlxml-tag]', function(e) {
36         e.stopPropagation();
37         manager.sandbox.publish('nodeHovered', canvasNode.create($(e.target)));
38     });
39     canvas.doc().dom().on('mouseout', '[wlxml-tag]', function(e) {
40         e.stopPropagation();
41         manager.sandbox.publish('nodeBlured', canvasNode.create($(e.target)));
42     });
43     canvas.doc().dom().on('click', '[wlxml-tag]', function(e) {
44         e.stopPropagation();
45         console.log('clicked node type: '+e.target.nodeType);
46         manager.selectNode(canvasNode.create($(e.target)));
47     });
48
49     canvas.doc().dom().on('keyup', '#rng-module-documentCanvas-contentWrapper', function(e) {
50         var anchor = $(window.getSelection().anchorNode);
51         
52         if(anchor[0].nodeType === Node.TEXT_NODE)
53             anchor = anchor.parent();
54         if(!anchor.is('[wlxml-tag]'))
55             return;
56         manager.selectNode(canvasNode.create(anchor));
57     });
58     
59     canvas.doc().dom().on('keydown', '#rng-module-documentCanvas-contentWrapper', function(e) {
60         if(e.which === 13) { 
61             manager.onEnterKey(e);
62         }
63         if(e.which === 8) {
64             manager.onBackspaceKey(e);
65         }
66     });
67               
68     canvas.doc().dom().onShow = function() {
69         if(!manager.shownAlready) {
70             manager.shownAlready = true;
71             manager.selectFirstNode();
72         } else if(manager.currentNode) {
73             manager.movecaretToNode(manager.getNodeElement(manager.currentNode));
74             canvas.doc().dom().find('#rng-module-documentCanvas-contentWrapper').scrollTop(manager.scrollbarPosition);
75         }
76     };
77     canvas.doc().dom().onHide = function() {
78        manager.scrollbarPosition = canvas.doc().dom().find('#rng-module-documentCanvas-contentWrapper').scrollTop();
79     };
80 };
81     
82 Manager.prototype.selectNode = function(cnode, options) {
83     options = options || {};
84     var nodeElement = this.getNodeElement(cnode);
85     
86     this.dimNode(cnode);
87     
88     this.canvas.doc().dom().find('.rng-module-documentCanvas-currentNode').removeClass('rng-module-documentCanvas-currentNode');
89     nodeElement.addClass('rng-module-documentCanvas-currentNode');
90     
91     if(options.movecaret) {
92         this.movecaretToNode(nodeElement, options.movecaret);
93     }
94     
95     this.currentNode = cnode;
96     this.sandbox.publish('nodeSelected', cnode);
97 };
98
99 Manager.prototype.getNodeElement = function(cnode) {
100     return this.canvas.doc().dom().find('#'+cnode.getId());
101 };
102
103 Manager.prototype.highlightNode = function(cnode) {
104     var nodeElement = this.getNodeElement(cnode);
105     if(!this.gridToggled) {
106         nodeElement.addClass('rng-common-hoveredNode');
107         var label = nodeElement.attr('wlxml-tag');
108         if(nodeElement.attr('wlxml-class'))
109             label += ' / ' + nodeElement.attr('wlxml-class');
110         var tag = $('<div>').addClass('rng-module-documentCanvas-hoveredNodeTag').text(label);
111         nodeElement.append(tag);
112     }
113 };
114
115 Manager.prototype.dimNode = function(cnode) {
116     var nodeElement = this.getNodeElement(cnode);
117     if(!this.gridToggled) {
118         nodeElement.removeClass('rng-common-hoveredNode');
119         nodeElement.find('.rng-module-documentCanvas-hoveredNodeTag').remove();
120     }
121 };
122
123 Manager.prototype.selectFirstNode = function() {
124     var firstNodeWithText = this.canvas.doc().dom().find('[wlxml-tag]').filter(function() {
125         return $(this).clone().children().remove().end().text().trim() !== '';
126     }).first();
127     var node;
128     if(firstNodeWithText.length)
129         node = $(firstNodeWithText[0]);
130     else {
131         node = this.canvas.doc().dom().find('[wlxml-class|="p"]');
132     }
133     this.selectNode(canvasNode.create(node), {movecaret: true});
134 };
135
136 Manager.prototype.movecaretToNode = function(nodeElement, where) {
137     if(!nodeElement.length)
138         return;
139     var range = document.createRange();
140     range.selectNodeContents(nodeElement[0]);
141     
142     var collapseArg = true;
143     if(where === 'end')
144         collapseArg = false;
145     range.collapse(collapseArg);
146     var selection = document.getSelection();
147     selection.removeAllRanges();
148     selection.addRange(range);
149 };
150
151 Manager.prototype.onEnterKey = function(e) {
152     e.preventDefault();
153     var pos = getCursorPosition();
154     var contextNode = this.canvas.getNodeById(pos.parentNode.attr('id'));
155     var newNode;
156
157     if(pos.isAtEnd) {
158         newNode = canvasNode.create({tag: pos.parentNode.attr('wlxml-tag'), klass: pos.parentNode.attr('wlxml-class')});
159         this.canvas.nodeInsertAfter({node: newNode, after: this.canvas.getNodeById(pos.parentNode.attr('id'))});
160     } else {
161         newNode = this.canvas.nodeSplit({node: contextNode, textNodeIdx: pos.textNodeIndex, offset: pos.textNodeOffset});
162     }
163     if(newNode)
164         this.selectNode(newNode, {movecaret: true});
165     this.sandbox.publish('contentChanged');
166 };
167
168 Manager.prototype.onBackspaceKey = function(e) {
169     var pos = getCursorPosition();
170     var len = pos.textNode.text().length;
171     if(len === 1) {
172         // Prevent deleting node by browser after last character removed;
173         e.preventDefault();
174         pos.parentNode.text('');
175     }
176     if(len === 0) {
177         e.preventDefault();
178         var toRemove = canvasNode.create(pos.textNode);
179         var prevNode = this.canvas.getPrecedingNode({node:toRemove});
180         this.canvas.nodeRemove({node: toRemove}); // jesli nie ma tekstu, to anchor nie jest tex nodem
181         this.selectNode(prevNode, {movecaret: 'end'});
182     }
183 };
184
185 Manager.prototype.command = function(command, params) {
186
187     var cursor = this.canvas.getCursor(),
188         selectionStart = cursor.getSelectionStart(),
189         selectionEnd = cursor.getSelectionEnd(),
190         parent1 = selectionStart.element.parent() || undefined,
191         parent2 = selectionEnd.element.parent() || undefined;
192
193     if(command === 'unwrap-node') {
194         if(this.canvas.list.areItemsOfTheSameList({element1: parent1, element2: parent2})) {
195             this.canvas.list.extractItems({element1: parent1, element2: parent2});
196         } else if(!cursor.isSelecting()) {
197             cursor.getPosition().element.unwrap();
198         }
199     } else if(command === 'wrap-node') {
200         if(this.canvas.list.areItemsOfTheSameList({element1: parent1, element2: parent2})) {
201             this.canvas.list.create({element1: parent1, element2: parent2});
202         }
203     } else if(command === 'toggle-list') {
204         if(params.toggle) {
205             this.canvas.list.create({element1: parent1, element2: parent2});
206         } else {
207             if(this.canvas.list.areItemsOfTheSameList({element1: parent1, element2: parent2})) {
208                 this.canvas.list.extractItems({element1: parent1, element2: parent2, merge: false});
209             } 
210         }
211     } else if(command == 'toggle-grid') {
212         this.canvas.doc().dom().find('[wlxml-tag]').toggleClass('rng-common-hoveredNode', params.toggle);
213         this.gridToggled = params.toggle;
214     } else if(command == 'newNodeRequested') {
215         if(cursor.isSelecting() && cursor.isSelectingSiblings()) {
216             if(cursor.isSelectingWithinElement()) {
217                 selectionStart.element.wrapWithNodeElement({tag: params.wlxmlTag, klass: params.wlxmlClass, start: selectionStart.offset, end: selectionEnd.offset});
218             }
219             else {
220                 var parent = selectionStart.element.parent();
221                 this.canvas.wrapText({
222                     inside: parent,
223                     _with: {tag: params.wlxmlTag, klass: params.wlxmlClass},
224                     offsetStart: selectionStart.offset,
225                     offsetEnd: selectionEnd.offset,
226                     textNodeIdx: [parent.childIndex(selectionStart.element), parent.childIndex(selectionEnd.element)]
227                 });
228             }
229         }
230     }
231 };
232
233
234 return Manager;
235     
236 });