<commit_before>
define([
    'flight/lib/component',
    './form/form',
    'tpl!./workspaces',
    'tpl!./list',
    'tpl!./item',
    'util/withDataRequest'
], function(defineComponent,
    WorkspaceForm,
    workspacesTemplate,
    listTemplate,
    itemTemplate,
    withDataRequest) {
    'use strict';

    return defineComponent(Workspaces, withDataRequest);

    function Workspaces() {

        this.defaultAttrs({
            listSelector: 'ul.nav-list',
            workspaceListItemSelector: 'ul.nav-list li',
            addNewInputSelector: 'input.new',
            addNewSelector: 'button.new',
            disclosureSelector: 'button.disclosure',
            formSelector: '.workspace-form'
        });

        this.onWorkspaceItemClick = function(event) {
            var $target = $(event.target);

            if ($target.is('input') || $target.is('button') || $target.is('.new-workspace')) return;
            if ($target.closest('.workspace-form').length) return;

            var li = $(event.target).closest('li'),
                workspaceId = li.data('workspaceId');

            if (workspaceId) {
                this.switchToWorkspace(workspaceId.toString());
            }
        };

        this.switchToWorkspace = function(workspaceId) {
            if (workspaceId !== lumifyData.currentWorkspaceId) {
                this.trigger('switchWorkspace', { workspaceId: workspaceId });
            }
        };

        this.onAddNew = function(event) {
            var self = this,
                $input = $(event.target).parents('li').find('input'),
                title = $.trim($input.val());

            if (!title) return;

            this.dataRequest('workspace', 'create', { title: title })
                .then(function(workspace) {
                    $input.val('')
                    self.trigger('switchWorkspace', { workspaceId: workspace.workspaceId });
                })
                .catch(function() {
                    $input.focus();
                })
        };

        this.onInputKeyUp = function(event) {
            switch (event.which) {
                case $.ui.keyCode.ENTER:
                    this.onAddNew(event);
            }
        };

        this.onDisclosure = function(event) {
            var self = this,
                $target = $(event.target),
                data = $target.closest('li').data();

            event.preventDefault();

            this.switchToWorkspace(data.workspaceId);

            var container = this.select('formSelector'),
                form = container.resizable({
                        handles: 'e',
                        minWidth: 120,
                        maxWidth: 250,
                        resize: function() {
                            self.trigger(document, 'paneResized');
                        }
                    }).show().find('.content'),
                instance = form.lookupComponent(WorkspaceForm);

            if (instance && instance.attr.data.workspaceId === data.workspaceId) {
                container.hide();
                instance.teardown();
                return self.trigger(document, 'paneResized');
            }

            WorkspaceForm.teardownAll();
            WorkspaceForm.attachTo(form, {
                data: data
            });

            self.trigger(container, 'paneResized');
        };

        this.collapseEditForm = function() {
            if (this.select('formSelector').is(':visible')) {
                WorkspaceForm.teardownAll();
                this.select('formSelector').hide();
                this.trigger(document, 'paneResized');
            }
        };

        this.onWorkspaceDeleting = function(event, data) {
            this.deletingWorkspace = $.extend({}, data);
        };

        this.onWorkspaceDeleted = function(event, data) {
            this.collapseEditForm();
            this.findWorkspaceRow(data.workspaceId).remove();
        };

        this.onSwitchWorkspace = function(event, data) {
            var li = this.findWorkspaceRow(data.workspaceId);
            if (li.length) {
                this.switchActive(data.workspaceId);
                li.find('.badge').addClass('loading').show().next().hide();
            }
            this.collapseEditForm();
        };

        this.onWorkspaceLoad = function(event, data) {
            if (this.$node.closest('.visible').length) {
                this.updateListItemWithData(data);
                this.switchActive(data.workspaceId);
            }
        };

        this.findWorkspaceRow = function(workspaceId) {
            return this.select('workspaceListItemSelector').filter(function() {
                return $(this).data('workspaceId') == workspaceId;
            });
        };

        this.onWorkspaceSaving = function(event, data) {
            var li = this.findWorkspaceRow(data.workspaceId);
            li.find('.badge').addClass('loading').show().next().hide();
        };

        this.onWorkspaceSaved = function(event, data) {
            var li = this.findWorkspaceRow(data.workspaceId);
            li.find('.badge').removeClass('loading').hide().next().show();
        };

        this.updateListItemWithData = function(data, timestamp) {
            var self = this,
                currentUser = lumifyData.currentUser,
                li = this.findWorkspaceRow(data.workspaceId);

            if (data.createdBy === currentUser.id ||
                _.contains(_.pluck(data.users, 'userId'), currentUser.id)
            ) {
                li.find('.badge').removeClass('loading').hide().next().show();
                this.workspaceDataForItemRow(data)
                    .done(function(data) {
                        var content = $(itemTemplate({ workspace: data, selected: self.workspaceId }));
                        if (li.length === 0) {
                            self.$node.find('li.nav-header').eq(data.sharedToUser ? 1 : 0).after(content);
                        } else {
                            li.replaceWith(content);
                        }

                        // Sort section because title might be renamed
                        var lis = self.getWorkspaceListItemsInSection(data.sharedToUser),
                            titleGetter = function() {
                                return $(this).data('title');
                            },
                            lowerCase = function(s) {
                                return s.toLowerCase();
                            },
                            titles = _.sortBy(lis.map(titleGetter).get(), lowerCase),
                            insertIndex = _.indexOf(titles, data.title),
                            currentIndex = lis.index(content);

                        if (currentIndex < insertIndex) {
                            content.insertAfter(lis.eq(insertIndex));
                        } else if (currentIndex > insertIndex) {
                            content.insertBefore(lis.eq(insertIndex));
                        }
                    });
            } else {
                li.remove();
            }
        };

        this.getWorkspaceListItemsInSection = function(shared) {
            if (shared) {
                return this.select('listSelector')
                    .children('.nav-header').eq(1)
                    .nextUntil();
            } else {
                return this.select('listSelector')
                    .children('.nav-header').eq(0)
                    .nextUntil('.new-workspace');
            }
        };

        this.onWorkspaceUpdated = function(event, data) {
            this.updateListItemWithData(data.workspace);
        };

        this.onWorkspaceNotAvailable = function(event, data) {
            this.loadWorkspaceList();
            this.trigger('displayInformation', { message: i18n('workspaces.not_found') });
        };

        this.switchActive = function(workspaceId) {
            var self = this;
            this.workspaceId = workspaceId;

            var found = false;
            this.select('workspaceListItemSelector')
                .removeClass('active')
                .each(function() {
                    if ($(this).data('workspaceId') == workspaceId) {
                        found = true;
                        $(this).addClass('active');
                        return false;
                    }
                });

            if (!found) {
                this.loadWorkspaceList();
            }
        };

        this.loadWorkspaceList = function(switchToFirst) {
            var self = this;

            // TODO: convert to d3
            return this.dataRequest('workspace', 'all')
                .then(function(workspacesResponse) {
                    var workspaces = workspacesResponse || [],
                        users = _.chain(workspaces)
                            .map(function(workspace) {
                                return _.pluck(workspace.users, 'userId');
                            })
                            .flatten()
                            .uniq()
                            .value(),
                        updateHtml = function() {
                            $.when.apply($, _.chain(workspaces)
                                .reject(function(workspace) {
                                    return _.isUndefined(workspace.createdBy);
                                })
                                .map(self.workspaceDataForItemRow.bind(self))
                                .value()
                            ).done(function() {
                                var rows = _.chain(arguments)
                                        .sortBy(function(w) {
                                            return w.title.toLowerCase()
                                        })
                                        .groupBy(function(w) {
                                            return w.sharedToUser ? 'shared' : 'mine';
                                        })
                                        .value();

                                self.select('listSelector').html(
                                    listTemplate({
                                        results: rows,
                                        selected: self.workspaceId
                                    })
                                );
                            });
                            self.trigger(document, 'paneResized');
                            if (switchToFirst) {
                                self.switchToWorkspace(workspaces[0].workspaceId);
                            }
                        };

                    if (users.length) {
                        return self.dataRequest('user', 'search', { userIds: users })
                            .done(function(result) {
                                self.usersById = $.extend(self.usersById, _.indexBy(users, 'id'));
                                updateHtml();
                            })
                    } else {
                        updateHtml();
                    }
                });
        };

        this.workspaceDataForItemRow = function(w) {
            var deferred = $.Deferred(),
                row = $.extend({}, w),
                usersNotCurrent = row.users.filter(function(u) {
                    return u.userId != lumifyData.currentUser.id;
                }),
                people = usersNotCurrent.length;

            if (row.sharedToUser) {
                this.dataRequest('user', 'search', { userIds: row.createdBy })
                    .done(function(createdBy) {
                        var name = createdBy && createdBy.displayName ||
                                i18n('workspaces.shared_with_me.subtitle.unknown_user');

                        deferred.resolve(i18n('workspaces.shared_with_me.subtitle.prefix', name));
                    });
            } else {
                deferred.resolve(i18n('workspaces.sharing.subtitle.prefix'));
            }

            return deferred.promise().then(function(text) {
                if (people === 1 && row.sharedToUser) {
                    row.sharingSubtitle = text + ' ' + i18n('workspaces.sharing.subtitle.suffix.only_you');
                } else if (people === 1) {
                    row.sharingSubtitle = text + ' ' + i18n('workspaces.sharing.subtitle.suffix.one_other');
                } else if (people) {
                    row.sharingSubtitle = text + ' ' + i18n('workspaces.sharing.subtitle.suffix.others', people);
                } else {
                    row.sharingSubtitle = null;
                }
                return row;
            });
        };

        this.onToggleMenu = function(event, data) {
            var self = this;

            if (data.name === 'workspaces') {
                if (this.$node.closest('.visible').length) {
                    this.loadWorkspaceList()
                        .then(function() {
                            self.switchActive(lumifyData.currentWorkspaceId);
                        })
                } else {
                    this.collapseEditForm();
                }
            }
        };

        this.after('initialize', function() {

            this.on(document, 'workspaceLoaded', this.onWorkspaceLoad);
            this.on(document, 'workspaceSaving', this.onWorkspaceSaving);
            this.on(document, 'workspaceSaved', this.onWorkspaceSaved);
            this.on(document, 'workspaceDeleted', this.onWorkspaceDeleted);
            this.on(document, 'workspaceUpdated', this.onWorkspaceUpdated);
            this.on(document, 'workspaceNotAvailable', this.onWorkspaceNotAvailable);

            this.on(document, 'menubarToggleDisplay', this.onToggleMenu);
            this.on(document, 'switchWorkspace', this.onSwitchWorkspace);

            this.on('workspaceDeleting', this.onWorkspaceDeleting);

            this.on('click', {
                workspaceListItemSelector: this.onWorkspaceItemClick,
                addNewSelector: this.onAddNew,
                disclosureSelector: this.onDisclosure
            });

            this.on('keyup', {
                addNewInputSelector: this.onInputKeyUp
            });

            this.$node.html(workspacesTemplate({}));
        });
    }

});
<commit_msg>Fix workspace sorting when 2 share same title<commit_after>
define([
    'flight/lib/component',
    './form/form',
    'tpl!./workspaces',
    'tpl!./list',
    'tpl!./item',
    'util/withDataRequest'
], function(defineComponent,
    WorkspaceForm,
    workspacesTemplate,
    listTemplate,
    itemTemplate,
    withDataRequest) {
    'use strict';

    return defineComponent(Workspaces, withDataRequest);

    function Workspaces() {

        this.defaultAttrs({
            listSelector: 'ul.nav-list',
            workspaceListItemSelector: 'ul.nav-list li',
            addNewInputSelector: 'input.new',
            addNewSelector: 'button.new',
            disclosureSelector: 'button.disclosure',
            formSelector: '.workspace-form'
        });

        this.onWorkspaceItemClick = function(event) {
            var $target = $(event.target);

            if ($target.is('input') || $target.is('button') || $target.is('.new-workspace')) return;
            if ($target.closest('.workspace-form').length) return;

            var li = $(event.target).closest('li'),
                workspaceId = li.data('workspaceId');

            if (workspaceId) {
                this.switchToWorkspace(workspaceId.toString());
            }
        };

        this.switchToWorkspace = function(workspaceId) {
            if (workspaceId !== lumifyData.currentWorkspaceId) {
                this.trigger('switchWorkspace', { workspaceId: workspaceId });
            }
        };

        this.onAddNew = function(event) {
            var self = this,
                $input = $(event.target).parents('li').find('input'),
                title = $.trim($input.val());

            if (!title) return;

            this.dataRequest('workspace', 'create', { title: title })
                .then(function(workspace) {
                    $input.val('')
                    self.trigger('switchWorkspace', { workspaceId: workspace.workspaceId });
                })
                .catch(function() {
                    $input.focus();
                })
        };

        this.onInputKeyUp = function(event) {
            switch (event.which) {
                case $.ui.keyCode.ENTER:
                    this.onAddNew(event);
            }
        };

        this.onDisclosure = function(event) {
            var self = this,
                $target = $(event.target),
                data = $target.closest('li').data();

            event.preventDefault();

            this.switchToWorkspace(data.workspaceId);

            var container = this.select('formSelector'),
                form = container.resizable({
                        handles: 'e',
                        minWidth: 120,
                        maxWidth: 250,
                        resize: function() {
                            self.trigger(document, 'paneResized');
                        }
                    }).show().find('.content'),
                instance = form.lookupComponent(WorkspaceForm);

            if (instance && instance.attr.data.workspaceId === data.workspaceId) {
                container.hide();
                instance.teardown();
                return self.trigger(document, 'paneResized');
            }

            WorkspaceForm.teardownAll();
            WorkspaceForm.attachTo(form, {
                data: data
            });

            self.trigger(container, 'paneResized');
        };

        this.collapseEditForm = function() {
            if (this.select('formSelector').is(':visible')) {
                WorkspaceForm.teardownAll();
                this.select('formSelector').hide();
                this.trigger(document, 'paneResized');
            }
        };

        this.onWorkspaceDeleting = function(event, data) {
            this.deletingWorkspace = $.extend({}, data);
        };

        this.onWorkspaceDeleted = function(event, data) {
            this.collapseEditForm();
            this.findWorkspaceRow(data.workspaceId).remove();
        };

        this.onSwitchWorkspace = function(event, data) {
            var li = this.findWorkspaceRow(data.workspaceId);
            if (li.length) {
                this.switchActive(data.workspaceId);
                li.find('.badge').addClass('loading').show().next().hide();
            }
            this.collapseEditForm();
        };

        this.onWorkspaceLoad = function(event, data) {
            if (this.$node.closest('.visible').length) {
                this.updateListItemWithData(data);
                this.switchActive(data.workspaceId);
            }
        };

        this.findWorkspaceRow = function(workspaceId) {
            return this.select('workspaceListItemSelector').filter(function() {
                return $(this).data('workspaceId') == workspaceId;
            });
        };

        this.onWorkspaceSaving = function(event, data) {
            var li = this.findWorkspaceRow(data.workspaceId);
            li.find('.badge').addClass('loading').show().next().hide();
        };

        this.onWorkspaceSaved = function(event, data) {
            var li = this.findWorkspaceRow(data.workspaceId);
            li.find('.badge').removeClass('loading').hide().next().show();
        };

        this.updateListItemWithData = function(data, timestamp) {
            var self = this,
                currentUser = lumifyData.currentUser,
                li = this.findWorkspaceRow(data.workspaceId);

            if (data.createdBy === currentUser.id ||
                _.contains(_.pluck(data.users, 'userId'), currentUser.id)
            ) {
                li.find('.badge').removeClass('loading').hide().next().show();
                this.workspaceDataForItemRow(data)
                    .done(function(data) {
                        var content = $(itemTemplate({ workspace: data, selected: self.workspaceId }));
                        if (li.length === 0) {
                            self.$node.find('li.nav-header').eq(data.sharedToUser ? 1 : 0).after(content);
                        } else {
                            li.replaceWith(content);
                        }

                        // Sort section because title might be renamed
                        var lis = self.getWorkspaceListItemsInSection(data.sharedToUser),
                            dataGetter = function(li) {
                                return $(this).data();
                            },
                            insertIndex = _.sortedIndex(lis.map(dataGetter).toArray(), data, function(w) {
                                return [w.title.toLowerCase(), w.createdBy, w.workspaceId].join('\u0000')
                            }),
                            currentIndex = lis.index(content);

                        if (currentIndex < insertIndex) {
                            content.insertAfter(lis.eq(insertIndex));
                        } else if (currentIndex > insertIndex) {
                            content.insertBefore(lis.eq(insertIndex));
                        }
                    });
            } else {
                li.remove();
            }
        };

        this.getWorkspaceListItemsInSection = function(shared) {
            if (shared) {
                return this.select('listSelector')
                    .children('.nav-header').eq(1)
                    .nextUntil();
            } else {
                return this.select('listSelector')
                    .children('.nav-header').eq(0)
                    .nextUntil('.new-workspace');
            }
        };

        this.onWorkspaceUpdated = function(event, data) {
            this.updateListItemWithData(data.workspace);
        };

        this.onWorkspaceNotAvailable = function(event, data) {
            this.loadWorkspaceList();
            this.trigger('displayInformation', { message: i18n('workspaces.not_found') });
        };

        this.switchActive = function(workspaceId) {
            var self = this;
            this.workspaceId = workspaceId;

            var found = false;
            this.select('workspaceListItemSelector')
                .removeClass('active')
                .each(function() {
                    if ($(this).data('workspaceId') == workspaceId) {
                        found = true;
                        $(this).addClass('active');
                        return false;
                    }
                });

            if (!found) {
                this.loadWorkspaceList();
            }
        };

        this.loadWorkspaceList = function(switchToFirst) {
            var self = this;

            // TODO: convert to d3
            return this.dataRequest('workspace', 'all')
                .then(function(workspacesResponse) {
                    var workspaces = workspacesResponse || [],
                        users = _.chain(workspaces)
                            .map(function(workspace) {
                                return _.pluck(workspace.users, 'userId');
                            })
                            .flatten()
                            .uniq()
                            .value(),
                        updateHtml = function() {
                            $.when.apply($, _.chain(workspaces)
                                .reject(function(workspace) {
                                    return _.isUndefined(workspace.createdBy);
                                })
                                .map(self.workspaceDataForItemRow.bind(self))
                                .value()
                            ).done(function() {
                                var rows = _.chain(arguments)
                                        .sortBy('workspaceId')
                                        .sortBy('createdBy')
                                        .sortBy(function(w) {
                                            return w.title.toLowerCase()
                                        })
                                        .groupBy(function(w) {
                                            return w.sharedToUser ? 'shared' : 'mine';
                                        })
                                        .value();

                                self.select('listSelector').html(
                                    listTemplate({
                                        results: rows,
                                        selected: self.workspaceId
                                    })
                                );
                            });
                            self.trigger(document, 'paneResized');
                            if (switchToFirst) {
                                self.switchToWorkspace(workspaces[0].workspaceId);
                            }
                        };

                    if (users.length) {
                        return self.dataRequest('user', 'search', { userIds: users })
                            .done(function(result) {
                                self.usersById = $.extend(self.usersById, _.indexBy(users, 'id'));
                                updateHtml();
                            })
                    } else {
                        updateHtml();
                    }
                });
        };

        this.workspaceDataForItemRow = function(w) {
            var deferred = $.Deferred(),
                row = $.extend({}, w),
                usersNotCurrent = row.users.filter(function(u) {
                    return u.userId != lumifyData.currentUser.id;
                }),
                people = usersNotCurrent.length;

            if (row.sharedToUser) {
                this.dataRequest('user', 'search', { userIds: row.createdBy })
                    .done(function(createdBy) {
                        var name = createdBy && createdBy.displayName ||
                                i18n('workspaces.shared_with_me.subtitle.unknown_user');

                        deferred.resolve(i18n('workspaces.shared_with_me.subtitle.prefix', name));
                    });
            } else {
                deferred.resolve(i18n('workspaces.sharing.subtitle.prefix'));
            }

            return deferred.promise().then(function(text) {
                if (people === 1 && row.sharedToUser) {
                    row.sharingSubtitle = text + ' ' + i18n('workspaces.sharing.subtitle.suffix.only_you');
                } else if (people === 1) {
                    row.sharingSubtitle = text + ' ' + i18n('workspaces.sharing.subtitle.suffix.one_other');
                } else if (people) {
                    row.sharingSubtitle = text + ' ' + i18n('workspaces.sharing.subtitle.suffix.others', people);
                } else {
                    row.sharingSubtitle = null;
                }
                return row;
            });
        };

        this.onToggleMenu = function(event, data) {
            var self = this;

            if (data.name === 'workspaces') {
                if (this.$node.closest('.visible').length) {
                    this.loadWorkspaceList()
                        .then(function() {
                            self.switchActive(lumifyData.currentWorkspaceId);
                        })
                } else {
                    this.collapseEditForm();
                }
            }
        };

        this.after('initialize', function() {

            this.on(document, 'workspaceLoaded', this.onWorkspaceLoad);
            this.on(document, 'workspaceSaving', this.onWorkspaceSaving);
            this.on(document, 'workspaceSaved', this.onWorkspaceSaved);
            this.on(document, 'workspaceDeleted', this.onWorkspaceDeleted);
            this.on(document, 'workspaceUpdated', this.onWorkspaceUpdated);
            this.on(document, 'workspaceNotAvailable', this.onWorkspaceNotAvailable);

            this.on(document, 'menubarToggleDisplay', this.onToggleMenu);
            this.on(document, 'switchWorkspace', this.onSwitchWorkspace);

            this.on('workspaceDeleting', this.onWorkspaceDeleting);

            this.on('click', {
                workspaceListItemSelector: this.onWorkspaceItemClick,
                addNewSelector: this.onAddNew,
                disclosureSelector: this.onDisclosure
            });

            this.on('keyup', {
                addNewInputSelector: this.onInputKeyUp
            });

            this.$node.html(workspacesTemplate({}));
        });
    }

});
<|endoftext|>


<commit_before>angular.module('ion-alpha-scroll', [])
  .directive('ionAlphaScroll', [
    '$ionicScrollDelegate', '$location', '$timeout', '$document', '$ionicPosition',
    function ($ionicScrollDelegate, $location, $timeout, $document, $ionicPosition) {
      return {
        require: '?ngModel',
        restrict: 'AE',
        replace: true,
        compile: function (tElement, tAttrs, tTransclude) {
          var children = tElement.contents();
          var templateElements = [
            '<ion-list class="ion_alpha_list_outer">',
            '<ion-scroll delegate-handle="alphaScroll">',
            '
',
            '<ion-item class="item item-divider" id="index_{{letter}}">{{letter}}</ion-item>',
            '<ion-item ng-repeat="item in items"></ion-item>',
            '
',
            '</ion-scroll>',
            '
',
            '
{{ letter }}
',
            '
',
            '</ion-list>'
          ];
          var refresher = angular.fromJson(tAttrs.refresher);
          if (refresher) {
            var refresherElement = '<ion-refresher pulling-text="' + refresher.text + '" on-refresh="' + refresher.callback + '"> </ion-refresher>';
            templateElements.splice(2, 0, refresherElement);
          }
          var template = angular.element(templateElements.join(''));

          var headerHeight = $document[0].body.querySelector('.bar-header').offsetHeight;
          var subHeaderHeight = tAttrs.subheader === "true" ? 44 : 0;
          var tabHeight = $document[0].body.querySelector('.tab-nav') ? $document[0].body.querySelector('.tab-nav').offsetHeight : 0;
          var windowHeight = window.innerHeight;

          var topHeight = headerHeight + subHeaderHeight + tabHeight;
          var contentHeight = windowHeight - topHeight;

          angular.element(template.find('ion-item')[1]).append(children);
          tElement.html('');
          tElement.append(template);

          tElement.find('ion-scroll').css({
            "height": contentHeight + 'px'
          });

          return function (scope, element, attrs, ngModel) {
            // do nothing if the model is not set
            if (!ngModel) return;

            var sidebar = $document[0].body.querySelector('.ion_alpha_sidebar');

            ngModel.$render = function () {
              scope.items = [];
              scope.items = ngModel.$viewValue;
              var tmp = {};
              for (i = 0; i < scope.items.length; i++) {
                var letter = scope.items[i][attrs.key].toUpperCase().charAt(0);
                if (tmp[letter] == undefined) {
                  tmp[letter] = []
                }
                tmp[letter].push(scope.items[i]);
              }
              scope.alphabet = iterateAlphabet(tmp);
              scope.alphabetStr = scope.alphabet.join('');
              scope.sorted_items = tmp;

              scope.alphaSwipeGoToList = function ($event) {
                var y = $event.gesture.center.pageY - topHeight;
                if (y < 0) {
                  return;
                }

                var sidebarHeight = $ionicPosition.position(angular.element(sidebar)).height;
                var currentHeight = sidebarHeight - y;
                if (currentHeight < 0) {
                  return;
                }

                var alphabetHeight = sidebarHeight / scope.alphabet.length;
                var idx = scope.alphabet.length - parseInt(currentHeight / alphabetHeight) - 1;
                scope.alphaScrollGoToList('index_' + scope.alphabetStr.charAt(idx));
              };

              scope.alphaScrollGoToList = function (id) {
                $location.hash(id);
                $ionicScrollDelegate.$getByHandle('alphaScroll').anchorScroll(true);
              };

              //Create alphabet object
              function iterateAlphabet(alphabet) {
                var str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
                if (Object.keys(alphabet).length != 0) {
                  str = '';
                  for (var i = 0; i < Object.keys(alphabet).length; i++) {
                    str += Object.keys(alphabet)[i];
                  }
                }
                var numbers = new Array();
                for (var i = 0; i < str.length; i++) {
                  var nextChar = str.charAt(i);
                  numbers.push(nextChar);
                }
                return numbers;
              }
            };
          }
        }
      };
    }
  ]);<commit_msg>加itemStyle，更改默认item样式<commit_after>angular.module('ion-alpha-scroll', [])
  .directive('ionAlphaScroll', [
    '$ionicScrollDelegate', '$location', '$timeout', '$document', '$ionicPosition',
    function ($ionicScrollDelegate, $location, $timeout, $document, $ionicPosition) {
      return {
        require: '?ngModel',
        restrict: 'E',
        replace: true,
        compile: function (tElement, tAttrs, tTransclude) {
          var children = tElement.contents();
          var templateElements = [
            '<ion-list class="ion_alpha_list_outer">',
            '<ion-scroll delegate-handle="alphaScroll">',
            '
',
            '<ion-item class="item item-divider" id="index_{{letter}}">{{letter}}</ion-item>',
            '<ion-item ng-class="{true: itemStyle}[true]" ng-repeat="item in items"></ion-item>',
            '
',
            '</ion-scroll>',
            '
',
            '
{{ letter }}
',
            '
',
            '</ion-list>'
          ];
          var refresher = angular.fromJson(tAttrs.refresher);
          if (refresher) {
            var refresherElement = '<ion-refresher pulling-text="' + refresher.text + '" on-refresh="' + refresher.callback + '"> </ion-refresher>';
            templateElements.splice(2, 0, refresherElement);
          }
          var template = angular.element(templateElements.join(''));

          var headerHeight = $document[0].body.querySelector('.bar-header').offsetHeight;
          var subHeaderHeight = tAttrs.subheader === "true" ? 44 : 0;
          var tabHeight = $document[0].body.querySelector('.tab-nav') ? $document[0].body.querySelector('.tab-nav').offsetHeight : 0;
          var windowHeight = window.innerHeight;

          var topHeight = headerHeight + subHeaderHeight + tabHeight;
          var contentHeight = windowHeight - topHeight;

          angular.element(template.find('ion-item')[1]).append(children);
          tElement.html('');
          tElement.append(template);

          tElement.find('ion-scroll').css({
            "height": contentHeight + 'px'
          });

          return function (scope, element, attrs, ngModel) {
            // do nothing if the model is not set
            if (!ngModel) return;

            scope.itemStyle = attrs.itemStyle;
            var sidebar = $document[0].body.querySelector('.ion_alpha_sidebar');

            ngModel.$render = function () {
              scope.items = [];
              scope.items = ngModel.$viewValue;
              var tmp = {};
              for (i = 0; i < scope.items.length; i++) {
                var letter = scope.items[i][attrs.key].toUpperCase().charAt(0);
                if (tmp[letter] == undefined) {
                  tmp[letter] = []
                }
                tmp[letter].push(scope.items[i]);
              }
              scope.alphabet = iterateAlphabet(tmp);
              scope.alphabetStr = scope.alphabet.join('');
              scope.sorted_items = tmp;

              scope.alphaSwipeGoToList = function ($event) {
                var y = $event.gesture.center.pageY - topHeight;
                if (y < 0) {
                  return;
                }

                var sidebarHeight = $ionicPosition.position(angular.element(sidebar)).height;
                var currentHeight = sidebarHeight - y;
                if (currentHeight < 0) {
                  return;
                }

                var alphabetHeight = sidebarHeight / scope.alphabet.length;
                var idx = scope.alphabet.length - parseInt(currentHeight / alphabetHeight) - 1;
                scope.alphaScrollGoToList('index_' + scope.alphabetStr.charAt(idx));
              };

              scope.alphaScrollGoToList = function (id) {
                $location.hash(id);
                $ionicScrollDelegate.$getByHandle('alphaScroll').anchorScroll(true);
              };

              //Create alphabet object
              function iterateAlphabet(alphabet) {
                var str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
                if (Object.keys(alphabet).length != 0) {
                  str = '';
                  for (var i = 0; i < Object.keys(alphabet).length; i++) {
                    str += Object.keys(alphabet)[i];
                  }
                }
                var numbers = new Array();
                for (var i = 0; i < str.length; i++) {
                  var nextChar = str.charAt(i);
                  numbers.push(nextChar);
                }
                return numbers;
              }
            };
          }
        }
      };
    }
  ]);<|endoftext|>


<commit_before>import React, { useContext, useEffect, useState } from 'react';
import Fade from 'react-reveal/Fade';
import Tilt from 'react-tilt';
import { Container, Row, Col } from 'react-bootstrap';
import PortfolioContext from '../../context/context';
import Title from '../Title/Title';
import ProjectImg from '../Image/ProjectImg';

const Projects = () => {
  const { projects } = useContext(PortfolioContext);

  const [isDesktop, setIsDesktop] = useState(false);
  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    if (window.innerWidth > 769) {
      setIsDesktop(true);
      setIsMobile(false);
    } else {
      setIsMobile(true);
      setIsDesktop(false);
    }
  }, []);

  return (

      <Container>

          <Title title="Projects" />
          {projects.map(project => {
            const { id, title, info, info2, url, repo, img } = project;

            return (
              <Row key={id}>
                <Col lg={4} sm={12}>
                  <Fade
                    left={isDesktop}
                    bottom={isMobile}
                    duration={1000}
                    delay={500}
                    distance="30px"
                  >

{title || 'Project Title'}

                          {info ||
                            'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Excepturi neque, ipsa animi maiores repellendu distinctioaperiam earum dolor voluptatum consequatur blanditiis inventore debitis fuga numquam voluptate architecto itaque molestiae.'}

{info2 || ''}

                        See Live

                      {repo && (

                          Source Code

                      )}

                  </Fade>
                </Col>
                <Col lg={8} sm={12}>
                  <Fade
                    right={isDesktop}
                    bottom={isMobile}
                    duration={1000}
                    delay={1000}
                    distance="30px"
                  >

                        <Tilt
                          options={{
                            reverse: false,
                            max: 8,
                            perspective: 1000,
                            scale: 1,
                            speed: 300,
                            transition: true,
                            axis: null,
                            reset: true,
                            easing: 'cubic-bezier(.03,.98,.52,.99)',
                          }}
                        >

                            <ProjectImg alt={title} filename={img} />

                        </Tilt>

                  </Fade>
                </Col>
              </Row>
            );
          })}

      </Container>

  );
};

export default Projects;
<commit_msg>add aria-label<commit_after>import React, { useContext, useEffect, useState } from 'react';
import Fade from 'react-reveal/Fade';
import Tilt from 'react-tilt';
import { Container, Row, Col } from 'react-bootstrap';
import PortfolioContext from '../../context/context';
import Title from '../Title/Title';
import ProjectImg from '../Image/ProjectImg';

const Projects = () => {
  const { projects } = useContext(PortfolioContext);

  const [isDesktop, setIsDesktop] = useState(false);
  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    if (window.innerWidth > 769) {
      setIsDesktop(true);
      setIsMobile(false);
    } else {
      setIsMobile(true);
      setIsDesktop(false);
    }
  }, []);

  return (

      <Container>

          <Title title="Projects" />
          {projects.map(project => {
            const { id, title, info, info2, url, repo, img } = project;

            return (
              <Row key={id}>
                <Col lg={4} sm={12}>
                  <Fade
                    left={isDesktop}
                    bottom={isMobile}
                    duration={1000}
                    delay={500}
                    distance="30px"
                  >

{title || 'Project Title'}

                          {info ||
                            'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Excepturi neque, ipsa animi maiores repellendu distinctioaperiam earum dolor voluptatum consequatur blanditiis inventore debitis fuga numquam voluptate architecto itaque molestiae.'}

{info2 || ''}

                        See Live

                      {repo && (

                          Source Code

                      )}

                  </Fade>
                </Col>
                <Col lg={8} sm={12}>
                  <Fade
                    right={isDesktop}
                    bottom={isMobile}
                    duration={1000}
                    delay={1000}
                    distance="30px"
                  >

                        <Tilt
                          options={{
                            reverse: false,
                            max: 8,
                            perspective: 1000,
                            scale: 1,
                            speed: 300,
                            transition: true,
                            axis: null,
                            reset: true,
                            easing: 'cubic-bezier(.03,.98,.52,.99)',
                          }}
                        >

                            <ProjectImg alt={title} filename={img} />

                        </Tilt>

                  </Fade>
                </Col>
              </Row>
            );
          })}

      </Container>

  );
};

export default Projects;
<|endoftext|>


<commit_before>describe('New Staged Message view', function(){
  var $d = window.open('').document;

  beforeEach(function(){
    $d.open();
    $d.write(
'<select id="input_type" name="input_type"><option value="">--Choose One--</option><option value="text">text</option><option value="file">file</option><option value="list">list</option></select>

text

file
list

'
    );
    $d.close();
  });

  it('has an input selector', function(){
    var $input_type;
    expect(function(){
      $input_type = jQuery('select#input_type', $d);
    }).not.toThrow();

    expect($input_type).toBeDefined();
    expect($input_type).not.toBeNull();

    var $size;
    expect(function(){
      $size = $input_type.size();
    }).not.toThrow();

    expect($size).toEqual(1);
  });

  it('has an inputPane div', function(){
    var $input_pane;
    expect(function(){
      $input_pane = jQuery('div#inputPane', $d);
    }).not.toThrow();

    expect($input_pane).toBeDefined();
    expect($input_pane).not.toBeNull();

    var $size;
    expect(function(){
      $size = $input_pane.size();
    }).not.toThrow();

    expect($size).toEqual(1);
  });

  it('adds the appropriate div when the selector is changed', function(){
    $input_type = jQuery('select#input_type', $d);

    $input_type.val('file').change();
    expect($input_type.val()).toEqual('file');

    /* to be continued */
  });
});
<commit_msg>interim<commit_after>describe('New Staged Message view', function(){
  var $d = window.open('').document;

  beforeEach(function(){
    $d.open();
    $d.write(
'<select id="input_type" name="input_type"><option value="">--Choose One--</option><option value="text">text</option><option value="file">file</option><option value="list">list</option></select>

text

file
list

'
    );
    $d.close();
  });

  it('has an input selector', function(){
    var $input_type;
    expect(function(){
      $input_type = jQuery('select#input_type', $d);
    }).not.toThrow();

    expect($input_type).toBeDefined();
    expect($input_type).not.toBeNull();

    var $size;
    expect(function(){
      $size = $input_type.size();
    }).not.toThrow();

    expect($size).toEqual(1);
  });

  it('has an inputPane div', function(){
    var $input_pane;
    expect(function(){
      $input_pane = jQuery('div#inputPane', $d);
    }).not.toThrow();

    expect($input_pane).toBeDefined();
    expect($input_pane).not.toBeNull();

    var $size;
    expect(function(){
      $size = $input_pane.size();
    }).not.toThrow();

    expect($size).toEqual(1);
  });

  it('adds the appropriate div when the selector is changed', function(){
    $input_type = jQuery('select#input_type', $d);

    $input_type.val('file').trigger('change');
    expect($input_type.val()).toEqual('file');

    /* to be continued */
    var $input_pane;
    expect(function() {
      $input_pane = jQuery('div#inputPane', $d);
    }).not.toThrow();
    expect($input_pane).toBeDefined();
    expect($input_pane).not.toBeNull();
    expect($input_pane.children().size()).toEqual(1);
    expect($input_pane.text()).toMatch(/file/);
  });
});
<|endoftext|>


<commit_before>import React from 'react'
import classes from './Speakers.scss'
import SpeakersSection from 'components/SpeakersSection'
import alex from 'static/Alex.png'
import stephen from 'static/Stephen.png'
import jason from 'static/Jason.png'
import dan from 'static/Dan.png'
import logo from 'static/blueprint-logo-basic.png'

export const Speakers = () => (

 Speakers

        <SpeakersSection title={'Alex Dehgan, Conservation X Labs'}
          text={'Alex Dehgan is the founder of ConservationXLabs, a conservation technology \
          start-up focused on developing “Conservation 3.0.” He is also the former Chief Scientist at \
          the U.S. Agency for International Development (USAID) and a Rubenstein Fellow at Duke University. \
          As part of Obama’s promise to restore science and technology at USAID, Alex leveraged or \
          raised $500 million dollars in less than four years to establish the USAID Development Lab. \
          He also led efforts to create Afghanistan’s first national park, conducted the first \
          comprehensive biological surveys of the country in 30 years, helped develop Afghanistan’s \
          biodiversity conservation laws and policies, and curtailed illegal wildlife trade on US and \
          ISAF military bases. He was named an “Icon of Science” by Seed Magazine, and received the World \
          Technology Award for Policy in 2011.'}
          imagePath={alex} />
        <SpeakersSection title={'Stephen Lee, U.S. ARO'}
          text={'Dr. Stephen T. Lee is the Chief Scientist (ST) at the U.S. \
          Army Research Office (ARO). He is interested in how technology can be \
          used to combat illegal poaching and the wildlife trade in Southern Africa.\
          Dr. Lee makes the case that reducing the illegal wildlife trade and poaching in \
          Africa is in the best interest of the international community regarding safety, \
          security, conservation and goodwill. Further, that science and technology have \
          pivotal roles to play via next generation tools and capabilities.'}
          imagePath={stephen} />
        <SpeakersSection title={'Jason Heckathorn, Forever Oceans'}
          text={'Jason Heckathorn is a high-energy entrepreneur with a track record for success \
          in starting and growing businesses. Jason founded the Lockheed Martin Mobile \
          Fish Pens line of business by reusing defense technologies for sustainable fish \
          farming. He grew the business with commercial contract wins and a TIME Magazine \
          Invention of the Year Award.  In 2014 he became CEO of Forever Oceans when it spun-out.  \
          Current aquaculture focuses span the Middle East, Asia, Central America, & the Caribbean. \
          Jason thrives in markets that require creative business strategies and is bold in undertaking \
          new ventures. He is on the board of two other companies focused on sustainability: \
          Hidden Clover Children’s Books and BluBridge, a sea cucumber company.  \
          He spends his personal time leading missions and conservation trips into the jungles \
          of Central America.  Jason holds three Ivy League degrees in engineering and business \
          and served as a mentor for the KAUST (Saudi Arabia University) Entrepreneurship Program.'}
          imagePath={jason} />
        <SpeakersSection title={'Dan Fay, Microsoft Research'}
          text={'Daniel Fay is the Director of External Research for Earth, Energy, and Environment \
          engagements for Microsoft Research, where he works with academic research projects focused \
          on utilizing computing technologies to aid in scientific and engineering research. Dan has \
          project experience working with High Performance Computing, Grid Computing, collaboration and \
          visualization tools in scientific research. Dan was previously the manager of eScience Program \
          in Microsoft Research where he started Microsoft\'s engagements in eScience including the MSR \
          eScience workshop. Before joining Microsoft in 1992, Dan was a senior software engineer with \
          Digital Equipment Corporation working on OSF/1 and embedded network devices. Dan is a graduate \
          of Northeastern University in Electrical Engineering.'}
          imagePath={dan} />
        <SpeakersSection title={'Stay Tuned'}
          beforeText={'We will be adding speakers as we get closer to the event. Want to be a speaker at this event? '}
          linkText={'Let us know!'}
          link={'mailto:speakers@dukeblueprint.com'}
          imagePath={logo} />

)

export default Speakers
<commit_msg>removes 'stay tuned'<commit_after>import React from 'react'
import classes from './Speakers.scss'
import SpeakersSection from 'components/SpeakersSection'
import alex from 'static/Alex.png'
import stephen from 'static/Stephen.png'
import jason from 'static/Jason.png'
import dan from 'static/Dan.png'

export const Speakers = () => (

 Speakers

        <SpeakersSection title={'Alex Dehgan, Conservation X Labs'}
          text={'Alex Dehgan is the founder of ConservationXLabs, a conservation technology \
          start-up focused on developing “Conservation 3.0.” He is also the former Chief Scientist at \
          the U.S. Agency for International Development (USAID) and a Rubenstein Fellow at Duke University. \
          As part of Obama’s promise to restore science and technology at USAID, Alex leveraged or \
          raised $500 million dollars in less than four years to establish the USAID Development Lab. \
          He also led efforts to create Afghanistan’s first national park, conducted the first \
          comprehensive biological surveys of the country in 30 years, helped develop Afghanistan’s \
          biodiversity conservation laws and policies, and curtailed illegal wildlife trade on US and \
          ISAF military bases. He was named an “Icon of Science” by Seed Magazine, and received the World \
          Technology Award for Policy in 2011.'}
          imagePath={alex} />
        <SpeakersSection title={'Stephen Lee, U.S. ARO'}
          text={'Dr. Stephen T. Lee is the Chief Scientist (ST) at the U.S. \
          Army Research Office (ARO). He is interested in how technology can be \
          used to combat illegal poaching and the wildlife trade in Southern Africa.\
          Dr. Lee makes the case that reducing the illegal wildlife trade and poaching in \
          Africa is in the best interest of the international community regarding safety, \
          security, conservation and goodwill. Further, that science and technology have \
          pivotal roles to play via next generation tools and capabilities.'}
          imagePath={stephen} />
        <SpeakersSection title={'Jason Heckathorn, Forever Oceans'}
          text={'Jason Heckathorn is a high-energy entrepreneur with a track record for success \
          in starting and growing businesses. Jason founded the Lockheed Martin Mobile \
          Fish Pens line of business by reusing defense technologies for sustainable fish \
          farming. He grew the business with commercial contract wins and a TIME Magazine \
          Invention of the Year Award.  In 2014 he became CEO of Forever Oceans when it spun-out.  \
          Current aquaculture focuses span the Middle East, Asia, Central America, & the Caribbean. \
          Jason thrives in markets that require creative business strategies and is bold in undertaking \
          new ventures. He is on the board of two other companies focused on sustainability: \
          Hidden Clover Children’s Books and BluBridge, a sea cucumber company.  \
          He spends his personal time leading missions and conservation trips into the jungles \
          of Central America.  Jason holds three Ivy League degrees in engineering and business \
          and served as a mentor for the KAUST (Saudi Arabia University) Entrepreneurship Program.'}
          imagePath={jason} />
        <SpeakersSection title={'Dan Fay, Microsoft Research'}
          text={'Daniel Fay is the Director of External Research for Earth, Energy, and Environment \
          engagements for Microsoft Research, where he works with academic research projects focused \
          on utilizing computing technologies to aid in scientific and engineering research. Dan has \
          project experience working with High Performance Computing, Grid Computing, collaboration and \
          visualization tools in scientific research. Dan was previously the manager of eScience Program \
          in Microsoft Research where he started Microsoft\'s engagements in eScience including the MSR \
          eScience workshop. Before joining Microsoft in 1992, Dan was a senior software engineer with \
          Digital Equipment Corporation working on OSF/1 and embedded network devices. Dan is a graduate \
          of Northeastern University in Electrical Engineering.'}
          imagePath={dan} />

)

export default Speakers
<|endoftext|>


<commit_before>import '../../stylus/components/_tooltips.styl'

// Mixins
import Colorable from '../../mixins/colorable'
import Delayable from '../../mixins/delayable'
import Dependent from '../../mixins/dependent'
import Detachable from '../../mixins/detachable'
import Menuable from '../../mixins/menuable'
import Toggleable from '../../mixins/toggleable'

// Helpers
import { convertToUnit } from '../../util/helpers'

/* @vue/component */
export default {
  name: 'v-tooltip',

  mixins: [Colorable, Delayable, Dependent, Detachable, Menuable, Toggleable],

  props: {
    debounce: {
      type: [Number, String],
      default: 0
    },
    disabled: Boolean,
    fixed: {
      type: Boolean,
      default: true
    },
    openDelay: {
      type: [Number, String],
      default: 200
    },
    tag: {
      type: String,
      default: 'span'
    },
    transition: String,
    zIndex: {
      default: null
    }
  },

  data: () => ({
    calculatedMinWidth: 0,
    closeDependents: false
  }),

  computed: {
    calculatedLeft () {
      const { activator, content } = this.dimensions
      const unknown = !this.bottom && !this.left && !this.top && !this.right
      let left = 0

      if (this.top || this.bottom || unknown) {
        left = (
          activator.left +
          (activator.width / 2) -
          (content.width / 2)
        )
      } else if (this.left || this.right) {
        left = (
          activator.left +
          (this.right ? activator.width : -content.width) +
          (this.right ? 10 : -10)
        )
      }

      return `${this.calcXOverflow(left)}px`
    },
    calculatedTop () {
      const { activator, content } = this.dimensions
      let top = 0

      if (this.top || this.bottom) {
        top = (
          activator.top +
          (this.bottom ? activator.height : -content.height) +
          (this.bottom ? 10 : -10)
        )
      } else if (this.left || this.right) {
        top = (
          activator.top +
          (activator.height / 2) -
          (content.height / 2)
        )
      }

      return `${this.calcYOverflow(top + this.pageYOffset)}px`
    },
    classes () {
      return {
        'v-tooltip--top': this.top,
        'v-tooltip--right': this.right,
        'v-tooltip--bottom': this.bottom,
        'v-tooltip--left': this.left
      }
    },
    computedTransition () {
      if (this.transition) return this.transition
      if (this.top) return 'slide-y-reverse-transition'
      if (this.right) return 'slide-x-transition'
      if (this.bottom) return 'slide-y-transition'
      if (this.left) return 'slide-x-reverse-transition'
    },
    offsetY () {
      return this.top || this.bottom
    },
    offsetX () {
      return this.left || this.right
    },
    styles () {
      return {
        left: this.calculatedLeft,
        maxWidth: convertToUnit(this.maxWidth),
        opacity: this.isActive ? 0.9 : 0,
        top: this.calculatedTop,
        zIndex: this.zIndex || this.activeZIndex
      }
    }
  },

  mounted () {
    this.value && this.callActivate()
  },

  methods: {
    activate () {
      // Update coordinates and dimensions of menu
      // and its activator
      this.updateDimensions()
      // Start the transition
      requestAnimationFrame(this.startTransition)
    }
  },

  render (h) {
    const tooltip = h('div', {
      staticClass: 'v-tooltip__content',
      'class': this.addBackgroundColorClassChecks({
        [this.contentClass]: true,
        'menuable__content__active': this.isActive
      }),
      style: this.styles,
      attrs: this.getScopeIdAttrs(),
      directives: [{
        name: 'show',
        value: this.isContentActive
      }],
      ref: 'content'
    }, this.showLazyContent(this.$slots.default))

    return h(this.tag, {
      staticClass: 'v-tooltip',
      'class': this.classes
    }, [
      h('transition', {
        props: {
          name: this.computedTransition
        }
      }, [tooltip]),
      h('span', {
        on: this.disabled ? {} : {
          mouseenter: () => {
            this.runDelay('open', () => (this.isActive = true))
          },
          mouseleave: () => {
            this.runDelay('close', () => (this.isActive = false))
          }
        },
        ref: 'activator'
      }, this.$slots.activator)
    ])
  }
}
<commit_msg>enh(v-tooltip): added support for nudge props (#4527)<commit_after>import '../../stylus/components/_tooltips.styl'

// Mixins
import Colorable from '../../mixins/colorable'
import Delayable from '../../mixins/delayable'
import Dependent from '../../mixins/dependent'
import Detachable from '../../mixins/detachable'
import Menuable from '../../mixins/menuable'
import Toggleable from '../../mixins/toggleable'

// Helpers
import { convertToUnit } from '../../util/helpers'

/* @vue/component */
export default {
  name: 'v-tooltip',

  mixins: [Colorable, Delayable, Dependent, Detachable, Menuable, Toggleable],

  props: {
    debounce: {
      type: [Number, String],
      default: 0
    },
    disabled: Boolean,
    fixed: {
      type: Boolean,
      default: true
    },
    openDelay: {
      type: [Number, String],
      default: 200
    },
    tag: {
      type: String,
      default: 'span'
    },
    transition: String,
    zIndex: {
      default: null
    }
  },

  data: () => ({
    calculatedMinWidth: 0,
    closeDependents: false
  }),

  computed: {
    calculatedLeft () {
      const { activator, content } = this.dimensions
      const unknown = !this.bottom && !this.left && !this.top && !this.right
      let left = 0

      if (this.top || this.bottom || unknown) {
        left = (
          activator.left +
          (activator.width / 2) -
          (content.width / 2)
        )
      } else if (this.left || this.right) {
        left = (
          activator.left +
          (this.right ? activator.width : -content.width) +
          (this.right ? 10 : -10)
        )
      }

      if (this.nudgeLeft) left -= parseInt(this.nudgeLeft)
      if (this.nudgeRight) left += parseInt(this.nudgeRight)

      return `${this.calcXOverflow(left)}px`
    },
    calculatedTop () {
      const { activator, content } = this.dimensions
      let top = 0

      if (this.top || this.bottom) {
        top = (
          activator.top +
          (this.bottom ? activator.height : -content.height) +
          (this.bottom ? 10 : -10)
        )
      } else if (this.left || this.right) {
        top = (
          activator.top +
          (activator.height / 2) -
          (content.height / 2)
        )
      }

      if (this.nudgeTop) top -= parseInt(this.nudgeTop)
      if (this.nudgeBottom) top += parseInt(this.nudgeBottom)

      return `${this.calcYOverflow(top + this.pageYOffset)}px`
    },
    classes () {
      return {
        'v-tooltip--top': this.top,
        'v-tooltip--right': this.right,
        'v-tooltip--bottom': this.bottom,
        'v-tooltip--left': this.left
      }
    },
    computedTransition () {
      if (this.transition) return this.transition
      if (this.top) return 'slide-y-reverse-transition'
      if (this.right) return 'slide-x-transition'
      if (this.bottom) return 'slide-y-transition'
      if (this.left) return 'slide-x-reverse-transition'
    },
    offsetY () {
      return this.top || this.bottom
    },
    offsetX () {
      return this.left || this.right
    },
    styles () {
      return {
        left: this.calculatedLeft,
        maxWidth: convertToUnit(this.maxWidth),
        opacity: this.isActive ? 0.9 : 0,
        top: this.calculatedTop,
        zIndex: this.zIndex || this.activeZIndex
      }
    }
  },

  mounted () {
    this.value && this.callActivate()
  },

  methods: {
    activate () {
      // Update coordinates and dimensions of menu
      // and its activator
      this.updateDimensions()
      // Start the transition
      requestAnimationFrame(this.startTransition)
    }
  },

  render (h) {
    const tooltip = h('div', {
      staticClass: 'v-tooltip__content',
      'class': this.addBackgroundColorClassChecks({
        [this.contentClass]: true,
        'menuable__content__active': this.isActive
      }),
      style: this.styles,
      attrs: this.getScopeIdAttrs(),
      directives: [{
        name: 'show',
        value: this.isContentActive
      }],
      ref: 'content'
    }, this.showLazyContent(this.$slots.default))

    return h(this.tag, {
      staticClass: 'v-tooltip',
      'class': this.classes
    }, [
      h('transition', {
        props: {
          name: this.computedTransition
        }
      }, [tooltip]),
      h('span', {
        on: this.disabled ? {} : {
          mouseenter: () => {
            this.runDelay('open', () => (this.isActive = true))
          },
          mouseleave: () => {
            this.runDelay('close', () => (this.isActive = false))
          }
        },
        ref: 'activator'
      }, this.$slots.activator)
    ])
  }
}
<|endoftext|>


<commit_before>var fs = require('fs');
ft = require('file-tail').startTailing('/var/log/secure');
var shell = require('shelljs');
var bufferString;

var listOfIpAddresses = [];
var bannedIPs = [];

var ipRegEx = /\d+\.\d+\.\d+\.\d+/;

var timeToCheck = process.argv[2];
var timeBeforeBan = process.argv[3];
var unbanTime = process.argv[4]

if(!process.argv[2] || !process.argv[3]){
    console.log("Usage: node ips.js timesToCheckBeforeBanning timeBeforeBanInSeconds timeToBanForInSeconds");
    process.exit(1);
}

setInterval(function(){
    //check to see if we should unban people
    currentTime = new Date();
    for(var index in bannedIPs){
        var indexTime = bannedIPs[index];
        var difference = currentTime - indexTime;
        if(difference > unbanTime){
            console.log("Unbanning " + index + ".");
            shell.exec("iptables -D INPUT -s " + index + " -j DROP");
            bannedIPs.splice(index, 1);
        }
    }
});

var logObject = function(ipAddress, primaryDateTimeOfFirstViolation){
    this.ipAddress = ipAddress;
    this.dateTime = [];
    //this.timeToCheck = process.argv[2];

    this.dateTime[0] = primaryDateTimeOfFirstViolation;

    this.addViolation = function(dateTimeOfViolation){
        //we only want to keep the last 10 failed attempts
        if(this.dateTime.length > 10){
            this.dateTime.shift();
        }
        // we add a violation to the datetime array here
        this.dateTime.push(dateTimeOfViolation);
        this.checkIfBan();
    }

    this.checkIfBan = function(){
        // check last n date Time's that they violated and see if
        // we need to ban them
        if(this.dateTime.length > timeToCheck){
            console.log("More attempts than allowed detected.");
            var lastLog = this.dateTime[this.dateTime.length - 1];
            var firstLog = this.dateTime[this.dateTime.length - 1 - timeToCheck];
            var timeDifference = (lastLog - firstLog) / 1000;
            //if difference between last attempt and first of range we want to check
            //is less than the time allowed for failed attempts set by user
            if(timeDifference <= timeBeforeBan){
                shell.exec("iptables -A INPUT -s " + this.ipAddress + " -j DROP");
                console.log(this.ipAddress + " has been banned");
                //add to banned array
                this.bannedIPs[this.ipAddress] = new Date();
            }

            //check for slow scan
            var countSlowFails = 0;
            //check for slow scan/patterns
            for(var i = 0; i < timeToCheck; i++){
                var timeOne = this.dateTime[this.dateTime.length - 1 - i]
                var timeTwo = this.dateTime[this.dateTime.length - 1 - (i + 1)]
                var timeDifference = (timeOne - timeTwo) / 1000;
                //if the previous time difference is equal to this time difference
                if(timeDifference >= ((timeBeforeBan / timeToCheck) - 1)){
                    console.log("Incrementing countSlowFails.");
                    countSlowFails++;
                    console.log("countSlowFails currently at: " + countSlowFails);
                }
                if(countSlowFails == timeToCheck){
                    shell.exec("iptables -A INPUT -s " + this.ipAddress + " -j DROP");
                    console.log("Slow scan via periodical attempts detected from IP: " + this.ipAddress);
                    console.log(this.ipAddress + " has been banned");
                    //add to banned array
                    this.bannedIPs[this.ipAddress] = new Date()
                    //iptables -D INPUT -s this.ipAddress -j DROP
                }
                prev = timeDifference;
            }
        }
    }

}

ft.on('line', function(line){
    bufferString += line;
    if(line.indexOf('Failed') > -1){
        var ip = line.match(ipRegEx);

        if(!listOfIpAddresses[ip[0]]){

        var lobj = new logObject(ip[0], new Date());
        listOfIpAddresses[ip[0]] = lobj;
    } else {
        listOfIpAddresses[ip[0]].addViolation(new Date());
    }
}

});
<commit_msg>don't need this, I think<commit_after>var fs = require('fs');
ft = require('file-tail').startTailing('/var/log/secure');
var shell = require('shelljs');
var bufferString;

var listOfIpAddresses = [];
var bannedIPs = [];

var ipRegEx = /\d+\.\d+\.\d+\.\d+/;

var timeToCheck = process.argv[2];
var timeBeforeBan = process.argv[3];
var unbanTime = process.argv[4]

if(!process.argv[2] || !process.argv[3]){
    console.log("Usage: node ips.js timesToCheckBeforeBanning timeBeforeBanInSeconds timeToBanForInSeconds");
    process.exit(1);
}

setInterval(function(){
    //check to see if we should unban people
    currentTime = new Date();
    for(var index in bannedIPs){
        var indexTime = bannedIPs[index];
        var difference = currentTime - indexTime;
        if(difference > unbanTime){
            console.log("Unbanning " + index + ".");
            shell.exec("iptables -D INPUT -s " + index + " -j DROP");
            bannedIPs.splice(index, 1);
        }
    }
});

var logObject = function(ipAddress, primaryDateTimeOfFirstViolation){
    this.ipAddress = ipAddress;
    this.dateTime = [];
    //this.timeToCheck = process.argv[2];

    this.dateTime[0] = primaryDateTimeOfFirstViolation;

    this.addViolation = function(dateTimeOfViolation){
        //we only want to keep the last 10 failed attempts
        if(this.dateTime.length > 10){
            this.dateTime.shift();
        }
        // we add a violation to the datetime array here
        this.dateTime.push(dateTimeOfViolation);
        this.checkIfBan();
    }

    this.checkIfBan = function(){
        // check last n date Time's that they violated and see if
        // we need to ban them
        if(this.dateTime.length > timeToCheck){
            console.log("More attempts than allowed detected.");
            var lastLog = this.dateTime[this.dateTime.length - 1];
            var firstLog = this.dateTime[this.dateTime.length - 1 - timeToCheck];
            var timeDifference = (lastLog - firstLog) / 1000;
            //if difference between last attempt and first of range we want to check
            //is less than the time allowed for failed attempts set by user
            if(timeDifference <= timeBeforeBan){
                shell.exec("iptables -A INPUT -s " + this.ipAddress + " -j DROP");
                console.log(this.ipAddress + " has been banned");
                //add to banned array
                bannedIPs[this.ipAddress] = new Date();
            }

            //check for slow scan
            var countSlowFails = 0;
            //check for slow scan/patterns
            for(var i = 0; i < timeToCheck; i++){
                var timeOne = this.dateTime[this.dateTime.length - 1 - i]
                var timeTwo = this.dateTime[this.dateTime.length - 1 - (i + 1)]
                var timeDifference = (timeOne - timeTwo) / 1000;
                //if the previous time difference is equal to this time difference
                if(timeDifference >= ((timeBeforeBan / timeToCheck) - 1)){
                    console.log("Incrementing countSlowFails.");
                    countSlowFails++;
                    console.log("countSlowFails currently at: " + countSlowFails);
                }
                if(countSlowFails == timeToCheck){
                    shell.exec("iptables -A INPUT -s " + this.ipAddress + " -j DROP");
                    console.log("Slow scan via periodical attempts detected from IP: " + this.ipAddress);
                    console.log(this.ipAddress + " has been banned");
                    //add to banned array
                    this.bannedIPs[this.ipAddress] = new Date()
                    //iptables -D INPUT -s this.ipAddress -j DROP
                }
                prev = timeDifference;
            }
        }
    }

}

ft.on('line', function(line){
    bufferString += line;
    if(line.indexOf('Failed') > -1){
        var ip = line.match(ipRegEx);

        if(!listOfIpAddresses[ip[0]]){

        var lobj = new logObject(ip[0], new Date());
        listOfIpAddresses[ip[0]] = lobj;
    } else {
        listOfIpAddresses[ip[0]].addViolation(new Date());
    }
}

});
<|endoftext|>


<commit_before>import React from 'react';
import { FormattedMessage, FormattedNumber, injectIntl } from 'react-intl';
import ArrowDropDownIcon from 'material-ui/svg-icons/navigation/arrow-drop-down';
import messages from '../../resources/messages';
import { storyPubDateToTimestamp } from '../../lib/dateUtil';
import { googleFavIconUrl, storyDomainName } from '../../lib/urlUtil';

const localMessages = {
  undateable: { id: 'story.publishDate.undateable', defaultMessage: 'Undateable' },
  foci: { id: 'story.foci.list', defaultMessage: 'List of Subtopics {list}' },
};

const ICON_STYLE = { margin: 0, padding: 0, width: 12, height: 12 };

class StoryTable extends React.Component {

  sortBySocial = () => {
    const { onChangeSort } = this.props;
    onChangeSort('social');
  }

  render() {
    const { stories, onChangeSort, sortedBy, maxTitleLength } = this.props;
    const { formatMessage, formatDate } = this.props.intl;
    let socialHeader = null;
    if ((onChangeSort !== undefined) && (onChangeSort !== null)) {
      if (sortedBy === 'social') {
        socialHeader = (

            <FormattedMessage {...messages.bitlyClicks} />
            <ArrowDropDownIcon style={ICON_STYLE} />

        );
      } else {
        socialHeader = (
          <a
            href={`#${formatMessage(messages.sortByBitlyClicks)}`}
            onClick={(e) => { e.preventDefault(); this.sortBySocial(); }}
            title={formatMessage(messages.sortByBitlyClicks)}
          >
            <FormattedMessage {...messages.bitlyClicks} />

        );
      }
    } else {
      socialHeader = <FormattedMessage {...messages.bitlyClicks} />;
    }
    return (

              <FormattedMessage {...messages.storyTitle} />
              {}
              <FormattedMessage {...messages.media} />
              <FormattedMessage {...messages.storyDate} />
              {socialHeader}
              {}

            {stories.map((story, idx) => {
              const domain = storyDomainName(story);
              let dateToShow = null;  // need to handle undateable stories
              let dateStyle = '';
              const title = maxTitleLength !== undefined ? `${story.title.substr(0, maxTitleLength)}...` : story.title;
              if (story.publish_date === 'undateable') {
                dateToShow = formatMessage(localMessages.undateable);
                dateStyle = 'story-date-undateable';
              } else {
                dateToShow = formatDate(storyPubDateToTimestamp(story.publish_date));
                dateStyle = (story.date_is_reliable === 0) ? 'story-date-unreliable' : 'story-date-reliable';
                if (story.date_is_reliable === 0) {
                  dateToShow += '?';
                }
              }
              return (

                    {title}

                    {story.media_name}

                  <span className={`story-date ${dateStyle}`}>{dateToShow}
                  <FormattedNumber value={story.bitly_click_count !== undefined && story.bitly_click_count !== null ? story.bitly_click_count : '?'} />

              );
            }
            )}

    );
  }

}

StoryTable.propTypes = {
  stories: React.PropTypes.array.isRequired,
  intl: React.PropTypes.object.isRequired,
  onChangeSort: React.PropTypes.func,
  onChangeFocusSelection: React.PropTypes.func,
  sortedBy: React.PropTypes.string,
  maxTitleLength: React.PropTypes.number,
};

export default injectIntl(StoryTable);
<commit_msg>show "unknown" if no bitly counts<commit_after>import React from 'react';
import { FormattedMessage, FormattedNumber, injectIntl } from 'react-intl';
import ArrowDropDownIcon from 'material-ui/svg-icons/navigation/arrow-drop-down';
import messages from '../../resources/messages';
import { storyPubDateToTimestamp } from '../../lib/dateUtil';
import { googleFavIconUrl, storyDomainName } from '../../lib/urlUtil';

const localMessages = {
  undateable: { id: 'story.publishDate.undateable', defaultMessage: 'Undateable' },
  foci: { id: 'story.foci.list', defaultMessage: 'List of Subtopics {list}' },
};

const ICON_STYLE = { margin: 0, padding: 0, width: 12, height: 12 };

class StoryTable extends React.Component {

  sortBySocial = () => {
    const { onChangeSort } = this.props;
    onChangeSort('social');
  }

  render() {
    const { stories, onChangeSort, sortedBy, maxTitleLength } = this.props;
    const { formatMessage, formatDate } = this.props.intl;
    let socialHeader = null;
    if ((onChangeSort !== undefined) && (onChangeSort !== null)) {
      if (sortedBy === 'social') {
        socialHeader = (

            <FormattedMessage {...messages.bitlyClicks} />
            <ArrowDropDownIcon style={ICON_STYLE} />

        );
      } else {
        socialHeader = (
          <a
            href={`#${formatMessage(messages.sortByBitlyClicks)}`}
            onClick={(e) => { e.preventDefault(); this.sortBySocial(); }}
            title={formatMessage(messages.sortByBitlyClicks)}
          >
            <FormattedMessage {...messages.bitlyClicks} />

        );
      }
    } else {
      socialHeader = <FormattedMessage {...messages.bitlyClicks} />;
    }
    return (

              <FormattedMessage {...messages.storyTitle} />
              {}
              <FormattedMessage {...messages.media} />
              <FormattedMessage {...messages.storyDate} />
              {socialHeader}
              {}

            {stories.map((story, idx) => {
              const domain = storyDomainName(story);
              let dateToShow = null;  // need to handle undateable stories
              let dateStyle = '';
              const title = maxTitleLength !== undefined ? `${story.title.substr(0, maxTitleLength)}...` : story.title;
              if (story.publish_date === 'undateable') {
                dateToShow = formatMessage(localMessages.undateable);
                dateStyle = 'story-date-undateable';
              } else {
                dateToShow = formatDate(storyPubDateToTimestamp(story.publish_date));
                dateStyle = (story.date_is_reliable === 0) ? 'story-date-unreliable' : 'story-date-reliable';
                if (story.date_is_reliable === 0) {
                  dateToShow += '?';
                }
              }
              const bitlyContent = story.bitly_click_count ? <FormattedNumber value={story.bitly_click_count} /> : <FormattedMessage {...messages.unknown} />;
              return (

                    {title}

                    {story.media_name}

                  <span className={`story-date ${dateStyle}`}>{dateToShow}
                  {bitlyContent}

              );
            }
            )}

    );
  }

}

StoryTable.propTypes = {
  stories: React.PropTypes.array.isRequired,
  intl: React.PropTypes.object.isRequired,
  onChangeSort: React.PropTypes.func,
  onChangeFocusSelection: React.PropTypes.func,
  sortedBy: React.PropTypes.string,
  maxTitleLength: React.PropTypes.number,
};

export default injectIntl(StoryTable);
<|endoftext|>