3 'modules/documentCanvas/canvas/documentElement',
4 'modules/documentCanvas/canvas/utils'
5 ], function($, documentElement, utils) {
21 var handleKey = function(event, canvas) {
22 handlers.some(function(handler) {
23 if(handles(handler, event) && handler[event.type]) {
24 handler[event.type](event, canvas);
30 var handles = function(handler, event) {
31 if(handler.key === event.which) {
34 if(handler.keys && handler.keys.indexOf(event.which) !== -1) {
41 var scroll = function(place, textElement) {
42 var rect = textElement.getBoundingClientRect(),
43 scroll = $('#rng-module-documentCanvas-contentWrapper'),
44 border = rect.bottom - (place === 'top' ? rect.height : 0) - scroll.offset().top + scroll[0].scrollTop,
45 visible = scroll[0].scrollTop + {top: 0, bottom: scroll.height()}[place],
49 if(place === 'top' && (border - padding < visible)) {
50 toScroll = border - visible - padding;
51 } else if(place === 'bottom' && (border + padding > visible)) {
52 toScroll = border - visible + padding;
55 scroll[0].scrollTop = scroll[0].scrollTop + toScroll;
60 var getLastRectAbove = function(node, y) {
61 var rects = node.getClientRects(),
64 while((rect = rects[idx])) {
75 var getFirstRectBelow = function(node, y) {
76 var rects = node.getClientRects(),
79 while((rect = rects[idx])) {
92 handlers.push({key: KEYS.ENTER,
93 keydown: function(event, canvas) {
94 event.preventDefault();
95 var cursor = canvas.getCursor(),
96 position = cursor.getPosition(),
97 element = position.element;
99 if(Object.keys(cursor.getPosition()).length === 0) {
100 var currentElement = canvas.getCurrentNodeElement();
101 if(currentElement && !currentElement.wlxmlNode.isRoot()) {
102 canvas.wlxmlDocument.transaction(function() {
103 var added = currentElement.wlxmlNode.after({
104 tagName: currentElement.wlxmlNode.getTagName() || 'div',
105 attrs: {'class': currentElement.wlxmlNode.getClass() || 'p'}
107 added.append({text:''});
111 description: gettext('Splitting text')
113 success: function(ret) {
114 canvas.setCurrentElement(utils.getElementForNode(ret), {caretTo: 'start'});
122 if(!cursor.isSelecting()) {
124 if(element instanceof documentElement.DocumentTextElement) {
125 element = element.parent();
128 canvas.wlxmlDocument.transaction(function() {
129 var added = element.wlxmlNode.after(
130 {tagName: element.wlxmlNode.getTagName() || 'div', attrs: {'class': element.wlxmlNode.getClass() || 'p'}}
132 added.append({text: ''});
136 description: gettext('Splitting text')
138 success: function(ret) {
139 canvas.setCurrentElement(utils.getElementForNode(ret), {caretTo: 'start'});
145 if(!(element.parent().parent())) {
146 return false; // top level element is unsplittable
149 var node = position.element.wlxmlNode,
150 result, goto, gotoOptions;
152 node.document.transaction(function() {
153 result = position.element.wlxmlNode.breakContent({offset: position.offset});
156 description: gettext('Splitting text')
160 if(result.emptyText) {
161 goto = result.emptyText;
164 goto = result.second;
165 gotoOptions = {caretTo: 'start'};
168 canvas.setCurrentElement(utils.getElementForNode(goto), gotoOptions);
175 var selectsWholeTextElement = function(cursor) {
176 if(cursor.isSelecting() && cursor.getSelectionStart().offsetAtBeginning && cursor.getSelectionEnd().offsetAtEnd) {
182 handlers.push({key: KEYS.X,
183 keydown: function(event, canvas) {
184 if(event.ctrlKey && selectsWholeTextElement(canvas.getCursor())) {
185 event.preventDefault();
190 handlers.push({keys: [KEYS.BACKSPACE, KEYS.DELETE],
191 keydown: function(event, canvas) {
192 var cursor = canvas.getCursor(),
193 position = canvas.getCursor().getPosition(),
194 element = position.element,
195 node = element ? element.wlxmlNode : null,
200 if(!element || !node) {
204 if(event.which === KEYS.DELETE) {
209 if(cursor.isSelecting()) {
210 event.preventDefault();
211 var start = cursor.getSelectionStart(),
212 end = cursor.getSelectionEnd();
214 if(direction === 'above') {
215 if(start.offsetAtBeginning) {
216 goto = canvas.getNearestTextElement('above', start.element);
219 goto = start.element;
220 caretTo = start.offset;
223 if(end.offsetAtEnd) {
224 goto = canvas.getNearestTextElement('below', start.element);
232 canvas.wlxmlDocument.deleteText({
234 node: start.element.wlxmlNode,
238 node: end.element.wlxmlNode,
243 canvas.setCurrentElement(goto, {caretTo: caretTo});
248 var cursorAtOperationEdge = position.offsetAtBeginning;
249 if(event.which === KEYS.DELETE) {
250 cursorAtOperationEdge = position.offsetAtEnd;
253 var willDeleteWholeText = function() {
254 return element.getText().length === 1 || selectsWholeTextElement(cursor);
257 canvas.wlxmlDocument.transaction(function() {
258 if(willDeleteWholeText()) {
259 event.preventDefault();
262 else if(cursorAtOperationEdge) {
263 if(direction === 'below') {
264 element = canvas.getNearestTextElement(direction, element);
266 if(element && element.wlxmlNode.getIndex() === 0) {
267 goto = element.wlxmlNode.parent().moveUp();
269 canvas.setCurrentElement(goto.node, {caretTo: goto.offset});
272 event.preventDefault();
276 description: gettext('Remove text')
282 var handleKeyEvent = function(e, s) {
283 keyEventHandlers.some(function(handler) {
284 if(handler.applies(e, s)) {
290 // todo: whileRemoveWholetext
291 var keyEventHandlers = [
293 applies: function(e, s) {
294 return e.key === KEYS.ARROW_UP && s.type === 'caret';
296 run: function(e, s) {
298 var caretRect = window.getSelection().getRangeAt(0).getClientRects()[0],
299 frameRects = s.element.dom[0].getClientRects(),
300 caretTop = caretRect.bottom - caretRect.height,
301 position, target,rect, scrolled;
304 if((frameRects[0].bottom === caretRect.bottom) || (caretRect.left < frameRects[0].left)) {
306 s.canvas.rootWrapper.find('[document-text-element]').each(function() {
307 var test = getLastRectAbove(this, caretTop);
316 scrolled = scroll('top', target);
317 position = utils.caretPositionFromPoint(caretRect.left, rect.bottom - 1 - scrolled);
318 s.canvas.setCurrentElement(s.canvas.getDocumentElement(position.textNode), {caretTo: position.offset});
322 scrolled = scroll('top', target);
323 var left = caretRect.left;
324 if(left > rect.left + rect.width) {
325 left = rect.left + rect.width;
326 } else if(left < rect.left ) {
329 position = utils.caretPositionFromPoint(left, rect.bottom - 1 - scrolled);
330 s.canvas.setCurrentElement(s.canvas.getDocumentElement(position.textNode), {caretTo: position.offset});
335 applies: function(e, s) {
336 return e.key === KEYS.ARROW_DOWN && s.type === 'caret';
338 run: function(e, s) {
340 var caretRect = window.getSelection().getRangeAt(0).getClientRects()[0],
341 frameRects = s.element.dom[0].getClientRects(),
342 lastRect = frameRects[frameRects.length-1],
343 position, target,rect, scrolled;
345 if(lastRect.bottom === caretRect.bottom || (caretRect.left > lastRect.left + lastRect.width)) {
347 s.canvas.rootWrapper.find('[document-text-element]').each(function() {
348 var test = getFirstRectBelow(this, caretRect.bottom);
356 scrolled = scroll('bottom', target);
357 position = utils.caretPositionFromPoint(caretRect.left, rect.top +1 - scrolled);
358 s.canvas.setCurrentElement(s.canvas.getDocumentElement(position.textNode), {caretTo: position.offset});
362 scrolled = scroll('bottom', target);
363 var left = caretRect.left;
364 if(left > rect.left + rect.width) {
365 left = rect.left + rect.width;
366 } else if(left < rect.left ) {
369 position = utils.caretPositionFromPoint(left, rect.top +1 - scrolled);
370 s.canvas.setCurrentElement(s.canvas.getDocumentElement(position.textNode), {caretTo: position.offset});
375 applies: function(e, s) {
376 return e.key === KEYS.ARROW_LEFT && s.type === 'caret';
378 run: function(e, s) {
384 prev = s.canvas.getPreviousTextElement(s.element);
386 scroll('top', prev.dom[0]);
387 s.canvas.setCurrentElement(s.canvas.getDocumentElement(prev.dom.contents()[0]), {caretTo: 'end'});
393 applies: function(e, s) {
394 return e.key === KEYS.ARROW_RIGHT && s.type === 'caret';
396 run: function(e, s) {
401 next = s.canvas.getNextTextElement(s.element);
403 scroll('bottom', next.dom[0]);
404 s.canvas.setCurrentElement(s.canvas.getDocumentElement(next.dom.contents()[0]), {caretTo: 0});
407 var secondToLast = (s.offset === s.element.wlxmlNode.getText().length -1);
409 // Only Flying Spaghetti Monster knows why this is need for FF (for versions at least 26 to 31)
411 s.canvas.setCurrentElement(s.element, {caretTo: 'end'});
417 applies: function(e, s) {
418 return s.type === 'caret' &&
419 s.element.wlxmlNode.parent().is({tagName: 'span'}) &&
420 s.element.wlxmlNode.getText().length === 1 &&
422 (e.key === KEYS.BACKSPACE);
424 run: function(e, s) {
426 prevTextNode = s.element.canvas.getPreviousTextElement(s.element).wlxmlNode;
428 s.element.wlxmlNode.parent().detach(params);
429 s.canvas.setCurrentElement(
430 (params.ret && params.ret.mergedTo) || prevTextNode,
431 {caretTo: params.ret ? params.ret.previousLen : (prevTextNode ? prevTextNode.getText().length : 0)});
435 applies: function(e, s) {
436 return s.type === 'caret' && (
437 (s.isAtBeginning() && e.key === KEYS.BACKSPACE) ||
438 (s.isAtEnd() && e.key === KEYS.DELETE)
442 var direction, caretTo, cursorAtOperationEdge, goto, element;
444 if(e.key === KEYS.BACKSPACE) {
447 cursorAtOperationEdge = s.isAtBeginning();
453 cursorAtOperationEdge = s.isAtEnd();
454 element = cursorAtOperationEdge && s.canvas.getNearestTextElement(direction, s.element);
457 if(!cursorAtOperationEdge || !element) {
463 s.canvas.wlxmlDocument.transaction(function() {
464 if(element.wlxmlNode.getIndex() === 0) {
465 goto = element.wlxmlNode.parent().moveUp();
467 goto = element.wlxmlNode.moveUp();
470 s.canvas.setCurrentElement(goto.node, {caretTo: goto.offset});
474 description: gettext('Remove text')
481 applies: function(e,s) {
482 return s.type === 'caret' && s.element.getText().length === 1 && (e.key === KEYS.BACKSPACE || e.key === KEYS.DELETE);
486 e.element.wlxmlNode.setText('');
487 s.canvas.setCurrentElement(s.element, {caretTo: 0});
492 applies: function(e, s) {
493 return s.type === 'textSelection' && (e.key === KEYS.BACKSPACE || e.key === KEYS.DELETE);
495 run: function(e, s) {
496 var direction = 'above',
500 if(e.key === KEYS.DELETE) {
507 if(s.startsAtBeginning && s.endsAtEnd && s.startElement.sameNode(s.endElement)) {
508 goto = s.startElement;
509 caretTo = s.startOffset;
510 } else if(direction === 'above') {
511 if(s.startsAtBeginning()) {
512 goto = s.canvas.getNearestTextElement('above', s.startElement);
515 goto = s.startElement;
516 caretTo = s.startOffset;
520 goto = s.canvas.getNearestTextElement('below', s.startElement);
528 var doc = s.canvas.wlxmlDocument;
529 doc.transaction(function() {
533 node: s.startElement.wlxmlNode,
534 offset: s.startOffset
537 node: s.endElement.wlxmlNode,
543 success: function() {
545 s.canvas.setCurrentElement(goto, {caretTo: caretTo});
553 applies: function(e, s) {
554 return s.type === 'caret' && e.key === KEYS.ENTER && !s.element.parent().isRootElement();
556 run: function(e, s) {
557 var result, goto, gotoOptions;
560 s.canvas.wlxmlDocument.transaction(function() {
561 result = s.element.wlxmlNode.breakContent({offset: s.offset});
564 description: gettext('Splitting text'),
565 fragment: s.toDocumentFragment()
569 if(result.emptyText) {
570 goto = result.emptyText;
573 goto = result.second;
574 gotoOptions = {caretTo: 'start'};
577 s.canvas.setCurrentElement(utils.getElementForNode(goto), gotoOptions);
583 handleKey: handleKey,
584 handleKeyEvent: handleKeyEvent,