// shortcut suggest functionality // auto attaches to all [with-shortcuts] elements (function() { let input = null, menu = null, options = [], index = -1; let backtrackAfterApplying = false; function getCaretPos(win) { win = win || window; let doc = win.document; let sel, range, rects, rect; let x = 0, y = 0; if (win.getSelection) { // won't work if getSelection() isn't available! sel = win.getSelection(); if(sel.toString() !== '') return null; // won't work if there isa if (sel.rangeCount) { range = sel.getRangeAt(0).cloneRange(); if (range.getClientRects) { range.collapse(true); let span = doc.createElement("span"); if (span.getClientRects) { // Ensure span has dimensions and position by // adding a zero-width space character span.appendChild( doc.createTextNode("\u200bXX") ); range.insertNode(span); rect = span.getClientRects()[0]; x = rect.left; y = rect.top; let spanParent = span.parentNode; spanParent.removeChild(span); // Glue any broken text nodes back together spanParent.normalize(); return [x, y]; } return null } } } return { x: x, y: y }; } function show(_input) { let pos = getCaretPos(); if(pos) { index = -1; let rawOptions = $(_input).closest('[data-shortcuts]').attr('data-shortcuts'); options = []; rawOptions = rawOptions.split('^^^'); for (let i = 0; i < rawOptions.length; i++) { let parts = rawOptions[i].split('|||'); options.push({ name: parts[0], value: parts[1] }); } menu.empty(); for(let i = 0; i < options.length; i++) { menu.append( $('
') .addClass('sc') .text(options[i].name) .attr('title', options[i].value) ); } menu .css({ left: pos[0] + 'px', top: pos[1] + 'px', }) .show(); } } function discard() { if($('.stag-shortcuts:visible').length) { $('.stag-shortcuts').hide(); return false; } } function highlightOption() { menu.find('.sc').removeClass('active'); if(options && options.length && index >= 0 && index < options.length) { menu.find('.sc:eq(' + index + ')').addClass('active'); } } function apply() { if(input && options && options.length && index >= 0 && index < options.length) { $(input).focus(); if(backtrackAfterApplying) { document.execCommand("delete", true, null); } document.execCommand("insertText", true, options[index].value); discard(); } } function isVisible() { return !!$('.stag-shortcuts:visible').length; } function init() { var selectedText = ''; $('.stag-shortcuts').remove(); options = []; menu = $('
') .addClass('stag-shortcuts') .appendTo('body'); $(document) .off('mousedown.outside-shortcuts') .on('mousedown.outside-shortcuts', function(_e) { return discard(); }); $(document) .off('keypress.shortcuts', '[with-shortcuts]') .on('keypress.shortcuts', '[with-shortcuts]', function(_e) { // console.log('KP: ', _e.which); input = this; switch(_e.which) { case 64: backtrackAfterApplying = true; show(this); break; default: if(isVisible()) return false; break; } }) .off('keydown.shortcuts', '[with-shortcuts]') .on('keydown.shortcuts', '[with-shortcuts]', function(_e) { // console.log('KD: ', _e.which); input = this; let consumed = false; switch(_e.which) { case 32: if(_e.ctrlKey) { backtrackAfterApplying = false; show(this); return false; } else { if(!isVisible()) return; _e.preventDefault(); apply(); consumed = true; } break; case 27: if(!isVisible()) return; consumed = !discard(); break; case 38: if(!isVisible()) return; if(index > 0) index--; highlightOption(); consumed = true; break; case 40: if(!isVisible()) return; if(index < options.length - 1) index++; highlightOption(); consumed = true; break; case 13: if(!isVisible()) return; apply(); consumed = true; break; default: consumed = isVisible(); break; } if(consumed) return false; }) .off('selectionchange.shortcuts') .on('selectionchange.shortcuts', function(_e) { console.log(_e); }); $(document) .off('click.apply-shortcuts', '.stag-shortcuts>.sc') .on('click.apply-shortcuts', '.stag-shortcuts>.sc', function(_e) { apply(); return false; }); menu.off('mousedown.inside-shortcuts') .on('mousedown.inside-shortcuts', function(_e) { return false; }); $(document) .off('mousedown.add-shortcuts', '.add-shortcut') .on('mousedown.add-shortcuts', '.add-shortcut', function(_e) { let hasFocus = $(document.activeElement).closest('.note-content').length; if(hasFocus) { selectedText = window.getSelection().toString(); if(selectedText !== '') return; } return false; }) .off('click.add-shortcuts', '.add-shortcut') .on('click.add-shortcuts', '.add-shortcut', function(_e) { if(selectedText === '') return; $('#selected-sc-text').val(selectedText); $('#create-shortcut-form') .css({ left: $(this).offset().left + 'px', top: ($(this).offset().top + $(this).outerHeight()) + 'px' }) .show(); showMoeFormMask(); return false; }) .off('mousedown.inside-form', '#create-shortcut-form *') .on('mousedown.inside-form', '#create-shortcut-form *', function(_e) { // return false; }) .off('submit.add-shortcut', '#create-shortcut-form') .on('submit.add-shortcut', '#create-shortcut-form', function(_e) { var label = $(this).find('[name="shortcut"]').val(), content = $(this).find('[name="text"]').val(); $.post('/api/proTextShortcut/create', $(this).serialize(), function(_data) { if(_data && _data.success && input) { var options = [$(input).closest('[data-shortcuts]').attr('data-shortcuts')] options.push(label + '|||' + content); options = options.join('^^^'); $(input).closest('[data-shortcuts]').attr('data-shortcuts', options); toastr.success('Shortcut saved'); hideMoeFormMask(); $('#create-shortcut-form').hide(); $(input).focus(); } }, 'json'); return false; }); } addMCInitializer('shortcut-suggest', init); })();