<commit_before>'use strict';

angular.module('mentio', [])
    .directive('mentio', function (mentioUtil, $compile, $log, $document) {
        return {
            restrict: 'A',
            scope: {
                macros: '=mentioMacros',
                search: '&mentioSearch',
                select: '&mentioSelect',
                items: '=mentioItems',
                typedTerm: '=mentioTypedTerm',
                ngModel: '='
            },
            controller: function($scope, $timeout, $attrs) {

                $scope.query = function (triggerChar, triggerText) {
                    var remoteScope = $scope.triggerCharMap[triggerChar];
                    remoteScope.showMenu();

                    remoteScope.search({
                        term: triggerText
                    });

                    remoteScope.typedTerm = triggerText;
                };

                $scope.defaultSearch = function(locals) {
                    var results = [];
                    angular.forEach($scope.items, function(item) {
                        if (item.label.toUpperCase().indexOf(locals.term.toUpperCase()) >= 0) {
                            results.push(item);
                        }
                    });
                    $scope.localItems = results;
                };

                $scope.bridgeSearch = function(termString) {
                    var searchFn = $attrs.mentioSearch ? $scope.search : $scope.defaultSearch;
                    searchFn({
                        term: termString
                    });
                };

                $scope.defaultSelect = function(locals) {
                    return $scope.defaultTriggerChar + locals.item.label;
                };

                $scope.bridgeSelect = function(itemVar) {
                    var selectFn = $attrs.mentioSelect ? $scope.select : $scope.defaultSelect;
                    return selectFn({
                        item: itemVar
                    });
                };

                $scope.setTriggerText = function(text) {
                    if ($scope.syncTriggerText) {
                        $scope.typedTerm = text;
                    }
                };

                $scope.replaceText = function (triggerChar, item) {
                    var remoteScope = $scope.triggerCharMap[triggerChar];
                    var text = remoteScope.select({
                        item: item
                    });
                    mentioUtil.replaceTriggerText($scope.targetElement, $scope.targetElementPath,
                        $scope.targetElementSelectedOffset, $scope.triggerCharSet, text);
                    $scope.setTriggerText('');
                    angular.element($scope.targetElement).triggerHandler('change');
                    if ($scope.isContentEditable()) {
                        $scope.contentEditableMenuPasted = true;
                        var timer = $timeout(function() {
                            $scope.contentEditableMenuPasted = false;
                        }, 100);
                        $scope.$on('$destroy', function() {
                            $timeout.cancel(timer);
                        });
                    }
                };

                $scope.hideAll = function () {
                    for (var key in $scope.triggerCharMap) {
                        if ($scope.triggerCharMap.hasOwnProperty(key)) {
                            $scope.triggerCharMap[key].hideMenu();
                        }
                    }
                };

                $scope.getActiveMenuScope = function () {
                    for (var key in $scope.triggerCharMap) {
                        if ($scope.triggerCharMap.hasOwnProperty(key)) {
                            if ($scope.triggerCharMap[key].visible) {
                                return $scope.triggerCharMap[key];
                            }
                        }
                    }
                    return null;
                };

                $scope.selectActive = function () {
                    for (var key in $scope.triggerCharMap) {
                        if ($scope.triggerCharMap.hasOwnProperty(key)) {
                            if ($scope.triggerCharMap[key].visible) {
                                $scope.triggerCharMap[key].selectActive();
                            }
                        }
                    }
                };

                $scope.isActive = function () {
                    for (var key in $scope.triggerCharMap) {
                        if ($scope.triggerCharMap.hasOwnProperty(key)) {
                            if ($scope.triggerCharMap[key].visible) {
                                return true;
                            }
                        }
                    }
                    return false;
                };

                $scope.isContentEditable = function() {
                    return ($scope.targetElement.nodeName !== 'INPUT' && $scope.targetElement.nodeName !== 'TEXTAREA');
                };

                $scope.replaceMacro = function(macro) {
                    if ($scope.replacingMacro) {
                        $timeout.cancel($scope.timer);
                    } else {
                        $scope.replacingMacro = true;
                    }
                    $scope.timer = $timeout(function() {
                        mentioUtil.replaceMacroText($scope.targetElement, $scope.targetElementPath,
                            $scope.targetElementSelectedOffset, $scope.macros, $scope.macros[macro]);
                        angular.element($scope.targetElement).triggerHandler('change');
                        $scope.replacingMacro = false;
                    }, 300);
                    $scope.$on('$destroy', function() {
                        $timeout.cancel($scope.timer);
                    });
                };

                $scope.addMenu = function(menuScope) {
                    if (menuScope.parentScope && $scope.triggerCharMap.hasOwnProperty(menuScope.triggerChar)) {
                        return;
                    }
                    $scope.triggerCharMap[menuScope.triggerChar] = menuScope;
                    if ($scope.triggerCharSet === undefined) {
                        $scope.triggerCharSet = [];
                    }
                    $scope.triggerCharSet.push(menuScope.triggerChar);
                    menuScope.setParent($scope);
                };

                $scope.$on(
                    'menuCreated', function (event, data) {
                        if ($attrs.id === data.targetElement) {
                            $scope.addMenu(data.scope);
                        }
                    }
                );

                $document.on(
                    'click', function () {
                        if ($scope.isActive()) {
                            $scope.$apply(function () {
                                $scope.hideAll();
                            });
                        }
                    }
                );

                $document.on(
                    'keydown keypress paste', function (event) {
                        var activeMenuScope = $scope.getActiveMenuScope();
                        if (activeMenuScope) {
                            if (event.which === 9) {
                                activeMenuScope.selectActive();
                            }

                            if (event.which === 27) {
                                event.preventDefault();
                                activeMenuScope.$apply(function () {
                                    activeMenuScope.hideMenu();
                                });
                            }

                            if (event.which === 40) {
                                event.preventDefault();
                                activeMenuScope.$apply(function () {
                                    activeMenuScope.activateNextItem();
                                });
                            }

                            if (event.which === 38) {
                                event.preventDefault();
                                activeMenuScope.$apply(function () {
                                    activeMenuScope.activatePreviousItem();
                                });
                            }

                            if (event.which === 13 || event.which === 32) {
                                event.preventDefault();
                                activeMenuScope.selectActive();
                            }
                        }
                    }
                );
            },
            link: function (scope, element, attrs) {
                scope.triggerCharMap = {};
                attrs.$set('autocomplete','off');

                if (attrs.mentioItems) {
                    scope.localItems = [];
                    scope.parentScope = scope;
                    var itemsRef = attrs.mentioSearch ? ' mentio-items="items"' : ' mentio-items="localItems"';

                    scope.defaultTriggerChar = attrs.mentioTriggerChar ? scope.$eval(attrs.mentioTriggerChar) : '@';

                    var html = '<mentio-menu' +
                        ' mentio-search="bridgeSearch(term)"' +
                        ' mentio-select="bridgeSelect(item)"' +
                        itemsRef;

                    if (attrs.mentioTemplateUrl) {
                        html = html + ' mentio-template-url="' + attrs.mentioTemplateUrl + '"';
                    }
                    html = html + ' mentio-trigger-char="\'' + scope.defaultTriggerChar + '\'"' +
                        ' mentio-parent-scope="parentScope"' +
                        '/>';
                    var linkFn = $compile(html);
                    var el = linkFn(scope);

                    element.parent().append(el);
                }

                if (attrs.mentioTypedTerm) {
                    scope.syncTriggerText = true;
                }

                scope.$watch(
                    'ngModel',
                    function () {
                        if (scope.contentEditableMenuPasted) {
                            // don't respond to changes from insertion of the menu content
                            scope.contentEditableMenuPasted = false;
                            return;
                        }

                        var mentionInfo = mentioUtil.getTriggerInfo(scope.triggerCharSet);

                        if (mentionInfo !== undefined) {
                            /** save selection info about the target control for later re-selection */
                            scope.targetElement = mentionInfo.mentionSelectedElement;
                            scope.targetElementPath = mentionInfo.mentionSelectedPath;
                            scope.targetElementSelectedOffset = mentionInfo.mentionSelectedOffset;

                            /* publish to external */
                            scope.setTriggerText(mentionInfo.mentionText);
                            /* perform query */
                            scope.query(mentionInfo.mentionTriggerChar, mentionInfo.mentionText);
                        } else {
                            scope.setTriggerText('');
                            scope.hideAll();

                            var macroMatchInfo = mentioUtil.getMacroMatch(scope.macros);
                            if (macroMatchInfo !== undefined) {
                                scope.targetElement = macroMatchInfo.macroSelectedElement;
                                scope.targetElementPath = macroMatchInfo.macroSelectedPath;
                                scope.targetElementSelectedOffset = macroMatchInfo.macroSelectedOffset;
                                scope.replaceMacro(macroMatchInfo.macroText);
                            }
                        }
                    }
                );
            }
        };
    })

    .directive('mentioMenu', function (mentioUtil, $rootScope, $log, $window, $document) {
        return {
            restrict: 'E',
            scope: {
                search: '&mentioSearch',
                select: '&mentioSelect',
                items: '=mentioItems',
                triggerChar: '=mentioTriggerChar',
                forElem: '=mentioFor',
                parentScope: '=mentioParentScope'
            },
            templateUrl: function(tElement, tAttrs) {
                return tAttrs.mentioTemplateUrl !== undefined ? tAttrs.mentioTemplateUrl : 'mentio-menu.tpl.html';
            },
            controller: function ($scope) {
                $scope.visible = false;

                // callable both with controller (menuItem) and without controller (local)
                this.activate = $scope.activate = function (item) {
                    $scope.activeItem = item;
                };

                // callable both with controller (menuItem) and without controller (local)
                this.isActive = $scope.isActive = function (item) {
                    return $scope.activeItem === item;
                };

                // callable both with controller (menuItem) and without controller (local)
                this.selectItem = $scope.selectItem = function (item) {
                    $scope.hideMenu();
                    $scope.parentMentio.replaceText($scope.triggerChar, item);
                };

                $scope.activateNextItem = function () {
                    var index = $scope.items.indexOf($scope.activeItem);
                    this.activate($scope.items[(index + 1) % $scope.items.length]);
                };

                $scope.activatePreviousItem = function () {
                    var index = $scope.items.indexOf($scope.activeItem);
                    this.activate($scope.items[index === 0 ? $scope.items.length - 1 : index - 1]);
                };

                $scope.selectActive = function () {
                    $scope.selectItem($scope.activeItem);
                };

                $scope.isVisible = function () {
                    return $scope.visible;
                };

                $scope.showMenu = function () {
                    if (!$scope.visible) {
                        $scope.requestVisiblePendingSearch = true;
                    }
                };

                $scope.setParent = function (scope) {
                    $scope.parentMentio = scope;
                };
            },

            link: function (scope, element) {
                element[0].parentNode.removeChild(element[0]);
                $document[0].body.appendChild(element[0]);
                scope.menuElement = element; // for testing

                if (scope.parentScope) {
                    scope.parentScope.addMenu(scope);
                } else {
                    if (!scope.forElem) {
                        $log.error('mentio-menu requires a target element in tbe mentio-for attribute');
                        return;
                    }
                    if (!scope.triggerChar) {
                        $log.error('mentio-menu requires a trigger char');
                        return;
                    }
                    var targetElement = $document[0].querySelector('#' + scope.forElem);

                    if (targetElement) {
                        var ngElem = angular.element(targetElement);
                        var mentioAttr = ngElem.attr('mentio');
                        if (mentioAttr !== undefined) {
                            // send own scope to mentio directive so that the menu
                            // becomes attached
                            $rootScope.$broadcast('menuCreated',
                                {
                                    targetElement : scope.forElem,
                                    scope : scope
                                });
                            scope.targetElement = ngElem;
                        } else {
                            $log.error('Error, no mentio directive on target element ' + scope.forElem);
                        }
                    } else {
                        $log.error('Error, no such element: ' + scope.forElem);
                    }
                }

                angular.element($window).bind(
                    'resize', function () {
                        if (scope.isVisible()) {
                            var triggerCharSet = [];
                            triggerCharSet.push(scope.triggerChar);
                            mentioUtil.popUnderMention(triggerCharSet, element);
                        }
                    }
                );

                scope.$watch('items', function (items) {
                    if (items && items.length > 0) {
                        scope.activate(items[0]);
                        if (!scope.visible && scope.requestVisiblePendingSearch) {
                            scope.visible = true;
                            scope.requestVisiblePendingSearch = false;
                        }
                    } else {
                        scope.hideMenu();
                    }
                });

                scope.$watch('isVisible()', function (visible) {
                    // wait for the watch notification to show the menu
                    if (visible) {
                        var triggerCharSet = [];
                        triggerCharSet.push(scope.triggerChar);
                        mentioUtil.popUnderMention(triggerCharSet, element);
                    }
                });

                scope.hideMenu = function () {
                    scope.visible = false;
                    element.css('display', 'none');
                };

            }
        };
    })

    .directive('mentioMenuItem', function () {
        return {
            restrict: 'A',
            scope: {
                item: '=mentioMenuItem'
            },
            require: '^mentioMenu',
            link: function (scope, element, attrs, controller) {

                scope.$watch(function () {
                    return controller.isActive(scope.item);
                }, function (active) {
                    if (active) {
                        element.addClass('active');
                    } else {
                        element.removeClass('active');
                    }
                });

                element.bind('mouseenter', function () {
                    scope.$apply(function () {
                        controller.activate(scope.item);
                    });
                });

                element.bind('click', function (e) {
                    e.preventDefault();
                    controller.selectItem(scope.item);
                });
            }
        };
    })
    .filter('unsafe', function($sce) {
        return function (val) {
            return $sce.trustAsHtml(val);
        };
    })
    .filter('mentioHighlight', function() {
        function escapeRegexp (queryToEscape) {
            return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
        }

        return function (matchItem, query, hightlightClass) {
            if (query) {
                var replaceText = hightlightClass ?
                                 '$&' :
                                 '$&';
                return ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), replaceText);
            } else {
                return matchItem;
            }
        };
    });
<commit_msg>Fixed tab going to next tab index<commit_after>'use strict';

angular.module('mentio', [])
    .directive('mentio', function (mentioUtil, $compile, $log, $document) {
        return {
            restrict: 'A',
            scope: {
                macros: '=mentioMacros',
                search: '&mentioSearch',
                select: '&mentioSelect',
                items: '=mentioItems',
                typedTerm: '=mentioTypedTerm',
                ngModel: '='
            },
            controller: function($scope, $timeout, $attrs) {

                $scope.query = function (triggerChar, triggerText) {
                    var remoteScope = $scope.triggerCharMap[triggerChar];
                    remoteScope.showMenu();

                    remoteScope.search({
                        term: triggerText
                    });

                    remoteScope.typedTerm = triggerText;
                };

                $scope.defaultSearch = function(locals) {
                    var results = [];
                    angular.forEach($scope.items, function(item) {
                        if (item.label.toUpperCase().indexOf(locals.term.toUpperCase()) >= 0) {
                            results.push(item);
                        }
                    });
                    $scope.localItems = results;
                };

                $scope.bridgeSearch = function(termString) {
                    var searchFn = $attrs.mentioSearch ? $scope.search : $scope.defaultSearch;
                    searchFn({
                        term: termString
                    });
                };

                $scope.defaultSelect = function(locals) {
                    return $scope.defaultTriggerChar + locals.item.label;
                };

                $scope.bridgeSelect = function(itemVar) {
                    var selectFn = $attrs.mentioSelect ? $scope.select : $scope.defaultSelect;
                    return selectFn({
                        item: itemVar
                    });
                };

                $scope.setTriggerText = function(text) {
                    if ($scope.syncTriggerText) {
                        $scope.typedTerm = text;
                    }
                };

                $scope.replaceText = function (triggerChar, item) {
                    var remoteScope = $scope.triggerCharMap[triggerChar];
                    var text = remoteScope.select({
                        item: item
                    });
                    mentioUtil.replaceTriggerText($scope.targetElement, $scope.targetElementPath,
                        $scope.targetElementSelectedOffset, $scope.triggerCharSet, text);
                    $scope.setTriggerText('');
                    angular.element($scope.targetElement).triggerHandler('change');
                    if ($scope.isContentEditable()) {
                        $scope.contentEditableMenuPasted = true;
                        var timer = $timeout(function() {
                            $scope.contentEditableMenuPasted = false;
                        }, 100);
                        $scope.$on('$destroy', function() {
                            $timeout.cancel(timer);
                        });
                    }
                };

                $scope.hideAll = function () {
                    for (var key in $scope.triggerCharMap) {
                        if ($scope.triggerCharMap.hasOwnProperty(key)) {
                            $scope.triggerCharMap[key].hideMenu();
                        }
                    }
                };

                $scope.getActiveMenuScope = function () {
                    for (var key in $scope.triggerCharMap) {
                        if ($scope.triggerCharMap.hasOwnProperty(key)) {
                            if ($scope.triggerCharMap[key].visible) {
                                return $scope.triggerCharMap[key];
                            }
                        }
                    }
                    return null;
                };

                $scope.selectActive = function () {
                    for (var key in $scope.triggerCharMap) {
                        if ($scope.triggerCharMap.hasOwnProperty(key)) {
                            if ($scope.triggerCharMap[key].visible) {
                                $scope.triggerCharMap[key].selectActive();
                            }
                        }
                    }
                };

                $scope.isActive = function () {
                    for (var key in $scope.triggerCharMap) {
                        if ($scope.triggerCharMap.hasOwnProperty(key)) {
                            if ($scope.triggerCharMap[key].visible) {
                                return true;
                            }
                        }
                    }
                    return false;
                };

                $scope.isContentEditable = function() {
                    return ($scope.targetElement.nodeName !== 'INPUT' && $scope.targetElement.nodeName !== 'TEXTAREA');
                };

                $scope.replaceMacro = function(macro) {
                    if ($scope.replacingMacro) {
                        $timeout.cancel($scope.timer);
                    } else {
                        $scope.replacingMacro = true;
                    }
                    $scope.timer = $timeout(function() {
                        mentioUtil.replaceMacroText($scope.targetElement, $scope.targetElementPath,
                            $scope.targetElementSelectedOffset, $scope.macros, $scope.macros[macro]);
                        angular.element($scope.targetElement).triggerHandler('change');
                        $scope.replacingMacro = false;
                    }, 300);
                    $scope.$on('$destroy', function() {
                        $timeout.cancel($scope.timer);
                    });
                };

                $scope.addMenu = function(menuScope) {
                    if (menuScope.parentScope && $scope.triggerCharMap.hasOwnProperty(menuScope.triggerChar)) {
                        return;
                    }
                    $scope.triggerCharMap[menuScope.triggerChar] = menuScope;
                    if ($scope.triggerCharSet === undefined) {
                        $scope.triggerCharSet = [];
                    }
                    $scope.triggerCharSet.push(menuScope.triggerChar);
                    menuScope.setParent($scope);
                };

                $scope.$on(
                    'menuCreated', function (event, data) {
                        if ($attrs.id === data.targetElement) {
                            $scope.addMenu(data.scope);
                        }
                    }
                );

                $document.on(
                    'click', function () {
                        if ($scope.isActive()) {
                            $scope.$apply(function () {
                                $scope.hideAll();
                            });
                        }
                    }
                );

                $document.on(
                    'keydown keypress paste', function (event) {
                        var activeMenuScope = $scope.getActiveMenuScope();
                        if (activeMenuScope) {
                            if (event.which === 9) {
                                event.preventDefault();
                                activeMenuScope.selectActive();
                            }

                            if (event.which === 27) {
                                event.preventDefault();
                                activeMenuScope.$apply(function () {
                                    activeMenuScope.hideMenu();
                                });
                            }

                            if (event.which === 40) {
                                event.preventDefault();
                                activeMenuScope.$apply(function () {
                                    activeMenuScope.activateNextItem();
                                });
                            }

                            if (event.which === 38) {
                                event.preventDefault();
                                activeMenuScope.$apply(function () {
                                    activeMenuScope.activatePreviousItem();
                                });
                            }

                            if (event.which === 13 || event.which === 32) {
                                event.preventDefault();
                                activeMenuScope.selectActive();
                            }
                        }
                    }
                );
            },
            link: function (scope, element, attrs) {
                scope.triggerCharMap = {};
                attrs.$set('autocomplete','off');

                if (attrs.mentioItems) {
                    scope.localItems = [];
                    scope.parentScope = scope;
                    var itemsRef = attrs.mentioSearch ? ' mentio-items="items"' : ' mentio-items="localItems"';

                    scope.defaultTriggerChar = attrs.mentioTriggerChar ? scope.$eval(attrs.mentioTriggerChar) : '@';

                    var html = '<mentio-menu' +
                        ' mentio-search="bridgeSearch(term)"' +
                        ' mentio-select="bridgeSelect(item)"' +
                        itemsRef;

                    if (attrs.mentioTemplateUrl) {
                        html = html + ' mentio-template-url="' + attrs.mentioTemplateUrl + '"';
                    }
                    html = html + ' mentio-trigger-char="\'' + scope.defaultTriggerChar + '\'"' +
                        ' mentio-parent-scope="parentScope"' +
                        '/>';
                    var linkFn = $compile(html);
                    var el = linkFn(scope);

                    element.parent().append(el);
                }

                if (attrs.mentioTypedTerm) {
                    scope.syncTriggerText = true;
                }

                scope.$watch(
                    'ngModel',
                    function () {
                        if (scope.contentEditableMenuPasted) {
                            // don't respond to changes from insertion of the menu content
                            scope.contentEditableMenuPasted = false;
                            return;
                        }

                        var mentionInfo = mentioUtil.getTriggerInfo(scope.triggerCharSet);

                        if (mentionInfo !== undefined) {
                            /** save selection info about the target control for later re-selection */
                            scope.targetElement = mentionInfo.mentionSelectedElement;
                            scope.targetElementPath = mentionInfo.mentionSelectedPath;
                            scope.targetElementSelectedOffset = mentionInfo.mentionSelectedOffset;

                            /* publish to external */
                            scope.setTriggerText(mentionInfo.mentionText);
                            /* perform query */
                            scope.query(mentionInfo.mentionTriggerChar, mentionInfo.mentionText);
                        } else {
                            scope.setTriggerText('');
                            scope.hideAll();

                            var macroMatchInfo = mentioUtil.getMacroMatch(scope.macros);
                            if (macroMatchInfo !== undefined) {
                                scope.targetElement = macroMatchInfo.macroSelectedElement;
                                scope.targetElementPath = macroMatchInfo.macroSelectedPath;
                                scope.targetElementSelectedOffset = macroMatchInfo.macroSelectedOffset;
                                scope.replaceMacro(macroMatchInfo.macroText);
                            }
                        }
                    }
                );
            }
        };
    })

    .directive('mentioMenu', function (mentioUtil, $rootScope, $log, $window, $document) {
        return {
            restrict: 'E',
            scope: {
                search: '&mentioSearch',
                select: '&mentioSelect',
                items: '=mentioItems',
                triggerChar: '=mentioTriggerChar',
                forElem: '=mentioFor',
                parentScope: '=mentioParentScope'
            },
            templateUrl: function(tElement, tAttrs) {
                return tAttrs.mentioTemplateUrl !== undefined ? tAttrs.mentioTemplateUrl : 'mentio-menu.tpl.html';
            },
            controller: function ($scope) {
                $scope.visible = false;

                // callable both with controller (menuItem) and without controller (local)
                this.activate = $scope.activate = function (item) {
                    $scope.activeItem = item;
                };

                // callable both with controller (menuItem) and without controller (local)
                this.isActive = $scope.isActive = function (item) {
                    return $scope.activeItem === item;
                };

                // callable both with controller (menuItem) and without controller (local)
                this.selectItem = $scope.selectItem = function (item) {
                    $scope.hideMenu();
                    $scope.parentMentio.replaceText($scope.triggerChar, item);
                };

                $scope.activateNextItem = function () {
                    var index = $scope.items.indexOf($scope.activeItem);
                    this.activate($scope.items[(index + 1) % $scope.items.length]);
                };

                $scope.activatePreviousItem = function () {
                    var index = $scope.items.indexOf($scope.activeItem);
                    this.activate($scope.items[index === 0 ? $scope.items.length - 1 : index - 1]);
                };

                $scope.selectActive = function () {
                    $scope.selectItem($scope.activeItem);
                };

                $scope.isVisible = function () {
                    return $scope.visible;
                };

                $scope.showMenu = function () {
                    if (!$scope.visible) {
                        $scope.requestVisiblePendingSearch = true;
                    }
                };

                $scope.setParent = function (scope) {
                    $scope.parentMentio = scope;
                };
            },

            link: function (scope, element) {
                element[0].parentNode.removeChild(element[0]);
                $document[0].body.appendChild(element[0]);
                scope.menuElement = element; // for testing

                if (scope.parentScope) {
                    scope.parentScope.addMenu(scope);
                } else {
                    if (!scope.forElem) {
                        $log.error('mentio-menu requires a target element in tbe mentio-for attribute');
                        return;
                    }
                    if (!scope.triggerChar) {
                        $log.error('mentio-menu requires a trigger char');
                        return;
                    }
                    var targetElement = $document[0].querySelector('#' + scope.forElem);

                    if (targetElement) {
                        var ngElem = angular.element(targetElement);
                        var mentioAttr = ngElem.attr('mentio');
                        if (mentioAttr !== undefined) {
                            // send own scope to mentio directive so that the menu
                            // becomes attached
                            $rootScope.$broadcast('menuCreated',
                                {
                                    targetElement : scope.forElem,
                                    scope : scope
                                });
                            scope.targetElement = ngElem;
                        } else {
                            $log.error('Error, no mentio directive on target element ' + scope.forElem);
                        }
                    } else {
                        $log.error('Error, no such element: ' + scope.forElem);
                    }
                }

                angular.element($window).bind(
                    'resize', function () {
                        if (scope.isVisible()) {
                            var triggerCharSet = [];
                            triggerCharSet.push(scope.triggerChar);
                            mentioUtil.popUnderMention(triggerCharSet, element);
                        }
                    }
                );

                scope.$watch('items', function (items) {
                    if (items && items.length > 0) {
                        scope.activate(items[0]);
                        if (!scope.visible && scope.requestVisiblePendingSearch) {
                            scope.visible = true;
                            scope.requestVisiblePendingSearch = false;
                        }
                    } else {
                        scope.hideMenu();
                    }
                });

                scope.$watch('isVisible()', function (visible) {
                    // wait for the watch notification to show the menu
                    if (visible) {
                        var triggerCharSet = [];
                        triggerCharSet.push(scope.triggerChar);
                        mentioUtil.popUnderMention(triggerCharSet, element);
                    }
                });

                scope.hideMenu = function () {
                    scope.visible = false;
                    element.css('display', 'none');
                };

            }
        };
    })

    .directive('mentioMenuItem', function () {
        return {
            restrict: 'A',
            scope: {
                item: '=mentioMenuItem'
            },
            require: '^mentioMenu',
            link: function (scope, element, attrs, controller) {

                scope.$watch(function () {
                    return controller.isActive(scope.item);
                }, function (active) {
                    if (active) {
                        element.addClass('active');
                    } else {
                        element.removeClass('active');
                    }
                });

                element.bind('mouseenter', function () {
                    scope.$apply(function () {
                        controller.activate(scope.item);
                    });
                });

                element.bind('click', function (e) {
                    e.preventDefault();
                    controller.selectItem(scope.item);
                });
            }
        };
    })
    .filter('unsafe', function($sce) {
        return function (val) {
            return $sce.trustAsHtml(val);
        };
    })
    .filter('mentioHighlight', function() {
        function escapeRegexp (queryToEscape) {
            return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
        }

        return function (matchItem, query, hightlightClass) {
            if (query) {
                var replaceText = hightlightClass ?
                                 '$&' :
                                 '$&';
                return ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), replaceText);
            } else {
                return matchItem;
            }
        };
    });
<|endoftext|>


<commit_before><commit_msg>accept express.Router instead of app<commit_after><|endoftext|>


<commit_before><commit_msg>test travis<commit_after><|endoftext|>


<commit_before><commit_msg>rm unused var<commit_after><|endoftext|>


<commit_before><commit_msg>Fixed index.js<commit_after><|endoftext|>


<commit_before><commit_msg>add type=“text/html” to iframe<commit_after><|endoftext|>


<commit_before><commit_msg>More details for desc<commit_after><|endoftext|>


<commit_before><commit_msg>Added debug info on publishing root bookJson file<commit_after><|endoftext|>


<commit_before><commit_msg>autoclose to make writing network servers much simpler<commit_after><|endoftext|>


<commit_before><commit_msg>fix(modelId): Fixing modelId not being defined in time<commit_after><|endoftext|>


<commit_before><commit_msg>Fix base URL endpoint<commit_after><|endoftext|>


<commit_before>/*
 * The main JavaScript file for the Spiffy UI framework
 */

spiffyui = {

    /*
     * These four functions are for showing different kinds of messages
     * in the general page UI.  They are accessor methods for implementations
     * in MessageUtil.java.  They are bound in that same class.
     */
    showFatalError: null,
    showError: null,
    showWarning: null,
    showMessage: null,

    autoloadCSS: true,
    autoloadHTML: true,

    /**
     * Gets a string of the date formatted into the short date
     * format in the current locale.
     */
    getDate: function(/*int*/ epoch) {
        var dateVal = new Date(epoch);
        return spiffyui.chineseAposWorkaround(dateVal.toString(Date.CultureInfo.formatPatterns.shortDate));
    },

    /**
     * Gets a string of the date formatted into the specified format
     */
    getDateString: function(/*int*/ epoch, /*string*/ format) {
        var dateVal = new Date(epoch);
        return spiffyui.chineseAposWorkaround(dateVal.toString(format));
    },

    /**
     * Gets a string of the date formatted into the long date
     * format in the current locale.
     */
    getLongDate: function(/*int*/ epoch) {
        var dateVal = new Date(epoch);
        return spiffyui.chineseAposWorkaround(dateVal.toString(Date.CultureInfo.formatPatterns.longDate));
    },

    /**
     * Gets a string of the date formatted into the month day
     * format in the current locale. If abbrev = true, then use the
     * abbreviation for month.
     */
    getMonthDay: function(/*int*/ epoch, /*boolean*/ abbrev) {
        var dateVal = new Date(epoch);
        var dateFormat = Date.CultureInfo.formatPatterns.monthDay;
        if (abbrev) {
            dateFormat = dateFormat.replace("MMMM", "MMM");
        }
        return spiffyui.chineseAposWorkaround(dateVal.toString(dateFormat));
    },

    /**
     * In zh locales the formatted date strings are
     * surrounding every Chinese character with apostrophes.
     * For example, it is showing, so this will remove all the
     * apostrophes.
     */
    chineseAposWorkaround: function(/*String*/ dateString) {
		if (Date.CultureInfo.name.indexOf("zh") === 0) {
			return dateString.replace(/'/g, "");
		}
		return dateString;
    },

    /**
     * Gets a string of the time formatted into the short time
     * format in the current locale.
     */
    getShortTime: function(/*int*/ epoch) {
        var dateVal = new Date(epoch);
        return spiffyui.midnightWorkaround(dateVal.toString(Date.CultureInfo.formatPatterns.shortTime));
    },

    /**
     * Gets a string of the time at the hour or half hour or
     * rounded up to the nearest half hour
     * and formatted into the short time format in the current
     * locale.
     */
    getShortTimeRounded: function(/*int*/ epoch) {
        var dateVal = new Date(epoch);
        var min = dateVal.getMinutes();
        if (min > 0) {
	        if (min < 30) {
	            dateVal.setMinutes(30);
	        } else if (min > 30) {
	            dateVal.setMinutes(0);
	            dateVal.setHours(dateVal.getHours() + 1);
	        }
        }

        return spiffyui.midnightWorkaround(dateVal.toString(Date.CultureInfo.formatPatterns.shortTime));
    },

    /**
     * 12 AM is showing up as 0 AM, which is a bug in date.js, who is already aware of the problem.
     * http://groups.google.com/group/datejs/browse_thread/thread/2d4f5d46916920a6/5e2106f2f911ea93?lnk=gst&q=0+AM#5e2106f2f911ea93
     * Since we're still seeing the problem, applying hackito.
     */
    midnightWorkaround: function (/*String*/ formattedString) {
        if (formattedString.indexOf("0:") === 0 && formattedString.indexOf("AM") === 5) {
            formattedString = formattedString.substring(2);
            formattedString = "12:" + formattedString;
        }
        return formattedString;
    },

    /**
     * Gets a string of the date time formatted into the full date time
     * format in the current locale.
     */
    getDateTime: function(/*int*/ epoch) {
        var dateVal = new Date(epoch);
        return spiffyui.chineseAposWorkaround(dateVal.toString(Date.CultureInfo.formatPatterns.fullDateTime));
    },

    /**
     * This is a helper function that gets a Date object from a time
     * string like 11:30pm.  This is just used in JSDateUtil and
     * shouldn't be used anywhere else.
     */
    getDateFromShortTime: function(/*string*/ formattedTime) {

         /*
            The date.js library has a bug where it gives the wrong
            24 hour value for 12AM and 12PM.  I've filed a bug report,
            but we still need to work aorund the issue.  Hackito
            ergo sum.
          */
         if (formattedTime === "12:00 PM") {
             formattedTime = "12:00 AM";
         } else if (formattedTime === "12:30 PM") {
             formattedTime = "12:30 AM";
         } else if (formattedTime === "12:00 AM") {
             formattedTime = "12:00 PM";
         } else if (formattedTime === "12:30 AM") {
             formattedTime = "12:30 PM";
         }

        /*
           We want to make the Date class parse our time, but it does
           like to parse time without the date so we put a bogus date
           in front of it because we are just interested in the hours
           and minutes.
         */
        var date = Date.parseExact(new Date().toString("yyyy-mm-dd") + " " + formattedTime,
                                   "yyyy-mm-dd " + Date.CultureInfo.formatPatterns.shortTime);
        return date;
    },
    /**
     * Gets a date that is set to the current date. The time is set to the start of the day (00:00 or 12:00 AM).
     */

    getToday: function() {
        return Date.today().getTime();
    },
    /**
     * Adds the 1 day to today.
     */
    nextDay: function() {
        return Date.today().add(1).days().getTime();
    },

    /**
     * Adds the specified number of units to the epochDate and returns the new epochDate
     */
    dateAdd: function(/*int*/ epochDate, /*int*/amt, /*String*/unit) {
        var date = new Date(epochDate);
        if (unit === "WEEK") {
            return date.addWeeks(amt).getTime();
        } else if (unit === "MONTH") {
            return date.addMonths(amt).getTime();
        } else if (unit === "HOUR") {
            return date.addHours(amt).getTime();
        } else if (unit === "MINUTE") {
            return date.addMinutes(amt).getTime();
        } else if (unit === "SECOND") {
            return date.addSeconds(amt).getTime();
        } else if (unit === "YEAR") {
            return date.addYears(amt).getTime();
        } else {
            return date.add(amt).days().getTime();
        }
    },
    /**
     * Gets the epoch string (milliseconds since 1970) from a date.
     */
    getEpoch: function(/*date string*/ formattedDate) {
        var epochObj = new Date(formattedDate);
        return epochObj.getTime();
    },

    /**
     * Get the Ordinal day (numeric day number) of the year, adjusted for leap year.
     * Returns 1 through 365 (366 in leap years).
     */
    getOrdinalNumber: function(/*int*/ epochDate) {
        var date = new Date(epochDate);
        return date.getOrdinalNumber();
    },

    /**
     * Format the ID for a JQuery selector.  This function adds a pound
     * sign to the beginning of the string if there isn't on already
     */
    formatId: function(/*string*/ id) {
         if (id.indexOf('#') === 0) {
             return id;
         } else {
             return '#' + id;
         }
    },

    /**
     * Get the UTC offset of the current browser timezone.  This
     * returns a number like -5 or 6
     */
    getUTCOffset: function() {
        var d = Date.today();
        var offset = d.getUTCOffset();
        if (offset.indexOf('+') === 0) {
            return parseInt(offset.substring(1, offset.length - 2), 10);
        } else if (offset.indexOf('-') === 0) {
            return -parseInt(offset.substring(1, offset.length - 2), 10);
        } else {
            /*
             * IE in some cases will return this value starting with undefined.  That means
             * negative.
             */
            return -parseInt(offset.substring(10, offset.length - 2), 10);
        }
    },

    /**
     * Add an item to the browser history.  This function is just
     * called from GWT.
     */
    addHistoryItem: function(/*function*/ func, /*object*/ scope, /*array*/item, /*boolean*/ bookmarkable) {
         if (bookmarkable) {
             dsHistory.setQueryVar('b', item.id);
             dsHistory.bindQueryVars(func, scope, item);
         } else {
             dsHistory.addFunction(func, scope, item);
         }
    },

    /**
     * Handle a history event from the browser.  This function is
     * just called from GWT.
     */
    handleHistoryEvent: function(contentObject, historyObject) {
         if (contentObject && contentObject.callback && contentObject.id) {
             spiffyui.doHandleHistoryEvent(contentObject, historyObject);
         }
    },

    /**
     * GWT often puts output files in a path with the module name.
     * We need to determine that path so we can figure out where to
     * reference our CSS files from.  There isn't a great way to do
     * this so we are looking for the nocache.js file and keying off
     * of that path.
     */
    getRelativePath: function() {
         var path = '';
         jQuery('script').each(function() {
             if (jQuery(this).attr('src') && jQuery(this).attr('src').indexOf('nocache.js') > -1) {
                 if (jQuery(this).attr('src').indexOf('/') === -1) {
                     return;
                 } else {
                     path =  jQuery(this).attr('src').substring(0, jQuery(this).attr('src').lastIndexOf('/') + 1);
                 }
             }
         });

         return path;
    },

    init: function() {
        if (spiffyui.autoloadHTML) {

            /*
             * We start by adding the HTML tags our CSS depends on
             */
            jQuery('body').append(
                '

' +
                '
' +
                    '
' +
                        '

' +
                        '
' +
                            '

' +
                            '

' +
                            '

' +
                        '
' +
                        '

' +
                    '
' +
                '
' +
                '

');
        }

        var path = spiffyui.getRelativePath();

        if (spiffyui.autoloadCSS) {
            /*
             * Almost all of our CSS is in spiffyui.css, but there are always a few
             * tweaks you need to add for IE.  This special style sheet is added only
             * if the browser is IE and contains just those tweaks.
             */
            if (navigator.appName === 'Microsoft Internet Explorer') {
                jQuery('head').prepend('<link>');
                css = $('head').children(':first');
                css.attr({
                    rel:  'stylesheet',
                    type: 'text/css',
                    href: path + 'spiffyui.ie.css'
                });
            }

            /*
             * Now we add the spiffyui.min.css file.  If the URL ends with -debug.html
             * then we'll add the uncompressed version.  We add these files dynamically
             * so users of the framework have less to add to their page and because IE8
             * give precedence to files loaded in the page over those added with JavaScript
             * and we can't do that because we override styles in spiffyui.ie.css.
             */
            if (window.location.href.substr(-11) === '-debug.html') {
                jQuery('head').prepend('<link>');
                css = $('head').children(':first');
                css.attr({
                    rel:  'stylesheet',
                    type: 'text/css',
                    href: 'spiffyui.css'
                });
            } else {
                jQuery('head').prepend('<link>');
                css = $('head').children(':first');
                css.attr({
                    rel:  'stylesheet',
                    type: 'text/css',
                    href: path + 'spiffyui.min.css'
                });
            }
        }
    }

};

jQuery(document).ready(function() {
    spiffyui.init();
});
<commit_msg>Added a new field that supports specifying an alternate path for the CSS files and a few more code comments about the options for spiffyui.js.<commit_after>/*
 * The main JavaScript file for the Spiffy UI framework
 */

spiffyui = {

    /*
     * These four functions are for showing different kinds of messages
     * in the general page UI.  They are accessor methods for implementations
     * in MessageUtil.java.  They are bound in that same class.
     */
    showFatalError: null,
    showError: null,
    showWarning: null,
    showMessage: null,

    /**
     * This field controls the auto-loading of the Spiffy UI CSS
     * references.  If this values is false those references will
     * not load.  This value is true by default.
     */
    autoloadCSS: true,

    /**
     * This field controls the auto-loading of the Spiffy UI DOM
     * elements.  If this values is false those DOM elements will
     * not load. This value is true by default.
     */
    autoloadHTML: true,

    /**
     * This field gives users a chance to override the determined
     * path for the CSS files loaded by spiffyui.js.
     *
     * If this value isn't specified then the CSS files will be
     * loaded in the same path as the nocache.js file for this GWT
     * application.
     *
     * If the autoloadCSS field is set to false then this field is
     * not used.
     */
    cssFilePath: null,

    /**
     * Gets a string of the date formatted into the short date
     * format in the current locale.
     */
    getDate: function(/*int*/ epoch) {
        var dateVal = new Date(epoch);
        return spiffyui.chineseAposWorkaround(dateVal.toString(Date.CultureInfo.formatPatterns.shortDate));
    },

    /**
     * Gets a string of the date formatted into the specified format
     */
    getDateString: function(/*int*/ epoch, /*string*/ format) {
        var dateVal = new Date(epoch);
        return spiffyui.chineseAposWorkaround(dateVal.toString(format));
    },

    /**
     * Gets a string of the date formatted into the long date
     * format in the current locale.
     */
    getLongDate: function(/*int*/ epoch) {
        var dateVal = new Date(epoch);
        return spiffyui.chineseAposWorkaround(dateVal.toString(Date.CultureInfo.formatPatterns.longDate));
    },

    /**
     * Gets a string of the date formatted into the month day
     * format in the current locale. If abbrev = true, then use the
     * abbreviation for month.
     */
    getMonthDay: function(/*int*/ epoch, /*boolean*/ abbrev) {
        var dateVal = new Date(epoch);
        var dateFormat = Date.CultureInfo.formatPatterns.monthDay;
        if (abbrev) {
            dateFormat = dateFormat.replace("MMMM", "MMM");
        }
        return spiffyui.chineseAposWorkaround(dateVal.toString(dateFormat));
    },

    /**
     * In zh locales the formatted date strings are
     * surrounding every Chinese character with apostrophes.
     * For example, it is showing, so this will remove all the
     * apostrophes.
     */
    chineseAposWorkaround: function(/*String*/ dateString) {
		if (Date.CultureInfo.name.indexOf("zh") === 0) {
			return dateString.replace(/'/g, "");
		}
		return dateString;
    },

    /**
     * Gets a string of the time formatted into the short time
     * format in the current locale.
     */
    getShortTime: function(/*int*/ epoch) {
        var dateVal = new Date(epoch);
        return spiffyui.midnightWorkaround(dateVal.toString(Date.CultureInfo.formatPatterns.shortTime));
    },

    /**
     * Gets a string of the time at the hour or half hour or
     * rounded up to the nearest half hour
     * and formatted into the short time format in the current
     * locale.
     */
    getShortTimeRounded: function(/*int*/ epoch) {
        var dateVal = new Date(epoch);
        var min = dateVal.getMinutes();
        if (min > 0) {
	        if (min < 30) {
	            dateVal.setMinutes(30);
	        } else if (min > 30) {
	            dateVal.setMinutes(0);
	            dateVal.setHours(dateVal.getHours() + 1);
	        }
        }

        return spiffyui.midnightWorkaround(dateVal.toString(Date.CultureInfo.formatPatterns.shortTime));
    },

    /**
     * 12 AM is showing up as 0 AM, which is a bug in date.js, who is already aware of the problem.
     * http://groups.google.com/group/datejs/browse_thread/thread/2d4f5d46916920a6/5e2106f2f911ea93?lnk=gst&q=0+AM#5e2106f2f911ea93
     * Since we're still seeing the problem, applying hackito.
     */
    midnightWorkaround: function (/*String*/ formattedString) {
        if (formattedString.indexOf("0:") === 0 && formattedString.indexOf("AM") === 5) {
            formattedString = formattedString.substring(2);
            formattedString = "12:" + formattedString;
        }
        return formattedString;
    },

    /**
     * Gets a string of the date time formatted into the full date time
     * format in the current locale.
     */
    getDateTime: function(/*int*/ epoch) {
        var dateVal = new Date(epoch);
        return spiffyui.chineseAposWorkaround(dateVal.toString(Date.CultureInfo.formatPatterns.fullDateTime));
    },

    /**
     * This is a helper function that gets a Date object from a time
     * string like 11:30pm.  This is just used in JSDateUtil and
     * shouldn't be used anywhere else.
     */
    getDateFromShortTime: function(/*string*/ formattedTime) {

         /*
            The date.js library has a bug where it gives the wrong
            24 hour value for 12AM and 12PM.  I've filed a bug report,
            but we still need to work aorund the issue.  Hackito
            ergo sum.
          */
         if (formattedTime === "12:00 PM") {
             formattedTime = "12:00 AM";
         } else if (formattedTime === "12:30 PM") {
             formattedTime = "12:30 AM";
         } else if (formattedTime === "12:00 AM") {
             formattedTime = "12:00 PM";
         } else if (formattedTime === "12:30 AM") {
             formattedTime = "12:30 PM";
         }

        /*
           We want to make the Date class parse our time, but it does
           like to parse time without the date so we put a bogus date
           in front of it because we are just interested in the hours
           and minutes.
         */
        var date = Date.parseExact(new Date().toString("yyyy-mm-dd") + " " + formattedTime,
                                   "yyyy-mm-dd " + Date.CultureInfo.formatPatterns.shortTime);
        return date;
    },
    /**
     * Gets a date that is set to the current date. The time is set to the start of the day (00:00 or 12:00 AM).
     */

    getToday: function() {
        return Date.today().getTime();
    },
    /**
     * Adds the 1 day to today.
     */
    nextDay: function() {
        return Date.today().add(1).days().getTime();
    },

    /**
     * Adds the specified number of units to the epochDate and returns the new epochDate
     */
    dateAdd: function(/*int*/ epochDate, /*int*/amt, /*String*/unit) {
        var date = new Date(epochDate);
        if (unit === "WEEK") {
            return date.addWeeks(amt).getTime();
        } else if (unit === "MONTH") {
            return date.addMonths(amt).getTime();
        } else if (unit === "HOUR") {
            return date.addHours(amt).getTime();
        } else if (unit === "MINUTE") {
            return date.addMinutes(amt).getTime();
        } else if (unit === "SECOND") {
            return date.addSeconds(amt).getTime();
        } else if (unit === "YEAR") {
            return date.addYears(amt).getTime();
        } else {
            return date.add(amt).days().getTime();
        }
    },
    /**
     * Gets the epoch string (milliseconds since 1970) from a date.
     */
    getEpoch: function(/*date string*/ formattedDate) {
        var epochObj = new Date(formattedDate);
        return epochObj.getTime();
    },

    /**
     * Get the Ordinal day (numeric day number) of the year, adjusted for leap year.
     * Returns 1 through 365 (366 in leap years).
     */
    getOrdinalNumber: function(/*int*/ epochDate) {
        var date = new Date(epochDate);
        return date.getOrdinalNumber();
    },

    /**
     * Format the ID for a JQuery selector.  This function adds a pound
     * sign to the beginning of the string if there isn't on already
     */
    formatId: function(/*string*/ id) {
         if (id.indexOf('#') === 0) {
             return id;
         } else {
             return '#' + id;
         }
    },

    /**
     * Get the UTC offset of the current browser timezone.  This
     * returns a number like -5 or 6
     */
    getUTCOffset: function() {
        var d = Date.today();
        var offset = d.getUTCOffset();
        if (offset.indexOf('+') === 0) {
            return parseInt(offset.substring(1, offset.length - 2), 10);
        } else if (offset.indexOf('-') === 0) {
            return -parseInt(offset.substring(1, offset.length - 2), 10);
        } else {
            /*
             * IE in some cases will return this value starting with undefined.  That means
             * negative.
             */
            return -parseInt(offset.substring(10, offset.length - 2), 10);
        }
    },

    /**
     * Add an item to the browser history.  This function is just
     * called from GWT.
     */
    addHistoryItem: function(/*function*/ func, /*object*/ scope, /*array*/item, /*boolean*/ bookmarkable) {
         if (bookmarkable) {
             dsHistory.setQueryVar('b', item.id);
             dsHistory.bindQueryVars(func, scope, item);
         } else {
             dsHistory.addFunction(func, scope, item);
         }
    },

    /**
     * Handle a history event from the browser.  This function is
     * just called from GWT.
     */
    handleHistoryEvent: function(contentObject, historyObject) {
         if (contentObject && contentObject.callback && contentObject.id) {
             spiffyui.doHandleHistoryEvent(contentObject, historyObject);
         }
    },

    /**
     * GWT often puts output files in a path with the module name.
     * We need to determine that path so we can figure out where to
     * reference our CSS files from.  There isn't a great way to do
     * this so we are looking for the nocache.js file and keying off
     * of that path.
     */
    getRelativePath: function() {
         if (spiffyui.cssFilePath) {
             return spiffyui.cssFilePath;
         }

         var path = '';
         jQuery('script').each(function() {
             if (jQuery(this).attr('src') && jQuery(this).attr('src').indexOf('nocache.js') > -1) {
                 if (jQuery(this).attr('src').indexOf('/') === -1) {
                     return;
                 } else {
                     path =  jQuery(this).attr('src').substring(0, jQuery(this).attr('src').lastIndexOf('/') + 1);
                 }
             }
         });

         return path;
    },

    init: function() {
        if (spiffyui.autoloadHTML) {

            /*
             * We start by adding the HTML tags our CSS depends on
             */
            jQuery('body').append(
                '

' +
                '
' +
                    '
' +
                        '

' +
                        '
' +
                            '

' +
                            '

' +
                            '

' +
                        '
' +
                        '

' +
                    '
' +
                '
' +
                '

');
        }

        var path = spiffyui.getRelativePath();

        if (spiffyui.autoloadCSS) {
            /*
             * Almost all of our CSS is in spiffyui.css, but there are always a few
             * tweaks you need to add for IE.  This special style sheet is added only
             * if the browser is IE and contains just those tweaks.
             */
            if (navigator.appName === 'Microsoft Internet Explorer') {
                jQuery('head').prepend('<link>');
                css = $('head').children(':first');
                css.attr({
                    rel:  'stylesheet',
                    type: 'text/css',
                    href: path + 'spiffyui.ie.css'
                });
            }

            /*
             * Now we add the spiffyui.min.css file.  If the URL ends with -debug.html
             * then we'll add the uncompressed version.  We add these files dynamically
             * so users of the framework have less to add to their page and because IE8
             * give precedence to files loaded in the page over those added with JavaScript
             * and we can't do that because we override styles in spiffyui.ie.css.
             */
            if (window.location.href.substr(-11) === '-debug.html') {
                jQuery('head').prepend('<link>');
                css = $('head').children(':first');
                css.attr({
                    rel:  'stylesheet',
                    type: 'text/css',
                    href: 'spiffyui.css'
                });
            } else {
                jQuery('head').prepend('<link>');
                css = $('head').children(':first');
                css.attr({
                    rel:  'stylesheet',
                    type: 'text/css',
                    href: path + 'spiffyui.min.css'
                });
            }
        }
    }

};

jQuery(document).ready(function() {
    spiffyui.init();
});
<|endoftext|>


<commit_before>'use strict';

/**
* @Author: Popov Gennadiy <dio>
* @Date:   2016-12-15T01:44:33+03:00
* @Email:  me@westtrade.tk
* @Last modified by:   dio
* @Last modified time: 2016-12-20T02:09:11+03:00
*/

const Cleantalk = require('../Cleantalk');
const CleantalkRequest = require('../CleantalkRequest');

const ALLOWED_REQUEST_TYPES = ['user', 'message'];
const assert = require('assert');

/**
 * cleantalkMiddleware - Middleware check request for spam
 *
 * @param {String} Options.auth_key   Service authorization key, you will
 * find it in you control panel
 * (Access key){@link https://cleantalk.org/register?platform=api;}
 *
 *
 * @param {object}   Options.aliases List of aliases
 * Explanation:
 * List of aliases in dot notaion e.g.
 * {
 * 	'sender_email': 'body.sender_email' - for post data in request body
 * 	'sender_nickname': 'query.sender_email' - for get data in http query like http://.../?sender_email=
 * }
 *
 * @param {object}   Options.options Parser options
 * Explanation:
 * @param {Boolean} Options.options.sendHeaders - automatic parse and send headers of request
 * @param {Array} Options.options.excludeHeaders - List of excluded headers
 * @param {String} Options.options.bodyType - Body parser type - maybe json or querystring, Default: querystring
 * @param {String} Options.options.server_url - Options.server_url Link to API endpoint, default is
 * https://moderate.cleantalk.org/api2.0
 *
 * @param Options.options.language - Language may be 'ru' or 'en'
 *
 * @param {String} type  - Type of request - maybe user or message
 *
 * @return {function} middleware function
 */
 const cleantalkMiddleware = (auth_key, type, aliases, options) =>  {

 	options = options || {};
 	const {server_url, language} = options;

 	assert(type, `Argument 'type' is required`);
 	assert.equal(ALLOWED_REQUEST_TYPES.includes(type), `Argument 'type' must be in allowed list: ${ALLOWED_REQUEST_TYPES.join(', ')}`);
 	const client = new Cleantalk({auth_key, server_url, language});
	return async (ctx, next) => {

		const method = type === 'message'
			? 'isAllowMessage'
			: 'isAllowUser';

		const ctRequest = new CleantalkRequest({options, aliases, request});
		ctx.spam = await client[method](ctRequest);
		return next();
	};
}

module.exports = CleantalkMiddleware;
<commit_msg>Fixed koa2 middleware<commit_after>'use strict';

/**
* @Author: Popov Gennadiy <dio>
* @Date:   2016-12-15T01:44:33+03:00
* @Email:  me@westtrade.tk
* @Last modified by:   dio
* @Last modified time: 2016-12-20T02:09:11+03:00
*/

const Cleantalk = require('../Cleantalk');
const CleantalkRequest = require('../CleantalkRequest');

const ALLOWED_REQUEST_TYPES = ['user', 'message'];
const assert = require('assert');

/**
 * cleantalkMiddleware - Middleware check request for spam
 *
 * @param {String} Options.auth_key   Service authorization key, you will
 * find it in you control panel
 * (Access key){@link https://cleantalk.org/register?platform=api;}
 *
 *
 * @param {object}   Options.aliases List of aliases
 * Explanation:
 * List of aliases in dot notaion e.g.
 * {
 * 	'sender_email': 'body.sender_email' - for post data in request body
 * 	'sender_nickname': 'query.sender_email' - for get data in http query like http://.../?sender_email=
 * }
 *
 * @param {object}   Options.options Parser options
 * Explanation:
 * @param {Boolean} Options.options.sendHeaders - automatic parse and send headers of request
 * @param {Array} Options.options.excludeHeaders - List of excluded headers
 * @param {String} Options.options.bodyType - Body parser type - maybe json or querystring, Default: querystring
 * @param {String} Options.options.server_url - Options.server_url Link to API endpoint, default is
 * https://moderate.cleantalk.org/api2.0
 *
 * @param Options.options.language - Language may be 'ru' or 'en'
 *
 * @param {String} type  - Type of request - maybe user or message
 *
 * @return {function} middleware function
 */
 const cleantalkMiddleware = (auth_key, type, aliases, options) =>  {

 	options = options || {};
 	const {server_url, language} = options;

 	assert(type, `Argument 'type' is required`);
 	assert.equal(ALLOWED_REQUEST_TYPES.includes(type), `Argument 'type' must be in allowed list: ${ALLOWED_REQUEST_TYPES.join(', ')}`);
 	const client = new Cleantalk({auth_key, server_url, language});
	return async (ctx, next) => {

		const method = type === 'message'
			? 'isAllowMessage'
			: 'isAllowUser';

		const ctRequest = new CleantalkRequest({options, aliases, ctx.req});
		ctx.spam = await client[method](ctRequest);
		return next();
	};
};

module.exports = CleantalkMiddleware;
<|endoftext|>


<commit_before>/**
 * model/core/bound.js
 */

var boundModel = models['bound'] = baseModel.extend({
    /**
     * Constructor function to initialize each new model instance.
     * @return {[type]}
     */
    'initialize': function () {
        var self = this;
        /**
         * Queue the autorun of update.  We want this to happen after the current JS module
         * is loaded but before anything else gets updated.  We can't do that with setTimeout
         * or _.defer because that could possibly fire after drainQueue.
         */
        self.scope = autorun(self.update, self.scopePriority, self,
                             self['Name'] && 'model_' + self['Name'],
                             self.onScopeExecute, self, true);
    },

    scopePriority: BASE_PRIORITY_MODEL_SYNC,

    /**
     * Wake up this model as well as (recursively) any models that depend on
     * it.  Any view that is directly or indirectly depended on by the current
     * model may now be able to be awoken based on the newly-bound listener to
     * this model.
     * @param  {Object.<string, Boolean>} woken Hash map of model IDs already awoken
     */
    wake: function (woken) {
        // Wake up this model if it was sleeping
        if (this.sleeping) {
            this.sleeping = false;
            this.reset();
        }
        /**
         * Wake up models that depend directly on this model that have not already
         * been woken up.
         * XXX - how does this work?
         */
        _.each((this.scope && this.scope.lookups) || [], function (lookup) {
            var bindable = lookup['obj'];
            if (bindable && !woken[uniqueId(bindable)]) {
                woken[uniqueId(bindable)] = true;
                bindable.wake(woken);
            }
        });
    },

    onScopeExecute: function (scope) {
        log(INFO, this, 'lookups', scope.lookups);
    },

    update: function () {
        var self = this;
        self.sleeping = self['sleepEnabled'] && !hasViewListener(self);
        if (self.sleeping) {
            /**
             * This model will not update itself until there's a view listener
             * waiting for data (directly or through a chain of other models)
             * from this model.
             */
            log(INFO, self, 'sleep');
        } else {
            self._update();
        }
    },

    _update: function () {
        var flag = this['assumeChanged'] ? QUERY_ASSUME_CHANGED : QUERY_DEFAULT;
        this['query'](flag, QUERY_SELF, this['state']());
        log(VERBOSE, this, 'updated', this.attributes);
    },

    /**
     * Triggers scope re-execution.
     */
    reset: function () {
        if (this.scope) {
            this.scope.trigger();
        }
    },

    'destroy': function () {
        if (this.scope) {
            this.scope.destroy();
        }
        this['unset'](QUERY_SELF);
    },

    'disableSleep': function () {
        // This is intended to be used only interactively for development.
        if (TBONE_DEBUG && this['sleepEnabled']) {
            log(WARN, this, 'disableSleep', 'Disabling sleep mode for <%-Name%>.', this);
            this['sleepEnabled'] = false;
            this['wake']();
        }
    },

    /**
     * returns the new state, synchronously
     */
    'state': noop,

    'sleepEnabled': false
});

function getQuerySetProp (flag, prop) {
    // This is a short version of the start of the `query` function, and it would be nice
    // to refactor that to incorporate this feature without a duplication of that logic.
    var hasValue = arguments.length === 3;
    if (typeof flag !== 'number') {
        prop = flag;
        flag = QUERY_DEFAULT;
        if (arguments.length === 2) {
            hasValue = true;
        }
    }
    var isSet = flag >= MIN_QUERY_SET_FLAG || hasValue;
    prop = (prop || '').replace('__self__', '');
    return isSet && prop;
}

if (TBONE_DEBUG) {
    boundModel['query'] = function (flag, prop, value) {
        var setProp = getQuerySetProp.apply(this, arguments);
        if (setProp) {
            log(WARN, this, 'boundModelSet', 'Attempting to set non-root property <%-prop%> of bound model!', {
                prop: setProp
            });
        }
        return query.apply(this, arguments);
    };
}
<commit_msg>Little edit of fix for #36<commit_after>/**
 * model/core/bound.js
 */

var boundModel = models['bound'] = baseModel.extend({
    /**
     * Constructor function to initialize each new model instance.
     * @return {[type]}
     */
    'initialize': function () {
        var self = this;
        /**
         * Queue the autorun of update.  We want this to happen after the current JS module
         * is loaded but before anything else gets updated.  We can't do that with setTimeout
         * or _.defer because that could possibly fire after drainQueue.
         */
        self.scope = autorun(self.update, self.scopePriority, self,
                             self['Name'] && 'model_' + self['Name'],
                             self.onScopeExecute, self, true);
    },

    scopePriority: BASE_PRIORITY_MODEL_SYNC,

    /**
     * Wake up this model as well as (recursively) any models that depend on
     * it.  Any view that is directly or indirectly depended on by the current
     * model may now be able to be awoken based on the newly-bound listener to
     * this model.
     * @param  {Object.<string, Boolean>} woken Hash map of model IDs already awoken
     */
    wake: function (woken) {
        // Wake up this model if it was sleeping
        if (this.sleeping) {
            this.sleeping = false;
            this.reset();
        }
        /**
         * Wake up models that depend directly on this model that have not already
         * been woken up.
         * XXX - how does this work?
         */
        _.each((this.scope && this.scope.lookups) || [], function (lookup) {
            var bindable = lookup['obj'];
            if (bindable && !woken[uniqueId(bindable)]) {
                woken[uniqueId(bindable)] = true;
                bindable.wake(woken);
            }
        });
    },

    onScopeExecute: function (scope) {
        log(INFO, this, 'lookups', scope.lookups);
    },

    update: function () {
        var self = this;
        self.sleeping = self['sleepEnabled'] && !hasViewListener(self);
        if (self.sleeping) {
            /**
             * This model will not update itself until there's a view listener
             * waiting for data (directly or through a chain of other models)
             * from this model.
             */
            log(INFO, self, 'sleep');
        } else {
            self._update();
        }
    },

    _update: function () {
        var flag = this['assumeChanged'] ? QUERY_ASSUME_CHANGED : QUERY_DEFAULT;
        this['query'](flag, QUERY_SELF, this['state']());
        log(VERBOSE, this, 'updated', this.attributes);
    },

    /**
     * Triggers scope re-execution.
     */
    reset: function () {
        if (this.scope) {
            this.scope.trigger();
        }
    },

    'destroy': function () {
        if (this.scope) {
            this.scope.destroy();
        }
        this['unset'](QUERY_SELF);
    },

    'disableSleep': function () {
        // This is intended to be used only interactively for development.
        if (TBONE_DEBUG && this['sleepEnabled']) {
            log(WARN, this, 'disableSleep', 'Disabling sleep mode for <%-Name%>.', this);
            this['sleepEnabled'] = false;
            this['wake']();
        }
    },

    /**
     * returns the new state, synchronously
     */
    'state': noop,

    'sleepEnabled': false
});

function getQuerySetProp (flag, prop) {
    // This is a short version of the start of the `query` function, and it would be nice
    // to refactor that to incorporate this feature without a duplication of that logic.
    var hasValue = arguments.length === 3;
    if (typeof flag !== 'number') {
        prop = flag;
        flag = QUERY_DEFAULT;
        if (arguments.length === 2) {
            hasValue = true;
        }
    }
    var isSet = flag >= MIN_QUERY_SET_FLAG || hasValue;
    prop = (prop || '').replace('__self__', '');
    return isSet ? prop : null;
}

if (TBONE_DEBUG) {
    boundModel['query'] = function (flag, prop, value) {
        var setProp = getQuerySetProp.apply(this, arguments);
        if (setProp) {
            log(WARN, this, 'boundModelSet', 'Attempting to set property <%-prop%> of bound model!', {
                prop: setProp
            });
        }
        return query.apply(this, arguments);
    };
}
<|endoftext|>


<commit_before>define(['jquery', 'knockout', 'underscore', 'select2'], function($, ko, _) {
    ko.bindingHandlers.select2 = {
        init: function(el, valueAccessor, allBindingsAccessor, viewmodel, bindingContext) {
            var self = this;
            var allBindings = allBindingsAccessor().select2;
            var select2Config = ko.utils.unwrapObservable(allBindings.select2Config);
            var value = select2Config.value;

            ko.utils.domNodeDisposal.addDisposeCallback(el, function() {
                $(el).select2('destroy');
            });

            select2Config.formatResult = function(item) {
                return ko.unwrap(item.text);
            };

            function formatData(data) {
                _.each(data, function(item) {
                    if (item.collector === 'collector') {
                        delete item.id;
                    }
                });
            }

            var placeholder = select2Config.placeholder
            placeholder.subscribe(function(newItems) {
                select2Config.placeholder = newItems;
                $(el).select2("destroy").select2(select2Config);
            });
            select2Config.placeholder = select2Config.placeholder();

            var data = select2Config.data.extend({
                rateLimit: 500
            });
            data.subscribe(function(newItems) {
                formatData(newItems);
                select2Config.data = newItems;
                $(el).select2("destroy").select2(select2Config);
                $(el).select2("val", select2Config.value)
            });

            select2Config.data = select2Config.data();

            formatData(select2Config.data);

            select2Config.createSearchChoice = function(term, data) {
                if ($(data).filter(function() {
                        return ko.unwrap(this.text).localeCompare(term) === 0;
                    }).length === 0) {
                    return {
                        id: term,
                        text: term
                    };
                }
            }

            select2Config.getConceptPath = function(root_conceptid) {
                var root = {
                    'children': select2Config.data
                }
                var result = [];
                var isParent = false;

                function lookForChild(obj, conceptid) {
                    isParent = false;
                    _.each(obj.children, function(item) {
                        if (item.conceptid === conceptid) {
                            isParent = true;
                            result.push(obj)
                        }
                    });

                    if (isParent === false) {
                        for (var i = 0; i < obj.children.length; i += 1) {
                            lookForChild(obj.children[i], conceptid);
                        }
                    } else {
                        lookForChild(root, obj.conceptid);
                    }
                };

                lookForChild(root, root_conceptid)

                return result;
            }

            select2Config.formatSelection = function(item) {
                var path = [];
                var result = ko.unwrap(item.text);
                if (select2Config.showParents === true) {
                    path = this.getConceptPath(item.conceptid);
                    if (path.length > 1) {
                        result = ko.unwrap(path[0].text) + ": " + ko.unwrap(item.text);
                    }
                }
                return result
            };

            select2Config.value = value();
            $(el).select2(select2Config);
            $(el).select2("val", value());
            $(el).on("change", function(val) {
                return value(val.val);
            });
            $(el).on("select2-opening", function(val) {
                if (select2Config.clickBubble) {
                    $(el).parent().trigger('click');
                }
            });
            value.subscribe(function(newVal) {
                select2Config.value = newVal;
                $(el).select2("val", newVal);
            }, this);
        }
    };

    return ko.bindingHandlers.select2;
});
<commit_msg>Fix save and cancel buttons when clearing a select dropdown. re #2020<commit_after>define(['jquery', 'knockout', 'underscore', 'select2'], function($, ko, _) {
    ko.bindingHandlers.select2 = {
        init: function(el, valueAccessor, allBindingsAccessor, viewmodel, bindingContext) {
            var self = this;
            var allBindings = allBindingsAccessor().select2;
            var select2Config = ko.utils.unwrapObservable(allBindings.select2Config);
            var value = select2Config.value;

            ko.utils.domNodeDisposal.addDisposeCallback(el, function() {
                $(el).select2('destroy');
            });

            select2Config.formatResult = function(item) {
                return ko.unwrap(item.text);
            };

            function formatData(data) {
                _.each(data, function(item) {
                    if (item.collector === 'collector') {
                        delete item.id;
                    }
                });
            }

            var placeholder = select2Config.placeholder
            placeholder.subscribe(function(newItems) {
                select2Config.placeholder = newItems;
                $(el).select2("destroy").select2(select2Config);
            });
            select2Config.placeholder = select2Config.placeholder();

            var data = select2Config.data.extend({
                rateLimit: 500
            });
            data.subscribe(function(newItems) {
                formatData(newItems);
                select2Config.data = newItems;
                $(el).select2("destroy").select2(select2Config);
                $(el).select2("val", select2Config.value)
            });

            select2Config.data = select2Config.data();

            formatData(select2Config.data);

            select2Config.createSearchChoice = function(term, data) {
                if ($(data).filter(function() {
                        return ko.unwrap(this.text).localeCompare(term) === 0;
                    }).length === 0) {
                    return {
                        id: term,
                        text: term
                    };
                }
            }

            select2Config.getConceptPath = function(root_conceptid) {
                var root = {
                    'children': select2Config.data
                }
                var result = [];
                var isParent = false;

                function lookForChild(obj, conceptid) {
                    isParent = false;
                    _.each(obj.children, function(item) {
                        if (item.conceptid === conceptid) {
                            isParent = true;
                            result.push(obj)
                        }
                    });

                    if (isParent === false) {
                        for (var i = 0; i < obj.children.length; i += 1) {
                            lookForChild(obj.children[i], conceptid);
                        }
                    } else {
                        lookForChild(root, obj.conceptid);
                    }
                };

                lookForChild(root, root_conceptid)

                return result;
            }

            select2Config.formatSelection = function(item) {
                var path = [];
                var result = ko.unwrap(item.text);
                if (select2Config.showParents === true) {
                    path = this.getConceptPath(item.conceptid);
                    if (path.length > 1) {
                        result = ko.unwrap(path[0].text) + ": " + ko.unwrap(item.text);
                    }
                }
                return result
            };

            select2Config.value = value();
            $(el).select2(select2Config);
            $(el).select2("val", value());
            $(el).on("change", function(val) {
                if (val.val === "") {
                  val.val = null
                }
                return value(val.val);
            });
            $(el).on("select2-opening", function(val) {
                if (select2Config.clickBubble) {
                    $(el).parent().trigger('click');
                }
            });
            value.subscribe(function(newVal) {
                select2Config.value = newVal;
                $(el).select2("val", newVal);
            }, this);
        }
    };

    return ko.bindingHandlers.select2;
});
<|endoftext|>