3 'modules/documentCanvas/canvas/utils'
4 ], function($, utils) {
20 var scroll = function(place, textElement) {
21 var rect = textElement.getBoundingClientRect(),
22 scroll = $('#rng-module-documentCanvas-contentWrapper'),
23 border = rect.bottom - (place === 'top' ? rect.height : 0) - scroll.offset().top + scroll[0].scrollTop,
24 visible = scroll[0].scrollTop + {top: 0, bottom: scroll.height()}[place],
28 if(place === 'top' && (border - padding < visible)) {
29 toScroll = border - visible - padding;
30 } else if(place === 'bottom' && (border + padding > visible)) {
31 toScroll = border - visible + padding;
34 scroll[0].scrollTop = scroll[0].scrollTop + toScroll;
39 var getLastRectAbove = function(node, y) {
40 var rects = node.getClientRects(),
43 while((rect = rects[idx])) {
54 var getFirstRectBelow = function(node, y) {
55 var rects = node.getClientRects(),
58 while((rect = rects[idx])) {
68 var handleKeyEvent = function(e, s) {
69 keyEventHandlers.some(function(handler) {
70 if(handler.applies(e, s)) {
76 // todo: whileRemoveWholetext
77 var keyEventHandlers = [
78 { // ctrl+x - prevented (?)
79 applies: function(e, s) {
82 s.type === 'textSelection' &&
83 s.startsAtBeginning() &&
92 applies: function(e, s) {
93 return e.key === KEYS.ARROW_UP && s.type === 'caret';
97 var caretRect = window.getSelection().getRangeAt(0).getClientRects()[0],
98 frameRects = s.element.dom[0].getClientRects(),
99 caretTop = caretRect.bottom - caretRect.height,
100 position, target,rect, scrolled;
103 if((frameRects[0].bottom === caretRect.bottom) || (caretRect.left < frameRects[0].left)) {
105 s.canvas.rootWrapper.find('[document-text-element]').each(function() {
106 var test = getLastRectAbove(this, caretTop);
115 scrolled = scroll('top', target);
116 position = utils.caretPositionFromPoint(caretRect.left, rect.bottom - 1 - scrolled);
117 s.canvas.setCurrentElement(s.canvas.getDocumentElement(position.textNode), {caretTo: position.offset});
121 scrolled = scroll('top', target);
122 var left = caretRect.left;
123 if(left > rect.left + rect.width) {
124 left = rect.left + rect.width;
125 } else if(left < rect.left ) {
128 position = utils.caretPositionFromPoint(left, rect.bottom - 1 - scrolled);
129 s.canvas.setCurrentElement(s.canvas.getDocumentElement(position.textNode), {caretTo: position.offset});
134 applies: function(e, s) {
135 return e.key === KEYS.ARROW_DOWN && s.type === 'caret';
137 run: function(e, s) {
139 var caretRect = window.getSelection().getRangeAt(0).getClientRects()[0],
140 frameRects = s.element.dom[0].getClientRects(),
141 lastRect = frameRects[frameRects.length-1],
142 position, target,rect, scrolled;
144 if(lastRect.bottom === caretRect.bottom || (caretRect.left > lastRect.left + lastRect.width)) {
146 s.canvas.rootWrapper.find('[document-text-element]').each(function() {
147 var test = getFirstRectBelow(this, caretRect.bottom);
155 scrolled = scroll('bottom', target);
156 position = utils.caretPositionFromPoint(caretRect.left, rect.top +1 - scrolled);
157 s.canvas.setCurrentElement(s.canvas.getDocumentElement(position.textNode), {caretTo: position.offset});
161 scrolled = scroll('bottom', target);
162 var left = caretRect.left;
163 if(left > rect.left + rect.width) {
164 left = rect.left + rect.width;
165 } else if(left < rect.left ) {
168 position = utils.caretPositionFromPoint(left, rect.top +1 - scrolled);
169 s.canvas.setCurrentElement(s.canvas.getDocumentElement(position.textNode), {caretTo: position.offset});
174 applies: function(e, s) {
175 return e.key === KEYS.ARROW_LEFT && s.type === 'caret';
177 run: function(e, s) {
183 prev = s.canvas.getPreviousTextElement(s.element);
185 scroll('top', prev.dom[0]);
186 s.canvas.setCurrentElement(s.canvas.getDocumentElement(prev.dom.contents()[0]), {caretTo: 'end'});
192 applies: function(e, s) {
193 return e.key === KEYS.ARROW_RIGHT && s.type === 'caret';
195 run: function(e, s) {
200 next = s.canvas.getNextTextElement(s.element);
202 scroll('bottom', next.dom[0]);
203 s.canvas.setCurrentElement(s.canvas.getDocumentElement(next.dom.contents()[0]), {caretTo: 0});
206 var secondToLast = (s.offset === s.element.wlxmlNode.getText().length -1);
208 // Only Flying Spaghetti Monster knows why this is need for FF (for versions at least 26 to 31)
210 s.canvas.setCurrentElement(s.element, {caretTo: 'end'});
215 { // backspace removing the last character in a span
216 applies: function(e, s) {
217 return s.type === 'caret' &&
218 s.element.wlxmlNode.parent().is({tagName: 'span'}) &&
219 s.element.wlxmlNode.getText().length === 1 &&
221 (e.key === KEYS.BACKSPACE);
223 run: function(e, s) {
225 prevTextNode = s.element.canvas.getPreviousTextElement(s.element).wlxmlNode;
227 s.element.wlxmlNode.parent().detach(params);
228 s.canvas.setCurrentElement(
229 (params.ret && params.ret.mergedTo) || prevTextNode,
230 {caretTo: params.ret ? params.ret.previousLen : (prevTextNode ? prevTextNode.getText().length : 0)});
233 { // backspace/delete through an edge (behaves weirdly at spans)
234 applies: function(e, s) {
235 return s.type === 'caret' && (
236 (s.isAtBeginning() && e.key === KEYS.BACKSPACE) ||
237 (s.isAtEnd() && e.key === KEYS.DELETE)
241 var direction, caretTo, cursorAtOperationEdge, goto, element;
243 if(e.key === KEYS.BACKSPACE) {
246 cursorAtOperationEdge = s.isAtBeginning(); // always true?
252 cursorAtOperationEdge = s.isAtEnd(); // always true?
253 element = cursorAtOperationEdge && s.canvas.getNearestTextElement(direction, s.element);
256 if(!cursorAtOperationEdge || !element) {
261 var parent = element.wlxmlNode.parent();
262 if(element.wlxmlNode.getIndex() === 0 && parent.isContextRoot() && (!parent.is('item') || parent.getIndex() === 0)) {
263 // Don't even try to do anything at the edge of a context root, except for non-first items
264 // - this is a temporary solution until key events handling get refactored into something more sane.
270 s.canvas.wlxmlDocument.transaction(function() {
271 if(element.wlxmlNode.getIndex() === 0) {
272 goto = element.wlxmlNode.parent().moveUp();
274 goto = element.wlxmlNode.moveUp();
277 s.canvas.setCurrentElement(goto.node, {caretTo: goto.offset});
281 description: gettext('Remove text')
287 { // backspace/delete last character in a node - why is it needed?
288 applies: function(e,s) {
289 return s.type === 'caret' && s.element.getText().length === 1 && (e.key === KEYS.BACKSPACE || e.key === KEYS.DELETE);
293 s.element.wlxmlNode.setText('');
294 s.canvas.setCurrentElement(s.element, {caretTo: 0});
299 applies: function(e, s) {
300 return s.type === 'textSelection' && (e.key === KEYS.BACKSPACE || e.key === KEYS.DELETE);
302 run: function(e, s) {
303 var direction = 'above',
307 if(e.key === KEYS.DELETE) {
314 if(s.startsAtBeginning && s.endsAtEnd && s.startElement.sameNode(s.endElement)) {
315 goto = s.startElement;
316 caretTo = s.startOffset;
317 } else if(direction === 'above') {
318 if(s.startsAtBeginning()) {
319 goto = s.canvas.getNearestTextElement('above', s.startElement);
322 goto = s.startElement;
323 caretTo = s.startOffset;
327 goto = s.canvas.getNearestTextElement('below', s.startElement);
335 var doc = s.canvas.wlxmlDocument;
336 doc.transaction(function() {
340 node: s.startElement.wlxmlNode,
341 offset: s.startOffset
344 node: s.endElement.wlxmlNode,
350 success: function() {
352 s.canvas.setCurrentElement(goto, {caretTo: caretTo});
359 { // enter on an empty list item - creates paragraph after list
360 applies: function(e, s) {
361 var parent = s.element && s.element.wlxmlNode.parent(),
362 parentIsItem = parent && parent.is('item'),
363 itemIsOnList = parent && parent.parent() && parent.parent().is('list');
364 return s.type === 'caret' && e.key === KEYS.ENTER && s.element.isEmpty() && parentIsItem && itemIsOnList;
366 run: function(e, s) {
367 var item = s.element.wlxmlNode.parent(),
368 list = item.parent();
370 s.canvas.wlxmlDocument.transaction(function() {
371 var p = list.after({tagName: 'div', attrs: {'class': 'p'}});
372 p.append({text: ''});
374 if(list.contents().length === 0) {
379 success: function(p) {
380 s.canvas.setCurrentElement(p);
385 { // enter - split node
386 applies: function(e, s) {
387 return s.type === 'caret' && e.key === KEYS.ENTER && !s.element.parent().isRootElement();
389 run: function(e, s) {
390 var parent = s.element.parent(),
391 children = parent.children(),
392 result, goto, gotoOptions;
396 if(children.length === 1 && s.element.isEmpty()) {
400 s.canvas.wlxmlDocument.transaction(function() {
401 result = s.element.wlxmlNode.breakContent({offset: s.offset});
404 description: gettext('Splitting text'),
405 fragment: s.toDocumentFragment()
409 if(result.emptyText) {
410 goto = result.emptyText;
413 goto = result.second;
414 gotoOptions = {caretTo: 'start'};
417 s.canvas.setCurrentElement(utils.getElementForNode(goto), gotoOptions);
420 { // enter - new paragraph after image/video
421 applies: function (e, s) {
422 return s.type === 'nodeSelection' && e.key === KEYS.ENTER && !s.element.isRootElement();
424 run: function (e, s) {
425 var parent = s.element.parent(),
426 children = parent.children(),
427 result, goto, gotoOptions;
430 s.canvas.wlxmlDocument.transaction(function() {
431 result = s.element.wlxmlNode.insertNewNode();
434 description: gettext('Inserting node'),
435 fragment: s.toDocumentFragment()
439 s.canvas.setCurrentElement(utils.getElementForNode(result), {caretTo: 'start'});
445 handleKeyEvent: handleKeyEvent,