// shortcut suggest functionality // auto attaches to all [with-shortcuts] elements (function() { let input = null, menu = null, options = [], index = -1; let backtrackAfterApplying = false; let strPart = ''; 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(_initial = false, _options) { let pos = getCaretPos(); // strPart = ''; if(pos) { index = -1; options = _options; if(strPart.length > 1) { options = _options.filter(function(_x) { return _x.name.toLowerCase().indexOf(strPart.substr(1).toLowerCase()) !== -1; }); } menu.empty(); for(let i = 0; i < options.length; i++) { menu.append( $('
') .addClass('sc') .text(options[i].name) .attr('title', options[i].value) ); } if(_initial) { menu .css({ left: pos[0] + 'px', top: (pos[1] + $(window).scrollTop()) + 'px', }) .show(); document.execCommand("insertText", true, '@'); document.execCommand("delete", true, null); } } } 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(_clearReturn = false) { if(input && options && options.length && index >= 0 && index < options.length) { // $(input).focus(); if(backtrackAfterApplying) { for (let i = 0; i < strPart.length; i++) { document.execCommand("delete", true, null); } } // if(_clearReturn) 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'); let refreshOptions = false; $(document) .off('mousedown.outside-shortcuts') .on('mousedown.outside-shortcuts', function(_e) { if($(_e.target).closest('.stag-shortcuts').length) return; return discard(); }); $(document) .off('keypress.shortcuts', '[with-shortcuts]') .on('keypress.shortcuts', '[with-shortcuts]', function(_e) { input = this; switch(_e.which) { case 64: backtrackAfterApplying = true; strPart = '@'; let options = [], noteRTE = $(input).closest('[note-rte]'); let scSets = noteRTE.attr('use-shortcuts') ? noteRTE.attr('use-shortcuts').split(',') : ['user'] for (let i = 0; i < scSets.length; i++) { if(window[$.trim(scSets[i]) + 'Shortcuts']) { options = options.concat(window[$.trim(scSets[i]) + 'Shortcuts']); } } show(true, options); break; default: if(!isVisible()) return; let char = String.fromCharCode(_e.which); if(_e.ctrlKey || _e.metaKey || !char.match(/[a-z0-9 ]/i)) { return false; } break; } }) .off('keydown.shortcuts', '[with-shortcuts]') .on('keydown.shortcuts', '[with-shortcuts]', function(_e) { input = this; let consumed = false; refreshOptions = false; switch(_e.which) { /* case 32: if(_e.ctrlKey && !isVisible()) { backtrackAfterApplying = false; show(this); return false; } else { if(!isVisible()) return; _e.preventDefault(); apply(); consumed = true; } break; */ case 27: if(!isVisible()) return; consumed = !discard(); if(consumed) markEventAsConsumed(_e); 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(true); consumed = true; break; case 8: if(!isVisible()) break; if(strPart.length === 1) { strPart = ''; discard(); } else { strPart = strPart.substr(0, strPart.length - 1); refreshOptions = true; } consumed = false; break; default: if(!isVisible()) break; let char = String.fromCharCode(_e.which); if(!_e.ctrlKey && !_e.metaKey && char.match(/[a-z0-9 ]/i)) { strPart += char; refreshOptions = true; consumed = false; } else { consumed = true; } break; } if(consumed) { _e.stopImmediatePropagation(); _e.preventDefault(); return false; } }) .off('keyup.shortcuts', '[with-shortcuts]') .on('keyup.shortcuts', '[with-shortcuts]', function(_e) { let input = this; let options = window.userShortcuts, noteRTE = $(input).closest('[note-rte]'); if(noteRTE.length && noteRTE.is('[use-shortcuts]') && noteRTE.attr('use-shortcuts') && noteRTE.attr('use-shortcuts') !== 'user') { if(window[noteRTE.attr('use-shortcuts') + 'Shortcuts']) { options = window[noteRTE.attr('use-shortcuts') + 'Shortcuts']; } } if(isVisible() && refreshOptions) { show(false, options); } }) .off('paste.shortcuts', '[with-shortcuts]') .on('paste.shortcuts', '[with-shortcuts]', function(_e) { if(isVisible()) return false; }); $(document) .off('mousedown.apply-shortcuts', '.stag-shortcuts>.sc') .on('mousedown.apply-shortcuts', '.stag-shortcuts>.sc', function(_e) { index = $(this).index(); apply(); return false; }); $(document) .off('mousedown.add-shortcuts', '.add-shortcut') .on('mousedown.add-shortcuts', '.add-shortcut', function(_e) { let hasFocus = $(document.activeElement).closest('.note-content, .rte-holder').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') .attr('data-editor-id', $(this).attr('data-editor-id')) .css({ left: $(this).offset().left + 'px', top: ($(this).offset().top + $(this).outerHeight()) + 'px' }) .show(); showMoeFormMask(); return false; }) .off('submit.add-shortcut', '#create-shortcut-form') .on('submit.add-shortcut', '#create-shortcut-form', function(_e) { if(!input) input = $('.note-content [contenteditable][data-editor-id="' + $(this).attr('data-editor-id') + '"]').first(); if(!input) return false; var label = $(this).find('[name="shortcut"]').val(), content = $(this).find('[name="text"]').val(); if(!this.checkValidity()) return false; $.post('/api/proTextShortcut/create', $(this).serialize(), function(_data) { if(_data && _data.success && input) { window.userShortcuts.push({ name: label, value: content }); toastr.success('Shortcut saved'); hideMoeFormMask(); $('#create-shortcut-form').hide(); $(input).focus(); } }, 'json'); return false; }) .off('reset.add-shortcut', '#create-shortcut-form') .on('reset.add-shortcut', '#create-shortcut-form', function(_e) { if(!input) input = $('.note-content [contenteditable][data-editor-id="' + $(this).attr('data-editor-id') + '"]').first(); if(!input) return false; hideMoeFormMask(); $('#create-shortcut-form').hide(); $(input).focus(); }); } addMCInitializer('shortcut-suggest', init); })();