ソースを参照

Expanded minify.sh + js backup

Vijayakrishnan 3 年 前
コミット
0dfd5b2036
16 ファイル変更2232 行追加3 行削除
  1. 50 0
      js/click-to-copy.js
  2. 142 0
      js/dq.js
  3. 28 0
      js/icd-autocomplete.js
  4. 78 0
      js/mc-init.js
  5. 633 0
      js/mc.js
  6. 41 0
      js/option-list.js
  7. 172 0
      js/pro-suggest.js
  8. 302 0
      js/shortcut.js
  9. 22 0
      js/show-on-click.js
  10. 49 0
      js/stag-collapsible-card.js
  11. 284 0
      js/stag-popup.js
  12. 274 0
      js/stag-suggest.js
  13. 66 0
      js/stag-table-filter.js
  14. 77 0
      js/tsvToArray.js
  15. 0 0
      js/yemi.js
  16. 14 3
      minify.sh

+ 50 - 0
js/click-to-copy.js

@@ -0,0 +1,50 @@
+(function() {
+    function fallbackCopyTextToClipboard(text) {
+        var textArea = document.createElement("textarea");
+        textArea.value = text;
+
+        // Avoid scrolling to bottom
+        textArea.style.top = "0";
+        textArea.style.left = "0";
+        textArea.style.position = "fixed";
+
+        document.body.appendChild(textArea);
+        textArea.focus();
+        textArea.select();
+
+        try {
+            var successful = document.execCommand('copy');
+            var msg = successful ? 'successful' : 'unsuccessful';
+            console.log('Fallback: Copying text command was ' + msg);
+            toastr.success('Copied!');
+        } catch (err) {
+            console.error('Fallback: Oops, unable to copy', err);
+        }
+
+        document.body.removeChild(textArea);
+    }
+    function copyTextToClipboard(text) {
+        if (!navigator.clipboard) {
+            fallbackCopyTextToClipboard(text);
+            return;
+        }
+        navigator.clipboard.writeText(text).then(function() {
+            console.log('Async: Copying to clipboard was successful!');
+            toastr.success('Copied!');
+        }, function(err) {
+            console.error('Async: Could not copy text: ', err);
+        });
+    }
+
+    function init() {
+        $(document)
+            .off('click.click-to-copy', '.click-to-copy')
+            .on('click.click-to-copy', '.click-to-copy', function(event) {
+            copyTextToClipboard($(this).text());
+        });
+        window.copyTextToClipboard = copyTextToClipboard;
+    }
+
+    addMCInitializer('click-to-copy', init);
+
+}).call(window);

+ 142 - 0
js/dq.js

@@ -0,0 +1,142 @@
+(function() {
+    window.initDQ = function() {
+        $(document)
+            .off('change input paste', '.dq-edit-container input, .dq-edit-container textarea, .dq-edit-container select')
+            .on('change input paste', '.dq-edit-container input, .dq-edit-container textarea, .dq-edit-container select', function() {
+                let key = $(this).closest('.dq-line').attr('dq-key'),
+                    dataElem = $(this).closest('.dq-edit-container').find('>.dq-data-map').first(),
+                    current = JSON.parse(dataElem.text());
+                current[key] = $(this).val();
+                let serialized = JSON.stringify(current);
+                dataElem.text(serialized);
+                $(this).closest('form').find('input[name="data"]').val(serialized);
+                let fullData = {
+                    lines: JSON.parse($(this).closest('.dq-edit-container').find('>.dq-definition').first().text()),
+                    dataMap: current
+                };
+                $(this).closest('form').find('input[name="data"]').val(JSON.stringify(fullData));
+
+                runDQConditions($(this).closest('.dq-edit-container'));
+            });
+        $(document)
+            .off('input.auto-grow', '.dq-edit-container textarea')
+            .on('input.auto-grow', '.dq-edit-container textarea', function() {
+                this.style.minHeight = "calc(1.5em + .5rem + 2px)";
+                this.style.height = "calc(1.5em + .5rem + 2px)";
+                this.style.height = (this.scrollHeight)+"px";
+            });
+    };
+
+    function resolveAtomicCondition(_condition, _dataMap) {
+        if(!_condition.hasOwnProperty('key') || !_condition.hasOwnProperty('value')) {
+            _condition.resolution = false;
+            return;
+        }
+        let key = _condition.key, op = _condition.hasOwnProperty('op') ? _condition.op : 'eq';
+        let lhs = _dataMap[key], rhs = _condition.value;
+        switch(op) {
+            case 'eq':
+                _condition.resolution = (lhs == rhs); // NOTE: using == instead of === on purpose
+                break;
+            case 'lt':
+                _condition.resolution = (+lhs < +rhs);
+                break;
+            case 'lte':
+                _condition.resolution = (+lhs <= +rhs);
+                break;
+            case 'gt':
+                _condition.resolution = (+lhs > +rhs);
+                break;
+            case 'gte':
+                _condition.resolution = (+lhs >= +rhs);
+                break;
+            default:
+                _condition.resolution = false;
+        }
+    }
+
+    function resolveAllAtomicConditions(_conditions, _dataMap) {
+        if(Array.isArray(_conditions)) {
+            for (let i = 0; i < _conditions.length; i++) {
+                resolveAllAtomicConditions(_conditions[i], _dataMap);
+            }
+        }
+        else if(typeof _conditions === 'object') {
+            resolveAtomicCondition(_conditions, _dataMap);
+        }
+    }
+
+    function reduceConditionsIntoResolutions(_conditions) {
+        for (let i = 0; i < _conditions.length; i++) {
+            // if simple object, resolve
+            if(!Array.isArray(_conditions[i]) && typeof _conditions[i] === 'object') {
+                _conditions.splice(i, 1, _conditions[i].resolution);
+            }
+            else if(Array.isArray(_conditions[i])) {
+                reduceConditionsIntoResolutions(_conditions[i]);
+            }
+        }
+    }
+
+    function combineResolutionListsIntoSingleResolutions(_conditions) {
+        console.log('ALIX 1', _conditions);
+        for (let i = 0; i < _conditions.length; i++) {
+            if(Array.isArray(_conditions[i])) {
+                _conditions[i] = combineResolutionListsIntoSingleResolutions(_conditions[i]);
+            }
+        }
+        console.log('ALIX 2', _conditions);
+
+        // at this point, the array will have only booleans and "AND", "OR" combinators
+        let resolution = _conditions[0];
+        for (let i = 1; i < _conditions.length; i+=2) {
+            if(_conditions[i] === 'AND') {
+                resolution = resolution && _conditions[i + 1];
+            }
+            else if(_conditions[i] === 'OR') {
+                resolution = resolution || _conditions[i + 1];
+            }
+        }
+
+        return resolution;
+    }
+
+    window.runDQConditions = function(_parent) {
+
+        _parent.find('.dq-line.has-pre-condition').each(function() {
+            let conditions = JSON.parse($(this).find('>.dq-pre-condition').first().text()),
+                dataMap = JSON.parse($(this).closest('.dq-edit-container').find('>.dq-data-map').first().text());
+
+            resolveAllAtomicConditions(conditions, dataMap);
+
+            // if object, single condition with key, op and value
+            if(!Array.isArray(conditions) && typeof conditions === 'object') {
+                if(conditions.resolution) {
+                    $(this).removeClass('d-none');
+                }
+                else {
+                    $(this).addClass('d-none');
+                }
+            }
+            // else if array - means list of conditions with 'and' or 'or' separators - array size MUST be an odd number
+            else if(Array.isArray(conditions)) {
+
+                // goal is to reduce each item in the array into a single boolean - recursively
+                reduceConditionsIntoResolutions(conditions);
+
+                conditions = combineResolutionListsIntoSingleResolutions(conditions);
+
+                if(conditions) {
+                    $(this).removeClass('d-none');
+                }
+                else {
+                    $(this).addClass('d-none');
+                }
+
+            }
+
+        });
+
+    }
+    addMCInitializer('dq-edit', initDQ, '.dq-edit-container');
+}).call(window);

+ 28 - 0
js/icd-autocomplete.js

@@ -0,0 +1,28 @@
+(function() {
+
+    function init() {
+        let self = this;
+        $('[icd-autocomplete-code]:not([ac-initialized])').each(function() {
+            let elem = this, dynID = 'icd-' + Math.ceil(Math.random() * 1000000),
+                codeElem = $(this), descElem = $(this).closest('form').find('[icd-autocomplete-description]');
+            $(elem).attr('id', dynID);
+            new window.Def.Autocompleter.Search(dynID,
+                'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?sf=code,name&ef=name', {
+                    tableFormat: true,
+                    valueCols: [0],
+                    colHeaders: ['Code', 'Name'],
+                }
+            );
+            window.Def.Autocompleter.Event.observeListSelections(dynID, function() {
+                let autocomp = elem.autocomp, acData = autocomp.getSelectedItemData();
+                codeElem.val(acData[0].code).trigger('change');
+                descElem.val(acData[0].data['name']).trigger('change');
+                return false;
+            });
+            $(elem).attr('ac-initialized', 1);
+        });
+    }
+
+    addMCInitializer('icd-autocomplete', init);
+
+}).call(window);

+ 78 - 0
js/mc-init.js

@@ -0,0 +1,78 @@
+window.mcInitializers = {};
+window.addMCInitializer = function(_name, _func, _container) {
+    if(!!window.mcInitializers[_name]) {
+        // console.warn('ALIX initializer ' + _name + ' exists. Will overwrite.');
+        delete window.mcInitializers[_name];
+    }
+    window.mcInitializers[_name] = {
+        func: _func,
+        container: _container
+    };
+};
+window.runMCInitializers = function(_target = null) {
+    if(!!mcInitializers) {
+        // console.log('ALIX -----------------');
+        for(let initer in mcInitializers) {
+            if(mcInitializers.hasOwnProperty(initer)) {
+                try {
+                    let initerObj = mcInitializers[initer];
+                    if(!initerObj.container || $(initerObj.container).length) {
+                        // console.log("ALIX running MC initer", initer);
+                        initerObj.func(_target);
+                    }
+                    else {
+                        // console.warn("ALIX MC initer not in context. Removing ", initer);
+                        delete mcInitializers[initer];
+                        initerObj = null;
+                    }
+                }
+                catch(e) {
+                    console.warn('MC init error: ', e);
+                    console.warn('Initer: ', initer);
+                }
+            }
+        }
+    }
+};
+window.runMCInitializer = function(_initer) {
+    if(!!mcInitializers && !!mcInitializers[_initer]) {
+        try {
+            let initerObj = mcInitializers[_initer];
+            if(!initerObj.container || $(initerObj.container).length) {
+                // console.log("ALIX running MC initer", initer);
+                initerObj.func();
+            }
+            else {
+                // console.warn("ALIX MC initer not in context. Removing ", initer);
+                delete mcInitializers[_initer];
+                initerObj = null;
+            }
+        }
+        catch(e) {
+            console.warn('MC init error: ', e);
+            console.warn('Initer: ', _initer);
+        }
+    }
+};
+window.mcHooks = {};
+window.addMCHook = function(_name, _func) {
+    if(!!window.mcHooks[_name]) {
+        // console.warn('ALIX initializer ' + _name + ' exists. Will overwrite.');
+        delete window.mcHooks[_name];
+    }
+    window.mcHooks[_name] = {
+        func: _func
+    };
+};
+window.runMCHook = function(_initer) {
+    if(!!mcHooks && !!mcHooks[_initer]) {
+        try {
+            let initerObj = mcHooks[_initer];
+            initerObj.func();
+        }
+        catch(e) {
+            console.warn('MC hook error: ', e);
+            console.warn('Hook: ', _initer);
+        }
+    }
+};

+ 633 - 0
js/mc.js

@@ -0,0 +1,633 @@
+window.top.currentMcUrl = '';
+window.top.addEventListener('popstate', function (event) {
+    window.setTimeout(function () {
+        hideMask();
+        hideMoeFormMask();
+        if (!event || (!event.state && event.state !== '')) {
+            // console.error('ALIX No state!', event, event.state);
+            return;
+        }
+        var state = event.state;
+        if (state === '') state = '/';
+        if (state[0] !== '/') state = '/' + state;
+        if (!!state) fastLoad(state, false, true);
+    }, 0);
+});
+$(document).ready(function () {
+
+	var originalLocationPath = window.location.pathname;
+	var params = window.location.search;
+	// TODO make it so if param 'xy' is passed in, it doesn't go to parent
+	var noMcStuffOnLoad = originalLocationPath.indexOf('notes') >= 0;
+
+    if(!window.noMc){
+        if (window.location.pathname === window.top.location.pathname) {
+            window.top.location.href = '/mc' + window.location.pathname + window.location.search;
+            return;
+        }
+    }
+
+    if(window.noMc) return;
+
+    // window.top.ensureRHS();
+    $(document).on('click', '.stag_rhs_toggle', function () {
+        var state = window.top.toggleRHS(),
+            icon = $(this).find('i');
+        if (state === 'collapsed') {
+            icon.removeClass().addClass('fa fa-arrow-left');
+        } else {
+            icon.removeClass().addClass('fa fa-arrow-right');
+        }
+    });
+    var body = $(window.top.document.body),
+        icon = $('.stag_rhs_toggle i');
+    if (body.is('.stag_rhs_collapsed')) {
+        icon.removeClass().addClass('fa fa-arrow-left');
+    }
+    initCreateNote();
+    initQuillEdit();
+    initFastLoad();
+    initPrimaryForm();
+    initPatientPresenceIndicator();
+    runMCInitializers();
+    initFileInputs();
+
+    // if(typeof initializeCalendar !== 'undefined') {
+    //     initializeCalendar();
+    // }
+    // if(typeof initIntakeEvents !== 'undefined') {
+    //     initIntakeEvents();
+    // }
+
+    // populate history on fresh load
+    var target = window.top.location.pathname + window.top.location.search;
+    if (target.indexOf('/mc') === 0) {
+        target = target.split('/mc')[1];
+    }
+	if(noMcStuffOnLoad){
+		target = originalLocationPath;
+	}
+    if(!window.noMc){
+        fastLoad(target, true, false, true);
+    }
+
+
+});
+function enableTimeSpecificFields(_checked, _valueClass, _rangeClass) {
+    if (_valueClass) $('.' + _valueClass).prop('disabled', _checked);
+    if (_rangeClass) $('.' + _rangeClass).prop('disabled', !_checked);
+}
+function toggleDisabledAsNeeded(_el, _targetValue, _enableClass, _disableClass) {
+    if (_el.value === _targetValue) {
+        if (_enableClass) $('.' + _enableClass).prop('disabled', false);
+        if (_disableClass) $('.' + _disableClass).prop('disabled', true);
+    }
+    else {
+        if (_enableClass) $('.' + _enableClass).prop('disabled', true);
+        if (_disableClass) $('.' + _disableClass).prop('disabled', false);
+    }
+}
+function toggleVisibilityAsNeeded(_el, _targetValue, _visibleClass, _hiddenClass) {
+    if (_el.value === _targetValue) {
+        if (_visibleClass) $('.' + _visibleClass).removeClass('d-none');
+        if (_hiddenClass) $('.' + _hiddenClass).addClass('d-none');
+    }
+    else {
+        if (_visibleClass) $('.' + _visibleClass).addClass('d-none');
+        if (_hiddenClass) $('.' + _hiddenClass).removeClass('d-none');
+    }
+}
+var fastCache = {};
+
+function initFastLoad(_parent = false) {
+
+    var allAs = $('a[href]:not([onclick]):not([href="#"]):not([native])');
+    if (_parent) {
+        allAs = _parent.find('a[href]:not([onclick]):not([href="#"]):not([native])');
+    }
+
+    // clear cache
+    if (!_parent) {
+        fastCache = {};
+    }
+    else {
+        allAs.each(function () {
+            if (typeof fastCache[this.href] !== 'undefined') {
+                delete fastCache[this.href];
+            }
+        });
+    }
+
+    // find links without event handlers
+    allAs.each(function () {
+        if (!$(this).closest('[moe]').length) {
+            if ($(this).closest('.dropdown-menu[aria-labelledby="practice-management"]').length) {
+                enableFastLoad(this, true);
+            } else {
+                // var handlers = findEventHandlers('click', this);
+                // if (!handlers || !handlers.length) {
+                    enableFastLoad(this);
+                // }
+            }
+        }
+    });
+
+    function enableFastLoad(_a, _menuItem = false) {
+        $(_a)
+            .off('click.fast-load')
+            .on('click.fast-load', function () {
+                fastLoad(this.href, true, true);
+                $('.dropdown-menu[aria-labelledby="practice-management"]')
+                    .removeClass('show')
+                    .prev('.dropdown-toggle').attr('aria-expanded', 'false');
+                return false;
+            });
+        // console.info('FastLoad enabled for ' + _a.innerText + ' [' + _a.href + ']');
+    }
+
+    // fast cache
+    // allAs = $('a[href]:not([onclick]):not([href="#"])');
+    // allAs.each(function () {
+    //     var a = this;
+    //     $.get(a.href, function (_data) {
+    //         fastCache[a.href] = _data;
+    //     });
+    // });
+
+}
+
+function onFastLoaded(_data, _href, _history, _target = null) {
+
+    if (!Number.isInteger(_data)) {
+        _data = '<div>' + _data + '</div>';
+
+        // do for each element in _target
+        let responseError = false;
+        let originalTarget = _target;
+        _target = _target ? _target : '.stag-content';
+        _target = _target.split(',').map(_x => $.trim(_x));
+        for (let i = 0; i < _target.length; i++) {
+            let t = _target[i];
+            let targetElement = $(t).first();
+            if(targetElement.length) {
+                let sourceElement = $(_data).find(t).first();
+                if (sourceElement && sourceElement.length) {
+                    targetElement.html(sourceElement.html());
+                    initFastLoad(targetElement);
+                    console.log('ALIX loaded element: ' + t);
+                }
+                else {
+                    responseError = true;
+                }
+            }
+            else {
+                responseError = true;
+            }
+        }
+
+        if (!responseError) {
+            hideMask();
+            hideMoeFormMask();
+            window.setTimeout(function () {
+                initCreateNote();
+                initQuillEdit();
+                initPrimaryForm();
+                initPatientPresenceIndicator();
+                initFileInputs();
+                initMoes();
+                runMCInitializers(originalTarget);
+                if (window.top.currentMcUrl.split('?')[0] !== window.top.location.href.split('?')[0]) {
+                    $(window).scrollTop(0);
+                }
+                window.top.currentMcUrl = window.top.location.href;
+            }, 0);
+        }
+        else {
+            $('.stag-content').first().html('<p class="text-danger p-3 small">Error on page: <b>' + _href + '</b></p>');
+            hideMask();
+            hideMoeFormMask();
+        }
+    } else {
+        // fallback
+        let msg = 'Unable to open page: ';
+        switch (_data) {
+            case 403:
+                msg = 'You do not have access to this page: ';
+                break;
+            case 404:
+                msg = 'Page not found: ';
+                break;
+            case 500:
+                msg = 'Error on page: ';
+                break;
+            case 0: // not logged in - refresh top level window to go to login
+                window.top.location = '/';
+                break;
+        }
+        console.warn('MC: Target page failed: ' + _href);
+        $('.stag-content').first().html('<p class="text-danger p-3 small"><b>' + _data + '</b> - ' + msg + '<b>' + _href + '</b></p>');
+        hideMask();
+        hideMoeFormMask();
+    }
+    $('html, body').removeClass('no-scroll');
+}
+
+var fastReload = function(_target = null) {
+    var targetLocation = window.top.location.pathname + window.top.location.search;
+    if(targetLocation.indexOf('/mc') === 0) {
+        targetLocation = targetLocation.substr(3);
+    }
+    if(targetLocation === '' || targetLocation[0] !== '/') targetLocation = '/' + targetLocation;
+    fastLoad(targetLocation, false, false, false, _target);
+    return false;
+}
+
+function fastLoad(_href, _history = true, _useCache = true, _replaceState = false, _target = null) {
+
+    let domPro = $(window.top.document.body).attr('data-pro-uid'),
+        lsPro = window.top.localStorage.currentProUid;
+    if(lsPro && domPro && lsPro !== domPro) {
+        console.warn('ALIX Looks like you have a session as another pro on another tab. Refreshing window.top...');
+        window.top.location.href = _href;
+        return false;
+    }
+
+    showMask();
+
+    if (_href === '') _href = '/';
+
+    // push state
+    if (_history && window.parent === window.top) {
+        var target = _href;
+        if (target.indexOf('//') !== -1) {
+            target = target.split('//')[1];
+            if (target.indexOf('/') !== -1) {
+                target = target.substr(target.indexOf('/') + 1);
+            }
+        }
+        if (target[0] === '/') target = target.substr(1);
+        if (_replaceState) {
+            window.top.history.replaceState(target, null, '/mc/' + target);
+        }
+        else {
+            window.top.history.pushState(target, null, '/mc/' + target);
+        }
+    }
+
+    // dont show top nav if in iframe
+    if(window !== window.top && window.parent !== window.top) {
+        $('body').addClass('in-iframe');
+    }
+
+    if (_useCache && !!fastCache[_href]) {
+        onFastLoaded(fastCache[_href], _href, _history, _target);
+    } else {
+
+        let cleanedHREF = _href;
+        if(cleanedHREF.length > 1 && cleanedHREF[cleanedHREF.length - 1] === '/') {
+            cleanedHREF = cleanedHREF.substr(0, cleanedHREF.length - 1);
+        }
+        if(cleanedHREF.length > 2) {
+            cleanedHREF = cleanedHREF.replace('/?', '?');
+        }
+
+        if(cleanedHREF.length > 0 && cleanedHREF[0] === '?') cleanedHREF = '/' + cleanedHREF;
+
+        $.get(cleanedHREF, function (_data) {
+            onFastLoaded(_data, _href, _history, _target);
+        }).fail(function (_jqXhr) {
+            onFastLoaded(_jqXhr.status, _href, _history, _target);
+        });
+    }
+}
+
+function initPrimaryForm(_form = false) {
+    var primaryForm = _form ? _form : $('.primary-form:visible');
+    if (primaryForm.length) {
+        primaryForm = primaryForm.first();
+        var rte = primaryForm.find('[contenteditable="true"]').first();
+        if (rte.length) {
+            rte.focus().select();
+        }
+        else {
+            if (primaryForm.find('[autofocus]:visible').length) {
+                primaryForm.find('[autofocus]:visible').first().focus().select();
+            }
+            else {
+                primaryForm.find('input:not([type="hidden"]):visible, textarea:visible, select:visible').first().focus().select();
+            }
+        }
+    }
+}
+
+function openInRHS(_url) {
+    window.top.showRHS();
+    var icon = $('.stag_rhs_toggle i');
+    icon.removeClass().addClass('fa fa-arrow-right');
+    window.top.openInRHS(_url);
+    return false;
+}
+
+function initCreateNote() {
+    $(document)
+        .off('click.create-note', '.create-auto-note-trigger')
+        .on('click.create-note', '.create-auto-note-trigger', function () {
+            createNewNote($(this).attr('data-patient-uid'), $(this).attr('data-hcp-uid'), $(this).attr('data-effective-date'));
+        });
+    if ($('select[name="hasMcpDoneOnboardingVisit"]').length) {
+        $('select[name="hasMcpDoneOnboardingVisit"]').trigger('change');
+    }
+}
+
+function createNewNote(_patientUid, _hcpUid, _date) {
+    hideMoeFormMask();
+    showMask();
+    $.post('/api/note/createUsingFreeTextHtml', {
+        clientUid: _patientUid,
+        hcpProUid: _hcpUid,
+        effectiveDateEST: _date,
+    }, function (_data) {
+        hideMask();
+        if (!_data.success) {
+            toastr.error(_data.message);
+        }
+        else {
+            fastLoad('/patients/view/' + _patientUid + '/notes/view/' + _data.data, true, false);
+        }
+    }, 'json');
+}
+
+function initQuillEdit(_selector = '.note-content[auto-edit]') {
+
+    $(document)
+        .off('click.enable-edit', '.note-content:not([auto-edit]):not(.readonly)')
+        .on('click.enable-edit', '.note-content:not([auto-edit]):not(.readonly)', function () {
+            $(this).attr('auto-edit', 1);
+            initQuillEdit();
+            initPrimaryForm();
+            initPatientPresenceIndicator();
+        });
+
+    if (!$(_selector).length) return;
+    var noteUid = $(_selector).attr('data-note-uid');
+    var qe = new Quill(_selector, {
+        theme: 'snow',
+        modules: stagQuillConfig
+    });
+    var toolbar = $(qe.container).prev('.ql-toolbar');
+    var saveButton = $('<button class="btn btn-sm btn-primary w-auto px-3 py-0 text-sm text-white save-note-content">Save</button>');
+    toolbar.append(saveButton);
+    saveButton.on('click', function () {
+        $.post('/api/note/putFreeTextHtml', {
+            uid: noteUid,
+            freeTextHtml: qe.root.innerHTML,
+        }, function (_data) {
+            if (!_data.success) {
+                toastr.error(_data.message);
+            }
+            else {
+                // toastr.success('Note saved');
+                // saveButton.prop('disabled', true);
+                fastLoad(window.top.location.pathname.substr(3), false, false);
+            }
+        }, 'json');
+    });
+
+    // give a unique id to this editor instance
+    var editorID = Math.ceil(Math.random() * 99999);
+
+    // add button for new shortcut
+    var newSCButton = $('<button class="btn btn-sm btn-default w-auto px-2 ml-2 border py-0 ' +
+        'text-sm add-shortcut" data-editor-id="' + editorID + '">+ Shortcut</button>');
+    toolbar.append(newSCButton);
+
+    // qe.on('text-change', function() {
+    //     saveButton.prop('disabled', false);
+    // });
+    $('.ql-editor[contenteditable]')
+        .attr('data-editor-id', editorID)
+        .attr('with-shortcuts', 1);
+}
+
+var patientPresenceTimer = false;
+function initFileInputs() {
+    $(document)
+        .off('change', 'input[type="file"]')
+        .on('change', 'input[type="file"]', function(_e) {
+            if(_e.target.files && _e.target.files.length) {
+                $(this).attr('selected-file', _e.target.files[0].name);
+            }
+            else {
+                $(this).attr('selected-file', 'No file chosen');
+            }
+            return false;
+        });
+    $('input[type="file"]').attr('selected-file', 'No file chosen');
+}
+function initPatientPresenceIndicator() {
+    return false;
+    if (patientPresenceTimer !== false) {
+        window.clearInterval(patientPresenceTimer);
+        patientPresenceTimer = false;
+        console.log('Cancelled previous timer!');
+    }
+    var elem = $('.patient-presence-indicator[data-patient-uid]');
+    if (elem.length) {
+        var patientUid = elem.attr('data-patient-uid');
+        patientPresenceTimer = window.setInterval(function () {
+            var elem = $('.patient-presence-indicator[data-patient-uid]');
+            if (elem.length) {
+                var patientUid = elem.attr('data-patient-uid');
+                $.get('/patients/' + patientUid + '/presence', function (_data) {
+                    if (_data.online) {
+                        elem.addClass('online');
+                    }
+                    else {
+                        elem.removeClass('online');
+                    }
+                }, 'json');
+            }
+        }, 15000); // once in 15 seconds
+    }
+}
+
+// not really the place for this!
+// find a better place to put this
+window.fillJsonDataField = function(form, field = "data") {
+    // add [data-name] values to payload
+    if(form.is('[data-field-name]')) {
+        field = form.attr('data-field-name');
+    }
+    let dataField = form.find('[name="' + field + '"]').first();
+    let parsed = null;
+    if(dataField.val()) {
+        parsed = JSON.parse(dataField.val());
+    }
+    form.find('[data-name]').each(function() {
+        if(!parsed) parsed = {};
+
+        let keys = $(this).attr('data-name').split('->');
+        let currentNode = parsed;
+        for (let i = 0; i < keys.length; i++) {
+            if(i !== keys.length - 1) {
+                if(typeof currentNode[keys[i]] === 'undefined') {
+                    currentNode[keys[i]] = {};
+                }
+                currentNode = currentNode[keys[i]];
+            }
+            else {
+                if($(this).is(':checkbox')) {
+                    currentNode[keys[i]] = $(this).prop('checked');
+                }
+                else {
+                    currentNode[keys[i]] = $(this).val();
+                }
+            }
+        }
+
+    });
+    if(parsed) {
+        dataField.val(JSON.stringify(parsed));
+    }
+    return parsed;
+}
+window.saveVisitForm = function(_trigger, _silent = false, _close = false, _doneCallback = null) {
+    let form = $(_trigger).closest('form');
+
+    if (!_silent && !form[0].checkValidity()) {
+        form[0].reportValidity();
+        return false;
+    }
+
+    let parsed = fillJsonDataField(form);
+
+    let closeOnSave = false, noteSection = form.closest('.note-section');
+    if($(_trigger).closest('[visit-moe]').is('[close-on-save]')) {
+        closeOnSave = true;
+    }
+
+    // disallow-if-value-same-as
+    let compareWith = false;
+    if(form.find('.disallow-if-value-same-as')) {
+        compareWith = $.trim(form.find('.disallow-if-value-same-as').text());
+        if(compareWith && parsed) {
+            if(!parsed.value) {
+                alert('Value cannot be empty!');
+                return false;
+            }
+            let newValue = $('<div/>').html(parsed.value).text().replace(/[^a-zA-Z0-9]/g, '');
+            if(newValue === '') {
+                alert('Value cannot be empty!');
+                return false;
+            }
+            if(newValue === compareWith) {
+                alert('New value should be different from the previous value!');
+                return false;
+            }
+        }
+    }
+
+    if(!_silent) showMask();
+
+    $.post(form.attr('url'), form.serialize(), _data => {
+        if(!hasResponseError(_data)) {
+            if(typeof window.updateAllSegmentsInResponse !== 'undefined') {
+                window.updateAllSegmentsInResponse(_data, true, _silent);
+            }
+            if(typeof window.refreshRHSSidebar !== 'undefined') {
+                window.refreshRHSSidebar();
+            }
+            if(!_silent) {
+                hideMask();
+                if(noteSection.length) {
+                    if (closeOnSave) {
+                        noteSection.removeClass('edit');
+                        let segmentUid = form.find('[name="segmentUid"]').first();
+                        segmentUid = segmentUid.length ? segmentUid.val() : false;
+                        if (segmentUid) {
+                            window.setTimeout(() => {
+                                $('.note-tree-node>a[data-segment-uid="' + segmentUid + '"]').trigger('click');
+                            }, 250);
+                        }
+                    }
+                }
+                if($(_trigger).closest('[visit-moe]').closest('.stag-popup').length) {
+                    refreshDynamicStagPopup();
+                }
+            }
+            if(_close) {
+                closeStagPopup();
+            }
+            if(!!_doneCallback) {
+                _doneCallback();
+            }
+        }
+    }, 'json');
+    return false;
+};
+window.initSegmentMoes = function(_parent) {
+
+    $('body')
+        .off('mousedown.visit-moe-outside-click')
+        .on('mousedown.visit-moe-outside-click', function (e) {
+            if ($(e.target).closest('[visit-moe]').length ||
+                $(e.target).closest('#create-shortcut-form').length ||
+                $(e.target).is('#create-shortcut-form') ||
+                $(e.target).is('.stag-shortcuts .sc') ||
+                $(e.target).closest('.ui-datepicker').length) {
+                return;
+            }
+            $('[visit-moe] [url]:not([show])').hide();
+        });
+
+    _parent.find('[visit-moe] [submit]')
+        .off('click.visit-moe-submit')
+        .on('click.visit-moe-submit', function() {
+            saveVisitForm(this);
+            return false;
+        });
+
+    _parent.find('[visit-moe]>a[start]')
+        .off('click.visit-moe-show')
+        .on('click.visit-moe-show', function () {
+            $('[visit-moe] [url]:not([show])').hide();
+            $(this)
+                .closest('[visit-moe]')
+                .find('form[url]')
+                .show();
+            return false;
+        });
+
+    _parent.find('[visit-moe] [cancel]')
+        .off('click.visit-moe-cancel')
+        .on('click.visit-moe-cancel', function() {
+            $(this).closest('[visit-moe]').find('[url]:not([show])').hide();
+            if($(this).closest('[visit-moe]').is('[close-on-cancel]')) {
+                $(this).closest('.note-section').removeClass('edit');
+            }
+            return false;
+        });
+
+    $(document)
+        .off('keydown.visit-moe-escape')
+        .on('keydown.visit-moe-escape', function (e) {
+            if(e.which === 27) {
+                if(!isEventConsumed(e)) {
+                    let visibleMoes = $('[visit-moe] [url]:not([show]):visible');
+                    if (visibleMoes.length) {
+                        visibleMoes.hide();
+                        markEventAsConsumed(e);
+                        return false;
+                    }
+                }
+            }
+        });
+};
+window.isEventConsumed = function(_e) {
+    return _e && _e.originalEvent && _e.originalEvent.stagCosumed
+};
+window.markEventAsConsumed = function(_e) {
+    if(_e && _e.originalEvent) {
+        _e.originalEvent.stagCosumed = true;
+    }
+};

+ 41 - 0
js/option-list.js

@@ -0,0 +1,41 @@
+(function() {
+    function showOptionsList(_input) {
+        let ol = $(_input).next('.data-option-list');
+        if(ol.length && !ol.is(':visible')) {
+            ol.show();
+        }
+    }
+    function hideOptionsList(_input) {
+        let ol = $(_input).next('.data-option-list');
+        if(ol.length && ol.is(':visible')) {
+            ol.hide();
+        }
+    }
+    function init() {
+        $(document)
+            .off('mousedown.option-list', '.data-option-list>div')
+            .on('mousedown.option-list', '.data-option-list>div', function(_e) {
+                _e.stopPropagation();
+                _e.preventDefault();
+                markEventAsConsumed(_e);
+                $(this).parent().prev('input[data-option-list]').val('').focus();
+                document.execCommand('insertText', false, $(this).text());
+                $(this).closest('.data-option-list').hide();
+                return false;
+            });
+        $(document)
+            .off('click.trigger-option-list', 'input[data-option-list]')
+            .on('click.trigger-option-list', 'input[data-option-list]', function() {
+                showOptionsList(this);
+            })
+            .off('focus.trigger-option-list', 'input[data-option-list]')
+            .on('focus.trigger-option-list', 'input[data-option-list]', function() {
+                showOptionsList(this);
+            })
+            .off('blur.trigger-option-list', 'input[data-option-list]')
+            .on('blur.trigger-option-list', 'input[data-option-list]', function() {
+                hideOptionsList(this);
+            });
+    }
+    addMCInitializer('option-list', init);
+})();

+ 172 - 0
js/pro-suggest.js

@@ -0,0 +1,172 @@
+// pro suggest functionality
+(function() {
+
+    let suggestionsOuter = null;
+
+    const debounce = (func, wait) => {
+        let timeout;
+        return function executedFunction(...args) {
+            const later = () => {
+                clearTimeout(timeout);
+                func(...args);
+            };
+            clearTimeout(timeout);
+            timeout = setTimeout(later, wait);
+        };
+    };
+
+    var lastTerm = '';
+    var returnedFunction = debounce(function (elem) {
+        var term = elem.val();
+        if (!!term && lastTerm !== term) {
+            $.get('/pro-suggest?term=' + $.trim(term) + '&type=' + elem.attr('provider-type'), function (_data) {
+                suggestionsOuter.html(_data).removeClass('d-none');
+            });
+            lastTerm = term;
+        } else {
+            suggestionsOuter.addClass('d-none');
+        }
+    }, 250);
+
+    function handleKeydown(elem, e) {
+        let term = $.trim(elem.val());
+        let activeItem = suggestionsOuter.find('.suggest-item.active');
+        switch (e.which) {
+            case 27:
+                suggestionsOuter.addClass('d-none');
+                markEventAsConsumed(e);
+                return false;
+            case 38:
+                if (activeItem.prev().length) {
+                    activeItem.prev()
+                        .addClass('active')
+                        .siblings().removeClass('active');
+                    activeItem = suggestionsOuter.find('.suggest-item.active');
+                    if (activeItem.length) {
+                        activeItem[0].scrollIntoView();
+                    }
+                }
+                return false;
+            case 40:
+                if (activeItem.next().length) {
+                    activeItem.next()
+                        .addClass('active')
+                        .siblings().removeClass('active');
+                    activeItem = suggestionsOuter.find('.suggest-item.active');
+                    if (activeItem.length) {
+                        activeItem[0].scrollIntoView();
+                    }
+                }
+                return false;
+            case 13:
+                if (activeItem.length) {
+                    activeItem.first().click();
+                }
+                return false;
+            default:
+                if (!!term) {
+                    suggestionsOuter
+                        .html('<span class="d-block no-suggest-items">Searching...</span>')
+                        .removeClass('d-none');
+                    returnedFunction(elem);
+                } else {
+                    suggestionsOuter.addClass('d-none');
+                }
+                break;
+        }
+    }
+
+    function handleKeypress(elem, e) {
+        var term = $.trim(elem.val());
+        if (!!term) {
+            suggestionsOuter
+                .html('<span class="d-block no-suggest-items">Searching...</span>')
+                .removeClass('d-none');
+            returnedFunction(elem);
+        } else {
+            suggestionsOuter.addClass('d-none');
+        }
+    }
+
+    window.initProSuggest = function() {
+
+        // make select[provider-search] hidden & insert a textbox with pro-suggest
+        $('select[provider-search]:not([pro-suggest-initialized]):not([no-auto-pro-suggest-init])').each(function() {
+            let elem = $(this);
+            elem.next('.pro-suggest-input').remove();
+            elem.next('.pro-suggestions-container').remove();
+            let input = $('<input type="text" placeholder="Pro">').addClass('pro-suggest-input form-control form-control-sm').insertAfter(elem);
+            input.attr('provider-type', elem.attr('provider-type'));
+            $('<div class="pro-suggestions-container position-relative">' +
+                '<div class="suggestions-outer pro-suggestions position-absolute d-none"></div>' +
+                '</div>').insertAfter(input);
+            elem.hide();
+
+            let proUid = elem.attr('data-pro-uid');
+            if(!!proUid) {
+                $.get('/pro-display-name/' + proUid, function(_data) {
+                    input.val(_data).data('original', _data);
+                    elem.empty().append($('<option value="' + proUid + '" selected/>').text(_data));
+                });
+            }
+            else {
+                input.val(elem.attr('data-pro-name')).data('original', elem.attr('data-pro-name'));
+            }
+
+            if(elem.is('[required]')) {
+                input.attr('required', 'required');
+            }
+
+            input
+                .off('keydown.pro-suggest')
+                .on('keydown.pro-suggest', function (e) {
+                    suggestionsOuter = $(this).next('.pro-suggestions-container').find('>.suggestions-outer');
+                    return handleKeydown($(this), e);
+                })
+                .off('keypress.pro-suggest')
+                .on('keypress.pro-suggest', function (e) {
+                    suggestionsOuter = $(this).next('.pro-suggestions-container').find('>.suggestions-outer');
+                    return handleKeypress($(this), e);
+                });
+                // .off('blur.pro-suggest')
+                // .on('blur.pro-suggest', function (e) {
+                //     window.setTimeout(() => {
+                //         $(this).next('.pro-suggestions-container').find('>.suggestions-outer').addClass('d-none');
+                //         $(this).val($(this).data('original'));
+                //     }, 50);
+                // });
+
+            $(this).attr('pro-suggest-initialized', 1);
+        });
+
+        $(document).off('click', '.suggest-item.pro-suggest[data-target-uid]');
+        $(document).on('click', '.suggest-item.pro-suggest[data-target-uid]', function () {
+
+            $('.suggestions-outer.pro-suggestions').addClass('d-none');
+
+            let uid = $(this).attr('data-target-uid'),
+                label = $.trim($(this).text());
+
+            // set select value
+            let select = $(this).closest('.position-relative')
+                .prev('.pro-suggest-input')
+                .prev('select[provider-search]');
+            select.empty().append($('<option value="' + uid + '" selected/>').text(label));
+            select.val(uid).trigger('change');
+            select.trigger('pro-changed');
+
+            // set input value
+            $(this).closest('.position-relative')
+                .prev('.pro-suggest-input')
+                .val(label)
+                .data('original', label)
+                .trigger('change');
+
+            return false;
+        });
+
+    }
+    addMCInitializer('pro-suggest', initProSuggest);
+})();
+
+

+ 302 - 0
js/shortcut.js

@@ -0,0 +1,302 @@
+// 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(
+                    $('<div/>')
+                        .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 = $('<div/>')
+            .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);
+})();
+
+

+ 22 - 0
js/show-on-click.js

@@ -0,0 +1,22 @@
+(function() {
+    function init() {
+        $(document)
+            .off('click.show-on-click', '.show-on-click')
+            .on('click.show-on-click', '.show-on-click', function(_e) {
+                if($(this).text() === $(this).attr('data-value')) {
+                    $(this).text('Click to view');
+                }
+                else {
+                    $(this).text($(this).attr('data-value'));
+
+                    // fire pro event
+                    $.post('/api/proEvent/create', {
+                        proUid: $(this).attr('data-pro-uid'),
+                        event: $(this).attr('data-event'),
+                    }, () => {}, 'json');
+                }
+                return false;
+            });
+    }
+    addMCInitializer('show-on-click', init);
+})();

+ 49 - 0
js/stag-collapsible-card.js

@@ -0,0 +1,49 @@
+(function() {
+    function init() {
+        $('.card[stag-collapsible-card]:not([stag-collapsible-card-initialized])').each(function() {
+            let header = $(this).find('>.card-header').first();
+            if(header.length) {
+                let html = header.html();
+                let newHeader = $('<div class="d-flex align-items-center"></div>');
+                newHeader.append(html);
+                let collapseTrigger = $('<a href="#" class="stag-collapse-trigger ml-auto" />');
+                collapseTrigger.append('<i class="fa fa-chevron-up if-not-collapsed"/>');
+                collapseTrigger.append('<i class="fa fa-chevron-down if-collapsed"/>');
+                newHeader.append(collapseTrigger);
+                header
+                    .empty()
+                    .append(newHeader);
+            }
+            if(!!$(this).attr('stag-collapsible-card')) {
+                let state = localStorage['collapseState_' + $(this).attr('stag-collapsible-card')];
+                if(state === 'collapsed') {
+                    $(this).attr('collapsed', 1);
+                }
+                else if(state === 'not-collapsed') {
+                    $(this).removeAttr('collapsed');
+                }
+            }
+            $(this).attr('stag-collapsible-card-initialized', 1);
+        });
+
+        $(document)
+            .off('click.stag-collapse-toggle', '.card[stag-collapsible-card][stag-collapsible-card-initialized] .stag-collapse-trigger')
+            .on('click.stag-collapse-toggle', '.card[stag-collapsible-card][stag-collapsible-card-initialized] .stag-collapse-trigger', function() {
+                let card = $(this).closest('.card');
+                if(card.is('[collapsed]')) {
+                    card.removeAttr('collapsed');
+                    if(!!card.attr('stag-collapsible-card')) {
+                        localStorage['collapseState_' + card.attr('stag-collapsible-card')] = 'not-collapsed';
+                    }
+                }
+                else {
+                    card.attr('collapsed', 1);
+                    if(!!card.attr('stag-collapsible-card')) {
+                        localStorage['collapseState_' + card.attr('stag-collapsible-card')] = 'collapsed';
+                    }
+                }
+                return false;
+            });
+    }
+    addMCInitializer('stag-collapsible-card', init);
+}).call(window);

+ 284 - 0
js/stag-popup.js

@@ -0,0 +1,284 @@
+window.stagPopupsQueue = [];
+function showStagPopup(_key, _noAutoFocus) {
+    /*$('html, body').addClass('no-scroll');
+    $(window.top.document.body).find('#stag_mcp_lhs').addClass('no-scroll');*/
+    let stagPopup = $('[stag-popup-key="' + _key + '"]');
+    if(stagPopup.is('.show')) return false;
+    else {
+        let zIndex = $('.stag-popup.show[stag-popup-key]').last().css('z-index');
+        if(zIndex) zIndex++; else zIndex = 100;
+        stagPopup.css('z-index', zIndex);
+    }
+    stagPopup.addClass('show');
+    stagPopup.find('[moe][initialized]').removeAttr('initialized');
+    initMoes();
+    initFileInputs();
+    if(!_noAutoFocus) {
+        window.setTimeout(function() {
+            stagPopup.find('input[type="text"]:not([readonly]):visible,select:visible').first().focus();
+        }, 150);
+    }
+    stagPopupsQueue.push(stagPopup);
+    return false;
+}
+function submitStagPopup(_form) {
+    if(!_form[0].checkValidity()) {
+        _form[0].reportValidity();
+        return false;
+    }
+    showMask();
+    $.post(_form.attr('action'), _form.serialize(), function(_data) {
+        stagPopupsQueue = [];
+        fastReload();
+    });
+    return false;
+}
+function closeStagPopup(_noEvent = false) {
+    hideMoeFormMask();
+    if(!stagPopupsQueue.length) return false;
+    let popup = stagPopupsQueue[stagPopupsQueue.length - 1];
+    let closeAll = !!popup.attr('close-all-with-self');
+    popup.removeClass('show');
+    stagPopupsQueue.splice(stagPopupsQueue.length - 1, 1);
+    if(closeAll) {
+        while(stagPopupsQueue.length) {
+            closeStagPopup(true);
+        }
+    }
+    else {
+        if(popup.is('[update-parent]') && !_noEvent) {
+            if(stagPopupsQueue.length) {
+                refreshDynamicStagPopup();
+            }
+            else {
+                fastReload(popup.is('[update-target]') ? popup.attr('update-target') : '');
+                return;
+            }
+        }
+    }
+
+    // remove from the DOM
+    if(popup.is('.dynamic-popup')) popup.remove();
+
+    // if all closed
+    if(!stagPopupsQueue.length) {
+        $('html, body').removeClass('no-scroll');
+        $(window.top.document.body).find('#stag_mcp_lhs').removeClass('no-scroll');
+        if(!_noEvent) {
+            $('body').trigger('stag-popup-closed');
+        }
+    }
+    return false;
+}
+function convertContentLinksForInPopupNavigation(popup) {
+    popup.find('.stag-popup-content-inner').find('a').each(function() {
+        let a = $(this);
+        if(!(!a.attr('href') ||
+            a.is('[open-in-stag-popup]') ||
+            a.is('[native]') ||
+            a.attr('href') === '#' ||
+            a[0].onclick)) {
+            a.attr('replace-in-stag-popup', 1);
+        }
+    });
+}
+function openDynamicStagPopup(_url, initer, title, updateParent, style = '', replace = false, updateTarget = null) {
+    let url = _url;
+    if(url.indexOf('popupmode') === -1) {
+        url += (url.indexOf('?') !== -1 ? '&' : '?') + 'popupmode=1';
+    }
+    showMask();
+    window.noMc = true;
+    $.get(url, (_data) => {
+
+        let popup = null;
+        if(replace) {
+            if(!stagPopupsQueue.length) {
+                console.error('No stag-popup currently visible!');
+                return false;
+            }
+            popup = stagPopupsQueue[stagPopupsQueue.length - 1];
+            if(!popup.is('.dynamic-popup')) {
+                console.error('Topmost stag-popup is not dynamic!');
+                return false;
+            }
+            popup.attr('stag-popup-key', url);
+        }
+        else {
+            popup = $('.dynamic-popup[stag-popup-key="' + url + '"]');
+            if(!popup.length) {
+                $('main.stag-content').append(
+                    '<div class="stag-popup ' + (style ? style : 'stag-popup-lg') + ' dynamic-popup mcp-theme-1" stag-popup-key="' + url + '">' +
+                    '<div class="stag-popup-content p-0">' +
+                    '<h3 class="stag-popup-title mb-0 mt-3 mx-3 pb-0 border-bottom-0"><span></span>' +
+                    '<a href="#" class="ml-auto text-secondary" onclick="return closeStagPopup()"><i class="fa fa-times-circle"></i></a>\n' +
+                    '</h3>' +
+                    '<div class="stag-popup-content-inner"></div>' +
+                    '</div>' +
+                    '</div>'
+                );
+                popup = $('.dynamic-popup[stag-popup-key="' + url + '"]');
+            }
+        }
+
+        popup.attr('mc-initer', initer);
+        popup.find('.stag-popup-title>span').html(title);
+        popup.find('.stag-popup-content-inner').html(_data);
+
+        convertContentLinksForInPopupNavigation(popup);
+
+        if(!replace) {
+            if(updateParent) {
+                popup.attr('update-parent', 1);
+            }
+            else {
+                popup.removeAttr('update-parent');
+            }
+            showStagPopup(url, true);
+
+            if(updateTarget) {
+                popup.attr('update-target', updateTarget);
+            }
+        }
+
+        if(initer) runMCInitializer(initer);
+        runMCInitializer('pro-suggest'); // not the place for this! Move to better place.
+        initMoes();
+        hideMask();
+        initFileInputs();
+    }).fail(() => {
+        toastr.error('Unable to open ' + _url);
+        hideMask();
+    });
+}
+function isDynamicStagPopupPresent() {
+    if(!stagPopupsQueue.length) return false;
+    let popup = stagPopupsQueue[stagPopupsQueue.length - 1];
+    if(popup.is('.dynamic-popup')) return true;
+    return false;
+}
+function refreshDynamicStagPopup(_url = false, _target = null) {
+    if(!stagPopupsQueue.length) return false;
+    let popup = stagPopupsQueue[stagPopupsQueue.length - 1];
+    if(popup.is('.dynamic-popup')) {
+        showMask();
+        window.noMc = true;
+        if(_url) {
+            popup.attr('stag-popup-key', _url);
+        }
+        let url = popup.attr('stag-popup-key'),
+            initer = popup.attr('mc-initer');
+        $.get(url, (_data) => {
+            if(_target) {
+                _data = '<div>' + _data + '</div>';
+                _target = _target.split(',').map(_x => $.trim(_x));
+                for (let i = 0; i < _target.length; i++) {
+                    let t = _target[i];
+                    let targetElement = $(t).first();
+                    if(targetElement.length) {
+                        let sourceElement = $(_data).find(t).first();
+                        if (sourceElement && sourceElement.length) {
+                            targetElement.html(sourceElement.html());
+                            initFastLoad(targetElement);
+                            console.log('Replaced ' + t);
+                        }
+                        else {
+                            console.warn(t + ' not found in returned content');
+                        }
+                    }
+                    else {
+                        console.warn(t + ' not found in existing content');
+                    }
+                }
+            }
+            else {
+                popup.find('.stag-popup-content-inner').html(_data);
+            }
+            convertContentLinksForInPopupNavigation(popup);
+            if(initer) runMCInitializer(initer);
+            runMCInitializer('pro-suggest');
+            initMoes();
+            hideMask();
+            initFileInputs();
+        });
+    }
+    return false;
+}
+function hasResponseError(_data) {
+    let msg = 'Unknown error!';
+    if (_data) {
+        if (_data.success) return false;
+        else if (_data.message) msg = _data.message;
+    }
+    toastr.error(msg);
+    return true;
+}
+(function() {
+    window.initStagPopupEvents = function () {
+        $(document)
+            .off('mousedown.stag-popup-discard', '.stag-popup')
+            .on('mousedown.stag-popup-discard', '.stag-popup', function(_e) {
+                if($(_e.target).is('.stag-popup') && !isEventConsumed(_e)) {
+                    closeStagPopup();
+                    return false;
+                }
+            });
+        // catch ESC and discard any visible popups
+        $(document)
+            .off('keydown.stag-popup-escape')
+            .on('keydown.stag-popup-escape', function (e) {
+                if(e.which === 27) {
+                    if(!isEventConsumed(e)) {
+                        if(stagPopupsQueue.length) {
+                            if($('.stag-popup.show [moe] [url]:not([show]):visible').length || $('.stag-popup.show [visit-moe] [url]:not([show]):visible').length) {
+                                return;
+                            }
+                            closeStagPopup();
+                            markEventAsConsumed(e);
+                            return false;
+                        }
+                    }
+                }
+            });
+
+        $(document)
+            .off('click.open-in-stag-popup', '[open-in-stag-popup]')
+            .on('click.open-in-stag-popup', '[open-in-stag-popup]', function() {
+                let trig = $(this);
+                openDynamicStagPopup(
+                    trig.attr('href'),
+                    trig.attr('mc-initer'),
+                    trig.attr('title'),
+                    trig.is('[update-parent]'),
+                    trig.attr('popup-style'),
+                    false,
+                    trig.is('[update-target]') ? trig.attr('update-target') : null
+                );
+                return false;
+            });
+
+        $(document)
+            .off('click.replace-in-stag-popup', '[replace-in-stag-popup]')
+            .on('click.replace-in-stag-popup', '[replace-in-stag-popup]', function() {
+                let trig = $(this);
+                openDynamicStagPopup(
+                    trig.attr('href'),
+                    trig.attr('mc-initer'),
+                    trig.attr('title'),
+                    null, // will be inherited when replacing
+                    null, // will be inherited when replacing
+                    true,
+                    null
+                );
+                return false;
+            });
+
+        $(document)
+            .off('click.close-stag-popup', '.btn-close-stag-popup')
+            .on('click.close-stag-popup', '.btn-close-stag-popup', function() {
+                closeStagPopup();
+                return false;
+            });
+    }
+    addMCInitializer('stag-popups', window.initStagPopupEvents);
+})();

+ 274 - 0
js/stag-suggest.js

@@ -0,0 +1,274 @@
+(function () {
+    window.initStagSuggest = function () {
+
+        let suggestionsOuter = null;
+
+        const debounce = (func, wait) => {
+            let timeout;
+            return function executedFunction(...args) {
+                const later = () => {
+                    clearTimeout(timeout);
+                    func(...args);
+                };
+                clearTimeout(timeout);
+                timeout = setTimeout(later, wait);
+            };
+        };
+
+        var lastTerm = '';
+        var returnedFunction = debounce(function (elem) {
+            let term = elem.val();
+            let ep = $(elem).attr('stag-suggest-ep');
+            if(!!ep) { // remote lookup
+                if (!!term) {
+
+                    $.get(ep + '?term=' + $.trim(term), function (_data) {
+
+                        /*
+                        expected return format:
+                        {
+                            success: true,
+                            data: [
+                                {
+                                    x: ...,
+                                    y: ...,
+                                    text: ...    // "text" key is "mandatory"
+                                },
+                                {
+                                    x: ...,
+                                    y: ...,
+                                    text: ...    // "text" key is "mandatory"
+                                },
+                                ...
+                            ]
+                        }
+                         */
+
+                        suggestionsOuter.empty();
+                        if(!hasResponseError(_data) && _data.data && _data.data.length) {
+                            for (let i = 0; i < _data.data.length; i++) {
+                                let item = $('<a native href="#" class="d-block suggest-item stag-suggest text-nowrap"/>');
+                                for(let x in _data.data[i]) {
+                                    if(_data.data[i].hasOwnProperty(x) && x !== 'text') {
+                                        item.attr('data-' + x, _data.data[i][x]);
+                                    }
+                                }
+                                item.data('suggest-data', _data.data[i]);
+                                item.html(_data.data[i].text);
+                                if(_data.data[i].sub_text) {
+                                    item.append($('<span/>')
+                                        .addClass('ml-1 text-sm text-secondary')
+                                        .append(' (')
+                                        .append(_data.data[i].sub_text)
+                                        .append(')')
+                                    );
+                                }
+                                if(_data.data[i].text2) {
+                                    item.append($('<div/>')
+                                        .addClass('text-sm text-secondary')
+                                        .html(_data.data[i].text2)
+                                    );
+                                }
+                                if(_data.data[i].tooltip) {
+                                    item.attr('title', _data.data[i].tooltip);
+                                }
+                                suggestionsOuter.append(item);
+                            }
+                        }
+                        else {
+                            suggestionsOuter.html('<span class="d-block no-suggest-items">No matches!</span>');
+                        }
+
+                        suggestionsOuter.removeClass('d-none');
+                    }, 'json');
+                    lastTerm = term;
+                } else {
+                    suggestionsOuter.addClass('d-none');
+                }
+            }
+            else { // local lookup
+                let optionList = $(elem).next().next('.data-option-list');
+                if(optionList.length) {
+                    let matches = [];
+                    optionList.find('>div').each(function() {
+                        if(!term || $(this).text().toLowerCase().indexOf(term.toLowerCase()) !== -1) {
+                            matches.push($(this).text());
+                        }
+                    });
+                    suggestionsOuter.empty();
+                    if(matches.length) {
+                        for (let i = 0; i < matches.length; i++) {
+                            let item = $('<a native href="#" class="d-block suggest-item stag-suggest text-nowrap"/>');
+                            item.data('suggest-data', {});
+                            item.html(matches[i]);
+                            suggestionsOuter.append(item);
+                        }
+                    }
+                    else {
+                        suggestionsOuter.html('<span class="d-block no-suggest-items">No matches!</span>');
+                    }
+                    suggestionsOuter.removeClass('d-none');
+                }
+            }
+
+        }, 250);
+
+        function handleKeydown(elem, e) {
+            let term = $.trim(elem.val());
+            let activeItem = suggestionsOuter.find('.suggest-item.active');
+            switch (e.which) {
+                case 27:
+                    suggestionsOuter.addClass('d-none');
+                    markEventAsConsumed(e);
+                    return false;
+                case 38:
+                    if (activeItem.prev().length) {
+                        activeItem.prev()
+                            .addClass('active')
+                            .siblings().removeClass('active');
+                        activeItem = suggestionsOuter.find('.suggest-item.active');
+                        if (activeItem.length) {
+                            activeItem[0].scrollIntoView();
+                        }
+                    }
+                    return false;
+                case 40:
+                    if (activeItem.next().length) {
+                        activeItem.next()
+                            .addClass('active')
+                            .siblings().removeClass('active');
+                        activeItem = suggestionsOuter.find('.suggest-item.active');
+                        if (activeItem.length) {
+                            activeItem[0].scrollIntoView();
+                        }
+                    }
+                    return false;
+                case 13:
+                    if (activeItem.length) {
+                        activeItem.first().click();
+                    }
+                    return false;
+                default:
+                    if (!!term) {
+                        suggestionsOuter
+                            .html('<span class="d-block no-suggest-items">Searching...</span>')
+                            .removeClass('d-none');
+                        returnedFunction(elem);
+                    } else {
+                        suggestionsOuter.addClass('d-none');
+                    }
+                    break;
+            }
+        }
+
+        function handleKeypress(elem, e) {
+            var term = $.trim(elem.val());
+            if (!!term || !$(elem).is('[stag-suggest-ep]')) {
+                suggestionsOuter
+                    .html('<span class="d-block no-suggest-items">Searching...</span>')
+                    .removeClass('d-none');
+                returnedFunction(elem);
+            } else {
+                suggestionsOuter.addClass('d-none');
+            }
+        }
+
+        $('[stag-suggest]:not([stag-suggest-initialized])').each(function () {
+            let elem = $(this);
+            elem.next('.stag-suggestions-container').remove();
+            $('<div class="stag-suggestions-container position-relative">' +
+                '<div class="suggestions-outer stag-suggestions position-absolute d-none"></div>' +
+                '</div>').insertAfter(elem);
+
+            elem
+                .off('focus.stag-suggest')
+                .on('focus.stag-suggest', function (e) {
+                    if(!$(this).is('[stag-suggest-ep]')) {
+                        suggestionsOuter = $(this).next('.stag-suggestions-container').find('>.suggestions-outer');
+                        return handleKeypress($(this), e);
+                    }
+                })
+                .off('keydown.stag-suggest')
+                .on('keydown.stag-suggest', function (e) {
+                    suggestionsOuter = $(this).next('.stag-suggestions-container').find('>.suggestions-outer');
+                    return handleKeydown($(this), e);
+                })
+                .off('paste.stag-suggest')
+                .on('paste.stag-suggest', function (e) {
+                    window.setTimeout(() => {
+                        suggestionsOuter = $(this).next('.stag-suggestions-container').find('>.suggestions-outer');
+                        return handleKeypress($(this), e);
+                    }, 100);
+                })
+                .off('keypress.stag-suggest')
+                .on('keypress.stag-suggest', function (e) {
+                    suggestionsOuter = $(this).next('.stag-suggestions-container').find('>.suggestions-outer');
+                    return handleKeypress($(this), e);
+                });
+
+            $(this).attr('stag-suggest-initialized', 1);
+        });
+
+        // on auto-suggest selection
+        $(document).off('click', '.suggest-item.stag-suggest');
+        $(document).on('click', '.suggest-item.stag-suggest', function () {
+
+            $('.suggestions-outer.stag-suggestions').addClass('d-none');
+
+            let data = $(this).data('suggest-data'),
+                label = $.trim($(this).text());
+
+            // set value
+            let input = $(this).closest('.position-relative').prev('[stag-suggest]');
+
+            // if input is [stag-suggest-text-only] - use only the text bit in label
+            if(input.is('[stag-suggest-text-only]')) {
+                label = data.text;
+            }
+
+            input.val(label);
+            input.data('suggest-data', data);
+
+            let scope = input.attr('stag-suggest-scope');
+            if(!scope) scope = 'form';
+            scope = $(scope);
+
+            for(let x in data) {
+                if(data.hasOwnProperty(x)) {
+                    input.attr('data-' + x, data[x]);
+
+                    // auto-populate if there's a field matching data-name="x" in the scope
+                    if(scope.find('[data-name="' + x + '"]').length) {
+                        scope.find('[data-name="' + x + '"]').val(data[x]).trigger('change');
+                    }
+
+                }
+            }
+            input.trigger('input');
+            input.trigger('change');
+
+            input.trigger('stag-suggest-selected', [input, data]);
+
+            return false;
+        });
+
+        // outside click
+        $(document)
+            .off('mousedown.stag-suggest-outer-click')
+            .on('mousedown.stag-suggest-outer-click', function (_e) {
+                let elem = $(_e.target);
+                // if mousedown is on the input whose stag-suggest is on display, ignore
+                if(elem.is('[stag-suggest]') && elem.next('.stag-suggestions-container').find('>.suggestions-outer').is(':visible')) {
+                    return false;
+                }
+                if(!elem.is('.stag-suggestions-container') && !elem.closest('.stag-suggestions-container').length) {
+                    if($('.stag-suggestions-container .suggestions-outer:not(.d-none)').length) {
+                        $('.stag-suggestions-container .suggestions-outer').addClass('d-none');
+                        return false;
+                    }
+                }
+            });
+
+    }
+    addMCInitializer('stag-suggest', window.initStagSuggest);
+})();

+ 66 - 0
js/stag-table-filter.js

@@ -0,0 +1,66 @@
+(function () {
+    window.initStagTableFilters = function () {
+
+        const debounce = (func, wait) => {
+            let timeout;
+            return function executedFunction(...args) {
+                const later = () => {
+                    clearTimeout(timeout);
+                    func(...args);
+                };
+                clearTimeout(timeout);
+                timeout = setTimeout(later, wait);
+            };
+        };
+
+        var returnedFunction = debounce(function (_elem) {
+            let term = $.trim(_elem.val()).toLowerCase(),
+                columnIndex = _elem.closest('td, th').index(),
+                trs = _elem.closest('table').find('tbody').find('tr');
+            trs.removeClass('stag-filter-hide');
+            trs.find('.stag-filter-highlight').replaceWith(function() {
+                return $(this).text();
+            });
+            _elem.closest('td, th').siblings().find('input[stag-table-filter]').val('');
+            if(!!term) {
+                trs.each(function () {
+                    let td = $(this).find('td:eq(' + columnIndex + ')'),
+                        text = td.text();
+                    if (text.toLowerCase().indexOf(term) === -1) {
+                        $(this).addClass('stag-filter-hide');
+                    }
+                    /*else {
+                        let re = new RegExp('(' + term + ')', 'ig');
+                        text = text.replace(re, '<span class="stag-filter-highlight">$&</span>');
+                        td.html(text);
+                    }*/
+                });
+            }
+        }, 100);
+
+        function applyFilter(_elem) {
+            returnedFunction(_elem);
+        }
+
+        $(document)
+            .off('input.stag-table-filter').on('input.stag-table-filter', 'input[stag-table-filter]', function() { applyFilter($(this)); })
+            .off('change.stag-table-filter').on('change.stag-table-filter', 'input[stag-table-filter]', function() { applyFilter($(this)); })
+            .off('paste.stag-table-filter').on('paste.stag-table-filter', 'input[stag-table-filter]', function() { applyFilter($(this)); })
+
+        $(document)
+            .off('keyup.stag-table-filter')
+            .on('keyup.stag-table-filter', 'input[stag-table-filter]', function(_e) {
+                if(_e.which === 27) {
+                    if(!isEventConsumed(e)) {
+                        if ($(this).val() !== '') {
+                            $(this).val('');
+                            applyFilter($(this));
+                            markEventAsConsumed(_e);
+                            return false;
+                        }
+                    }
+                }
+            })
+    }
+    addMCInitializer('stag-table-filter', window.initStagTableFilters);
+})();

+ 77 - 0
js/tsvToArray.js

@@ -0,0 +1,77 @@
+var tsvToArray = null;
+(function ($) {
+    tsvToArray = {
+        file: 'cells.txt',
+        text: null,
+        json: null,
+
+        initLoadText: function (text) {
+            this.text = text;
+            return this.generateJSON();
+        },        
+        numberOfTabs: function (text) {
+            var count = 0;
+            var index = 0;
+            while (text.charAt(index++) === "\t") {
+                count++;
+            }
+            return count;
+        },
+        generateJSON: function () {
+            var self = this;
+            var text = self.text;
+            var lines = text.split(/\r\n|\r|\n/);
+            self.generatedArray = [];
+            var previousTabPosition = 0;
+            var currentContent = null;
+            var previousContent = null;
+            $.each(lines, function (lineNumber, line) {
+                var line = this;
+                var content = line.replace(/\t/g, '');
+                var tabPosition = self.numberOfTabs(line);
+                currentContent = content.length ? content : currentContent;
+
+                if (!self.generatedArray[lineNumber]) {
+                    self.generatedArray[lineNumber] = [];
+                }
+
+                if (tabPosition > previousTabPosition) {
+                    self.generatedArray[lineNumber] = self.autoFillArray(self.generatedArray[lineNumber], lineNumber, previousTabPosition, tabPosition, previousContent, currentContent);
+                } else {
+                    self.generatedArray[lineNumber].push(content);
+                    previousTabPosition = tabPosition;
+                    previousContent = content;
+                }
+
+            });
+
+            return self.generatedArray;
+        },
+        autoFillArray: function (array, lineNumber, previousTabPosition, currentTabPosition, previousContent, currentContent) {
+            var self = this;
+            var currentElementArray = [currentContent];
+            var previousLineArray = self.generatedArray[lineNumber - 1];
+            var pluckedArray = previousLineArray.slice(0, currentTabPosition);
+            var newArray = pluckedArray.concat(currentElementArray);
+            return newArray;
+        },
+        convertArrayToTabContentString: function(array){
+            var newLines = [];
+            for(var i = 0; i < array.length; i++){
+                var lineArray = array[i];
+                var lineArrayString = lineArray.join('\t');
+                newLines.push(lineArrayString);
+            }
+            return newLines.join('\n');
+        },
+        getAutoFilledTabContent: function(text){
+            var array = this.initLoadText(text);
+            var string = this.convertArrayToTabContentString(array);
+            return string;
+        },
+        init: function () {
+            
+        }
+    };
+    tsvToArray.init();
+})(jQuery);

ファイルの差分が大きいため隠しています
+ 0 - 0
js/yemi.js


+ 14 - 3
minify.sh

@@ -1,5 +1,16 @@
 ## npm install uglifyjs -g
 
-uglifyjs yemi.js --compress --mangle --output  public/js/yemi.js
-
-
+uglifyjs public/js/yemi.js --compress --mangle --output  public/js/yemi.js
+uglifyjs public/js/tsvToArray.js --compress --mangle --output  public/js/tsvToArray.js
+uglifyjs public/js/stag-table-filter.js --compress --mangle --output public/js/stag-table-filter.js
+uglifyjs public/js/stag-suggest.js --compress --mangle --output public/js/stag-suggest.js
+uglifyjs public/js/stag-popup.js --compress --mangle --output public/js/stag-popup.js
+uglifyjs public/js/stag-collapsible-card.js --compress --mangle --output public/js/stag-collapsible-card.js
+uglifyjs public/js/show-on-click.js --compress --mangle --output public/js/show-on-click.js
+uglifyjs public/js/shortcut.js --compress --mangle --output public/js/shortcut.js
+uglifyjs public/js/pro-suggest.js --compress --mangle --output public/js/pro-suggest.js
+uglifyjs public/js/option-list.js --compress --mangle --output public/js/option-list.js
+uglifyjs public/js/mc-init.js --compress --mangle --output public/js/mc-init.js
+uglifyjs public/js/mc.js --compress --mangle --output public/js/mc.js
+uglifyjs public/js/dq.js --compress --mangle --output public/js/dq.js
+uglifyjs public/js/click-to-copy.js --compress --mangle --output public/js/click-to-copy.js

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません