<commit_before>apos.define('apostrophe-ui', {

  afterConstruct: function(self) {
    $(function() {
      self.enablePrefix();
      self.enableShift();
      self.enableSelectize();
      self.enablePreview();
      self.enableTabbedModals();
      self.enableSkipdown();
      self.enableClickUrl();
      self.enableStyledFileButtons();
      self.enableActionable();
      self.enhanceDropdowns();
      self.enhanceAdminMenu();
      apos.emit('enhance', $('body'));
    });
  },

  construct: function(self, options) {
    self.enableShift = function() {
      // Status of the shift key. Automatically updated.
      self.shiftActive = false;
      var $body = $('body');
      $body.keydown(function(e) {
        if (e.keyCode === 16) {
          self.shiftActive = true;
          apos.emit('shiftDown', e);
        }
      });

      $body.keyup(function(e) {
        if (e.keyCode === 16) {
          self.shiftActive = false;
          apos.emit('shiftUp', e);
        }
      });
    };

    self.enableSelectize = function() {

      // Extend selectize to initiate select options on startup.
      // This will allow us to show fields from the default value
      $.extend(Selectize.prototype, {
          superSetup: Selectize.prototype.setup,
          setup: function() {
              this.superSetup();
              this.refreshOptions(false);
          }
      });

      apos.on('enhance', function($el) {

        // Selectize - Single Select
        $el.find('select[data-selectize]:not(.apos-template select[data-selectize], [select="multiple"])').selectize({
          create: false,
          sortField: 'text',
          dataAttr: 'data-extra',
          searchField: ['label', 'value'],
          valueField: 'value',
          labelField: 'label',

          // We need to render custom templates to include our data-extra fields.
          //
          // If you need to include extra fields on a select option
          // format a JSON string in 'data-extra' like this:
          //
          // <option data-extra='{ "myField": "thing" }' > Label </option>
          //
          // -matt
          render: {
            item: function(data) {
              var attrs = ' data-value="'+data.value+'"';
              for (key in _.omit(data, ['value', 'label', '$order'])) {
                attrs = attrs + ' data-' + key + '="' + data[key] + '"';
              }
              return '
' + data.label + '
';
            },
            option: function(data) {
              var attrs = 'data-value="'+data.value+'"';
              for (key in _.omit(data, ['value', 'label', '$order'])) {
                attrs = attrs + ' data-' + key + '="' + data[key] + '"';
              }
              return '
' + data.label + '
';
            }
          }
        });

        // Selectize - Multi Select
        $el.find('select[data-selectize][select="multiple"]:not(.apos-template select[data-selectize])').selectize({
          maxItems: null,
          delimiter: ', ',
        });
      });
    };

    self.enablePreview = function() {
      var $body = $('body');
      $body.on('click', '.apos-preview-toggle', function(event){
        console.log('REFACTOR THIS TO USE DATA ATTR');
        $('.apos-preview-toggle').toggleClass('previewing');
        $body.toggleClass('previewing');
      });
    }

    self.enableTabbedModals = function() {
      //sets up listeners for tabbed modals
      var $body = $('body');
      $body.on('click', '.apos-modal-tab-title', function(){
        console.log('REFACTOR THIS TO USE DATA ATTR');
        $(this).closest('.apos-modal-tabs').find('.apos-active').removeClass('apos-active');
        $(this).closest('.apos-modal-tabs').find('[data-tab-id="'+$(this).attr('data-tab')+'"]').addClass('apos-active');
        $(this).addClass('apos-active');
      });
    }

    // Utility that lets you add the data attribute 'data-apos-actionable' to any element and have
    // it's 'apos-active' class be toggled on click.
    // optionally you can give the attr a value which can be used to target another element's active
    // class be toggled.
    self.enableActionable = function() {
      var $body = $('body');
      $body.on('click', '[data-apos-actionable]', function(event) {
        var $el = $(this);
        if ($el.attr('data-apos-actionable')) {
          // $el.closest('[' + $el.attr('data-apos-actionable') + ']').toggleClass('apos-active');
          $el.closest('[' + $el.attr('data-apos-actionable') + ']').toggleClass('apos-active');
        } else {
          $el.toggleClass('apos-active');
        }
      });
    }

    self.enhanceAdminMenu = function() {
      var $bar = $('[data-apos-admin-bar]');
      var $dropdowns = $('[data-apos-dropdown]');
      $bar.css('overflow', 'visible');

      // on transitionend, turn overflow on so we can see dropdowns!
      $bar.find('[data-apos-admin-bar-items]').on('transitionend', function() {
        if ($bar.hasClass('apos-active')) {
          $bar.css('overflow', 'visible');
        } else {
          $bar.css('overflow', '');
        }
      });

      // when we collapse the menu, turn all dropdowns off
      $bar.find('[data-apos-admin-bar-logo]').on('click', function(){
        $bar.find('[data-apos-dropdown]').removeClass('apos-active');
      });

      // when the bar is clicked, make a note of it so we don't auto
      // collapse the menu from load
      $bar.on('click', function(){ $bar.addClass('apos-admin-bar--clicked') });

      setTimeout(function() {
        if (!$bar.hasClass('apos-admin-bar--clicked')) {
          $bar.removeClass('apos-active');
        }
      }, 3000);

      // when opening dropdowns, close other dropdowns
      $dropdowns.on('click', function(){
        $bar.find('[data-apos-dropdown]').not(this).removeClass('apos-active');
      });

    }

    self.enhanceDropdowns = function() {
      $('body').on('click change', '[data-apos-dropdown]', function(event) {
        event.preventDefault();
        var $this = $(this);
        if ($this.find('[data-apos-dropdown-button-label]').length) {
          // only adjust width for button'y dropdowns
          // not global menu dropdowns
          var $label = $(this).find('[data-apos-dropdown-button-label]');
          if ($label.attr('data-apos-dropdown-button-label') === 'active') {
            $label.css('padding-right', $label.attr('data-apos-original-right-padding'));
            $label.attr('data-apos-dropdown-button-label', '');
          } else {
            $label.attr('data-apos-original-right-padding', $label.css('padding-right'));
            var val = parseInt($label.attr('data-apos-original-right-padding').substring(0, $label.attr('data-apos-original-right-padding').length - 2));
            var extra = ($(this).find('[data-apos-dropdown-items]')[0].getBoundingClientRect().width - $label[0].getBoundingClientRect().width + val );
            $label.css('padding-right', extra);
            $label.attr('data-apos-dropdown-button-label', 'active');
          }
        }
      })
    }

    self.enableSkipdown = function() {
      // If the URL ends in #skipdown, locate the element with the
      // attribute data-skip-down-to and scroll to it, then remove
      // #skipdown from the URL. Useful for "poor man's AJAX" situations
      // where a page refresh is needed but you don't want the user to
      // be forced to scroll past the logo, etc. again.

      if (document.location.hash === '#skipdown') {
        document.location.hash = '';
        // slice off the remaining '#' in modern browsers
        if (window.history && (typeof window.history.replaceState === 'function')) {
          history.replaceState({}, '', window.location.href.slice(0, -1));
        }
        // Must yield first or this has no effect in Chrome.
        setImmediate(function() {
          $('html, body').scrollTop($('[data-skip-down-to]').offset().top - 50);
        });
      }
    };

    // If the URL ends in: #click-whatever
    //
    // ... Then we locate an element with the attribute data-whatever,
    // and trigger a click event on it.
    //
    // This is useful for resuming an activity after requiring the
    // user to log in.

    self.enableClickUrl = function() {

      // Waiting long enough for both the click and the autoscroll to work is
      // tricky. We need to yield beyond DOMready so that other code installing click
      // handlers on DOMready has had time to do so. And we need to yield a little
      // extra time or the browser will crush our efforts to set scrollTop based on
      // its own idea of where we are on the page (at least in Chrome). ):

      setTimeout(function() {
        var hash = window.location.hash;
        var matches = hash.match(/^\#click\-(.*)$/);
        if (matches) {
          var $element = $('[data-' + matches[1] + ']');
          if ($element.length) {
            // Scroll back to the right neighborhood
            var offset = $element.offset();
            var scrollTop = offset.top - 100;
            $('html, body').scrollTop(scrollTop);
            // Now carry out the action
            $('[data-' + matches[1] + ']').trigger('click');
          }
        }
      }, 200);
    };

    self.enablePrefix = function(prefix) {
      // If Apostrophe has a global URL prefix, patch
      // jQuery's AJAX capabilities to prepend that prefix
      // to any non-absolute URL

      if (prefix) {
        $.ajaxPrefilter(function(options, originalOptions, jqXHR) {
          if (options.url) {
            if (!options.url.match(/^[a-zA-Z]+:/))
            {
              options.url = prefix + options.url;
            }
          }
          return;
        });
      }
    };

    self.enableStyledFileButtons = function() {
      // Click the original file upload button if the styled
      // proxy for it is clicked. Allows indirect styling of
      // file buttons
      $('body').on('click', '[data-file-styled]', function() {
        $(this).parent().children('input').click();
      });
    };

    self.globalBusyCounter = 0;

    // If state is true, the interface changes to
    // indicate Apostrophe is busy loading a modal
    // dialog or other experience that preempts other
    // activities. If state is false, the interface
    // is unlocked. Calls may be nested and the
    // interface will not unlock until all
    // locks are released.
    //
    // See also apos.busy for interactions that
    // only need to indicate that one particular
    // element is busy.

    self.globalBusy = function(state) {
      if (state) {
        self.globalBusyCounter++;
        if (self.globalBusyCounter === 1) {
          // Newly busy
          self.globalLock();
        }
      } else {
        self.globalBusyCounter--;
        if (!self.globalBusyCounter) {
          // newly free
          self.globalUnlock();
        }
      }
    };

    // Do not call this method yourself. It is called
    // by self.globalBusy to display the global spinner.
    // Freel free to override this method to change the UI.

    self.globalLock = function() {
      var $freezer = $('

');
      $('body').append($freezer);
    };

    // Do not call this method yourself. It is called
    // by self.globalBusy to hide the global spinner.
    // Freel free to override this method to change the UI.

    self.globalUnlock = function() {
      $('.apos-global-busy').remove();
    }

    // Simple progress display. Locates a progress display
    // inside the given element. If state is true, the
    // element with a [data-progress] attribute is shown,
    // otherwise the element with a [data-finished] attribute
    // is shown. Neither element is required. Supports
    // nested calls; does not revert to indicating complete
    // until the nesting level is 0.

    self.busy = function($el, state) {
      var busy = $el.data('busy') || 0;
      if (state) {
        busy++;
        $el.data('busy', busy);
        if (busy === 1) {
          $el.find('[data-progress]').show();
          $el.find('[data-finished]').hide();
        }
      } else {
        busy--;
        $el.data('busy', busy);
        if (!busy) {
          $el.find('[data-progress]').hide();
          $el.find('[data-finished]').show();
        }
      }
    };

    // Redirect correctly to the given location on the
    // Apostrophe site, even if the prefix option is in use
    // (you should provide a non-prefixed path)

    self.redirect = function(slug) {
      var href = apos.prefix + slug;
      if (href === window.location.href) {
        window.location.reload();
      } else {
        window.location.href = href;
      }
    };

    // Enhance a plaintext date field with a pikaday date widget.
    self.enhanceDate = function($el, options) {
      if (!options) {
        options = {};
      }

      new Pikaday({
        field: $el[0],
        format: 'YYYY-MM-DD'
      });
    };

    // Converts apostrophe 24hr times to local time
    self.formatTime = function(time, options) {
      if(!options) {
        options = {};
      }

      return moment(time, 'HH:mm:ss').format('h:mma');
    };

    // Status of the shift key. Automatically updated.c
    self.shiftActive = false;

    // Add a callback when a link is clicked to trigger
    // a certain action.
    //
    // Example:
    //
    // self.link($el, 'edit', 'blogPost', fn)
    //
    // When a click occurs on anything inside $el with a
    // data-edit-blog-post attribute, invoke
    // "fn" with the jQuery element clicked and the value
    // of the attribute.
    //
    // self.link('apos-manage', 'blogPost', fn)
    //
    // When a click occurs anywhere on the page
    // on something with a data-manage-blog-post
    // attribute, invoke "fn" with the jquery element
    // clicked and the value of that attribute.
    //
    // Event propagation and the default behavior of
    // the click event are both automatically stopped.
    //
    // The word "object" refers to "the object of the sentence."
    // It is a STRING, not a javascript object.

    self.link = function(sel, verb, object, callback) {
      if (arguments.length === 3) {
        callback = object;
        object = verb;
        verb = sel;
        sel = 'body';
      }
      var attribute;
      if (object === null) {
        attribute = 'data-' + apos.utils.cssName(verb);
      } else {
        attribute = 'data-' + apos.utils.cssName(verb) + '-' + apos.utils.cssName(object);
      }
      var attributeSel = '[' + attribute + ']';
      $(sel).on('click', attributeSel, function() {
        callback($(this), $(this).attr(attribute));
        return false;
      });
    };

    apos.ui = self;
  }
});
<commit_msg>Don't show overflow while transitioning the admin bar<commit_after>apos.define('apostrophe-ui', {

  afterConstruct: function(self) {
    $(function() {
      self.enablePrefix();
      self.enableShift();
      self.enableSelectize();
      self.enablePreview();
      self.enableTabbedModals();
      self.enableSkipdown();
      self.enableClickUrl();
      self.enableStyledFileButtons();
      self.enableActionable();
      self.enhanceDropdowns();
      self.enhanceAdminMenu();
      apos.emit('enhance', $('body'));
    });
  },

  construct: function(self, options) {
    self.enableShift = function() {
      // Status of the shift key. Automatically updated.
      self.shiftActive = false;
      var $body = $('body');
      $body.keydown(function(e) {
        if (e.keyCode === 16) {
          self.shiftActive = true;
          apos.emit('shiftDown', e);
        }
      });

      $body.keyup(function(e) {
        if (e.keyCode === 16) {
          self.shiftActive = false;
          apos.emit('shiftUp', e);
        }
      });
    };

    self.enableSelectize = function() {

      // Extend selectize to initiate select options on startup.
      // This will allow us to show fields from the default value
      $.extend(Selectize.prototype, {
          superSetup: Selectize.prototype.setup,
          setup: function() {
              this.superSetup();
              this.refreshOptions(false);
          }
      });

      apos.on('enhance', function($el) {

        // Selectize - Single Select
        $el.find('select[data-selectize]:not(.apos-template select[data-selectize], [select="multiple"])').selectize({
          create: false,
          sortField: 'text',
          dataAttr: 'data-extra',
          searchField: ['label', 'value'],
          valueField: 'value',
          labelField: 'label',

          // We need to render custom templates to include our data-extra fields.
          //
          // If you need to include extra fields on a select option
          // format a JSON string in 'data-extra' like this:
          //
          // <option data-extra='{ "myField": "thing" }' > Label </option>
          //
          // -matt
          render: {
            item: function(data) {
              var attrs = ' data-value="'+data.value+'"';
              for (key in _.omit(data, ['value', 'label', '$order'])) {
                attrs = attrs + ' data-' + key + '="' + data[key] + '"';
              }
              return '
' + data.label + '
';
            },
            option: function(data) {
              var attrs = 'data-value="'+data.value+'"';
              for (key in _.omit(data, ['value', 'label', '$order'])) {
                attrs = attrs + ' data-' + key + '="' + data[key] + '"';
              }
              return '
' + data.label + '
';
            }
          }
        });

        // Selectize - Multi Select
        $el.find('select[data-selectize][select="multiple"]:not(.apos-template select[data-selectize])').selectize({
          maxItems: null,
          delimiter: ', ',
        });
      });
    };

    self.enablePreview = function() {
      var $body = $('body');
      $body.on('click', '.apos-preview-toggle', function(event){
        console.log('REFACTOR THIS TO USE DATA ATTR');
        $('.apos-preview-toggle').toggleClass('previewing');
        $body.toggleClass('previewing');
      });
    }

    self.enableTabbedModals = function() {
      //sets up listeners for tabbed modals
      var $body = $('body');
      $body.on('click', '.apos-modal-tab-title', function(){
        console.log('REFACTOR THIS TO USE DATA ATTR');
        $(this).closest('.apos-modal-tabs').find('.apos-active').removeClass('apos-active');
        $(this).closest('.apos-modal-tabs').find('[data-tab-id="'+$(this).attr('data-tab')+'"]').addClass('apos-active');
        $(this).addClass('apos-active');
      });
    }

    // Utility that lets you add the data attribute 'data-apos-actionable' to any element and have
    // it's 'apos-active' class be toggled on click.
    // optionally you can give the attr a value which can be used to target another element's active
    // class be toggled.
    self.enableActionable = function() {
      var $body = $('body');
      $body.on('click', '[data-apos-actionable]', function(event) {
        var $el = $(this);
        if ($el.attr('data-apos-actionable')) {
          // $el.closest('[' + $el.attr('data-apos-actionable') + ']').toggleClass('apos-active');
          $el.closest('[' + $el.attr('data-apos-actionable') + ']').toggleClass('apos-active');
        } else {
          $el.toggleClass('apos-active');
        }
      });
    }

    self.enhanceAdminMenu = function() {
      var $bar = $('[data-apos-admin-bar]');
      var $dropdowns = $('[data-apos-dropdown]');
      $bar.css('overflow', 'visible');

      // on transitionend, turn overflow on so we can see dropdowns!
      $bar.find('[data-apos-admin-bar-items]').on('transitionend webkitTransitionEnd oTransitionEnd', function() {
        if ($bar.hasClass('apos-active')) {
          $bar.css('overflow', 'visible');
        }
      });

      // when we collapse the menu, turn all dropdowns off. Don't show overflow while transitioning
      $bar.find('[data-apos-admin-bar-logo]').on('click', function() {
        $bar.css('overflow', '');
        $bar.find('[data-apos-dropdown]').removeClass('apos-active');
      });

      // when the bar is clicked, make a note of it so we don't auto
      // collapse the menu from load
      $bar.on('click', function(){ $bar.addClass('apos-admin-bar--clicked') });

      setTimeout(function() {
        if (!$bar.hasClass('apos-admin-bar--clicked')) {
          $bar.removeClass('apos-active');
        }
      }, 3000);

      // when opening dropdowns, close other dropdowns
      $dropdowns.on('click', function(){
        $bar.find('[data-apos-dropdown]').not(this).removeClass('apos-active');
      });

    }

    self.enhanceDropdowns = function() {
      $('body').on('click change', '[data-apos-dropdown]', function(event) {
        event.preventDefault();
        var $this = $(this);
        if ($this.find('[data-apos-dropdown-button-label]').length) {
          // only adjust width for button'y dropdowns
          // not global menu dropdowns
          var $label = $(this).find('[data-apos-dropdown-button-label]');
          if ($label.attr('data-apos-dropdown-button-label') === 'active') {
            $label.css('padding-right', $label.attr('data-apos-original-right-padding'));
            $label.attr('data-apos-dropdown-button-label', '');
          } else {
            $label.attr('data-apos-original-right-padding', $label.css('padding-right'));
            var val = parseInt($label.attr('data-apos-original-right-padding').substring(0, $label.attr('data-apos-original-right-padding').length - 2));
            var extra = ($(this).find('[data-apos-dropdown-items]')[0].getBoundingClientRect().width - $label[0].getBoundingClientRect().width + val );
            $label.css('padding-right', extra);
            $label.attr('data-apos-dropdown-button-label', 'active');
          }
        }
      })
    }

    self.enableSkipdown = function() {
      // If the URL ends in #skipdown, locate the element with the
      // attribute data-skip-down-to and scroll to it, then remove
      // #skipdown from the URL. Useful for "poor man's AJAX" situations
      // where a page refresh is needed but you don't want the user to
      // be forced to scroll past the logo, etc. again.

      if (document.location.hash === '#skipdown') {
        document.location.hash = '';
        // slice off the remaining '#' in modern browsers
        if (window.history && (typeof window.history.replaceState === 'function')) {
          history.replaceState({}, '', window.location.href.slice(0, -1));
        }
        // Must yield first or this has no effect in Chrome.
        setImmediate(function() {
          $('html, body').scrollTop($('[data-skip-down-to]').offset().top - 50);
        });
      }
    };

    // If the URL ends in: #click-whatever
    //
    // ... Then we locate an element with the attribute data-whatever,
    // and trigger a click event on it.
    //
    // This is useful for resuming an activity after requiring the
    // user to log in.

    self.enableClickUrl = function() {

      // Waiting long enough for both the click and the autoscroll to work is
      // tricky. We need to yield beyond DOMready so that other code installing click
      // handlers on DOMready has had time to do so. And we need to yield a little
      // extra time or the browser will crush our efforts to set scrollTop based on
      // its own idea of where we are on the page (at least in Chrome). ):

      setTimeout(function() {
        var hash = window.location.hash;
        var matches = hash.match(/^\#click\-(.*)$/);
        if (matches) {
          var $element = $('[data-' + matches[1] + ']');
          if ($element.length) {
            // Scroll back to the right neighborhood
            var offset = $element.offset();
            var scrollTop = offset.top - 100;
            $('html, body').scrollTop(scrollTop);
            // Now carry out the action
            $('[data-' + matches[1] + ']').trigger('click');
          }
        }
      }, 200);
    };

    self.enablePrefix = function(prefix) {
      // If Apostrophe has a global URL prefix, patch
      // jQuery's AJAX capabilities to prepend that prefix
      // to any non-absolute URL

      if (prefix) {
        $.ajaxPrefilter(function(options, originalOptions, jqXHR) {
          if (options.url) {
            if (!options.url.match(/^[a-zA-Z]+:/))
            {
              options.url = prefix + options.url;
            }
          }
          return;
        });
      }
    };

    self.enableStyledFileButtons = function() {
      // Click the original file upload button if the styled
      // proxy for it is clicked. Allows indirect styling of
      // file buttons
      $('body').on('click', '[data-file-styled]', function() {
        $(this).parent().children('input').click();
      });
    };

    self.globalBusyCounter = 0;

    // If state is true, the interface changes to
    // indicate Apostrophe is busy loading a modal
    // dialog or other experience that preempts other
    // activities. If state is false, the interface
    // is unlocked. Calls may be nested and the
    // interface will not unlock until all
    // locks are released.
    //
    // See also apos.busy for interactions that
    // only need to indicate that one particular
    // element is busy.

    self.globalBusy = function(state) {
      if (state) {
        self.globalBusyCounter++;
        if (self.globalBusyCounter === 1) {
          // Newly busy
          self.globalLock();
        }
      } else {
        self.globalBusyCounter--;
        if (!self.globalBusyCounter) {
          // newly free
          self.globalUnlock();
        }
      }
    };

    // Do not call this method yourself. It is called
    // by self.globalBusy to display the global spinner.
    // Freel free to override this method to change the UI.

    self.globalLock = function() {
      var $freezer = $('

');
      $('body').append($freezer);
    };

    // Do not call this method yourself. It is called
    // by self.globalBusy to hide the global spinner.
    // Freel free to override this method to change the UI.

    self.globalUnlock = function() {
      $('.apos-global-busy').remove();
    }

    // Simple progress display. Locates a progress display
    // inside the given element. If state is true, the
    // element with a [data-progress] attribute is shown,
    // otherwise the element with a [data-finished] attribute
    // is shown. Neither element is required. Supports
    // nested calls; does not revert to indicating complete
    // until the nesting level is 0.

    self.busy = function($el, state) {
      var busy = $el.data('busy') || 0;
      if (state) {
        busy++;
        $el.data('busy', busy);
        if (busy === 1) {
          $el.find('[data-progress]').show();
          $el.find('[data-finished]').hide();
        }
      } else {
        busy--;
        $el.data('busy', busy);
        if (!busy) {
          $el.find('[data-progress]').hide();
          $el.find('[data-finished]').show();
        }
      }
    };

    // Redirect correctly to the given location on the
    // Apostrophe site, even if the prefix option is in use
    // (you should provide a non-prefixed path)

    self.redirect = function(slug) {
      var href = apos.prefix + slug;
      if (href === window.location.href) {
        window.location.reload();
      } else {
        window.location.href = href;
      }
    };

    // Enhance a plaintext date field with a pikaday date widget.
    self.enhanceDate = function($el, options) {
      if (!options) {
        options = {};
      }

      new Pikaday({
        field: $el[0],
        format: 'YYYY-MM-DD'
      });
    };

    // Converts apostrophe 24hr times to local time
    self.formatTime = function(time, options) {
      if(!options) {
        options = {};
      }

      return moment(time, 'HH:mm:ss').format('h:mma');
    };

    // Status of the shift key. Automatically updated.c
    self.shiftActive = false;

    // Add a callback when a link is clicked to trigger
    // a certain action.
    //
    // Example:
    //
    // self.link($el, 'edit', 'blogPost', fn)
    //
    // When a click occurs on anything inside $el with a
    // data-edit-blog-post attribute, invoke
    // "fn" with the jQuery element clicked and the value
    // of the attribute.
    //
    // self.link('apos-manage', 'blogPost', fn)
    //
    // When a click occurs anywhere on the page
    // on something with a data-manage-blog-post
    // attribute, invoke "fn" with the jquery element
    // clicked and the value of that attribute.
    //
    // Event propagation and the default behavior of
    // the click event are both automatically stopped.
    //
    // The word "object" refers to "the object of the sentence."
    // It is a STRING, not a javascript object.

    self.link = function(sel, verb, object, callback) {
      if (arguments.length === 3) {
        callback = object;
        object = verb;
        verb = sel;
        sel = 'body';
      }
      var attribute;
      if (object === null) {
        attribute = 'data-' + apos.utils.cssName(verb);
      } else {
        attribute = 'data-' + apos.utils.cssName(verb) + '-' + apos.utils.cssName(object);
      }
      var attributeSel = '[' + attribute + ']';
      $(sel).on('click', attributeSel, function() {
        callback($(this), $(this).attr(attribute));
        return false;
      });
    };

    apos.ui = self;
  }
});
<|endoftext|>


<commit_before>/*!
 * Start Bootstrap - Freelancer v3.3.7+1 (http://startbootstrap.com/template-overviews/freelancer)
 * Copyright 2013-2016 Start Bootstrap
 * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE)
 */
!function(o){"use strict";o(".page-scroll a").bind("click",function(t){var l=o(this);o("html, body").stop().animate({scrollTop:o(l.attr("href")).offset().top-50},1250,"easeInOutExpo"),t.preventDefault()}),o("body").scrollspy({target:".navbar-fixed-top",offset:51}),o(".navbar-collapse ul li a").click(function(){o(".navbar-toggle:visible").click()}),o("#mainNav").affix({offset:{top:100}}),o(function(){o("body").on("input propertychange",".floating-label-form-group",function(t){o(this).toggleClass("floating-label-form-group-with-value",!!o(t.target).val())}).on("focus",".floating-label-form-group",function(){o(this).addClass("floating-label-form-group-with-focus")}).on("blur",".floating-label-form-group",function(){o(this).removeClass("floating-label-form-group-with-focus")})})}(jQuery);<commit_msg>Delete freelancer.min.js<commit_after><|endoftext|>


<commit_before>import Analyzer from 'Parser/Core/Analyzer';

import Ability from './Ability';
import AbilityTracker from './AbilityTracker';
import Haste from './Haste';

class Abilities extends Analyzer {
  static dependencies = {
    abilityTracker: AbilityTracker,
    haste: Haste,
  };
  static SPELL_CATEGORIES = {
    ROTATIONAL: 'Rotational Spell',
    ROTATIONAL_AOE: 'Spell (AOE)',
    ITEMS: 'Item',
    COOLDOWNS: 'Cooldown',
    DEFENSIVE: 'Defensive Cooldown',
    SEMI_DEFENSIVE: 'Offensive & Defensive Cooldown',
    OTHERS: 'Spell',
    UTILITY: 'Utility',
    HEALER_DAMAGING_SPELL: 'Damaging Spell',
    HIDDEN: 'Hidden',
  };
  static ABILITY_CLASS = Ability;

  /**
   * This will be called *once* during initialization. See the Ability class for the available properties. This should contain ALL spells available to a player of your spec, including utility such as interrupts, dispells, etc
   * @returns {object[]}
   */
  spellbook() {
    // This list will NOT be recomputed during the fight. If a cooldown changes based on something like Haste or a Buff you need to put it in a function.
    // While you can put checks for talents/traits outside of the cooldown prop, you generally should aim to keep everything about a single spell together. In general only move a prop up if you're regularly checking for the same talent/trait in multiple spells.
    return [];
  }

  abilities = [];
  activeAbilities = [];
  constructor(...args) {
    super(...args);
    this.loadSpellbook(this.spellbook());
  }
  loadSpellbook(spellbook) {
    this.abilities = spellbook.map(options => new this.constructor.ABILITY_CLASS(this, options));
    this.activeAbilities = this.abilities.filter(ability => ability.enabled);
  }

  /**
   * Add an ability to the list of active abilities.
   * @param {object} options An object with all the properties and their values that gets passed to the Ability class.
   */
  add(options) {
    const ability = new this.constructor.ABILITY_CLASS(this, options);
    this.abilities.push(ability);
    this.activeAbilities.push(ability);
  }

  /**
   * Returns the first ACTIVE spellInfo with the given spellId (or undefined if there is no such spellInfo)
   */
  getAbility(spellId) {
    return this.activeAbilities.find(ability => {
      if (ability.spell instanceof Array) {
        return ability.spell.some(spell => spell.id === spellId);
      } else {
        return ability.spell.id === spellId;
      }
    });
  }

  /**
   * Returns the expected cooldown (in seconds) of the given spellId at the current timestamp (or undefined if there is no such spellInfo)
   */
  getExpectedCooldownDuration(spellId) {
    const ability = this.getAbility(spellId);
    return ability ? Math.round(ability.cooldown * 1000) : undefined;
  }

  /**
   * Returns the max charges of the given spellId, or 1 if the spell doesn't have charges (or undefined if there is no such spellInfo)
   */
  getMaxCharges(spellId) {
    const ability = this.getAbility(spellId);
    return ability ? (ability.charges || 1) : undefined;
  }

  /**
   * Returns the timeline sort index, or null if none is set. (or undefined if there is no such spellInfo)
   */
  getTimelineSortIndex(spellId) {
    const ability = this.getAbility(spellId);
    return ability ? ability.timelineSortIndex : undefined;
  }

  /**
   * Returns the buff spell Id to a given spell, or null if none is set. (or undefined if there is no such spellInfo)
   */
  getBuffSpellId(spellId) {
    const ability = this.getAbility(spellId);
    return ability ? (ability.buffSpellId || null) : undefined;
  }

  /**
  * Return the first ability that has the given SpellId set as the buff.
  */
  getSpellBuffAbility(spellId) {
    return this.activeAbilities.find(ability => {
        return ability.buffSpellId === spellId;
    });
  }
}

export default Abilities;
<commit_msg>Report spells missing in the spellbook<commit_after>import Analyzer from 'Parser/Core/Analyzer';

import Ability from './Ability';
import AbilityTracker from './AbilityTracker';
import Haste from './Haste';

class Abilities extends Analyzer {
  static dependencies = {
    abilityTracker: AbilityTracker,
    haste: Haste,
  };
  static SPELL_CATEGORIES = {
    ROTATIONAL: 'Rotational Spell',
    ROTATIONAL_AOE: 'Spell (AOE)',
    ITEMS: 'Item',
    COOLDOWNS: 'Cooldown',
    DEFENSIVE: 'Defensive Cooldown',
    SEMI_DEFENSIVE: 'Offensive & Defensive Cooldown',
    OTHERS: 'Spell',
    UTILITY: 'Utility',
    HEALER_DAMAGING_SPELL: 'Damaging Spell',
    HIDDEN: 'Hidden',
  };
  static ABILITY_CLASS = Ability;

  /**
   * This will be called *once* during initialization. See the Ability class for the available properties. This should contain ALL spells available to a player of your spec, including utility such as interrupts, dispells, etc
   * @returns {object[]}
   */
  spellbook() {
    // This list will NOT be recomputed during the fight. If a cooldown changes based on something like Haste or a Buff you need to put it in a function.
    // While you can put checks for talents/traits outside of the cooldown prop, you generally should aim to keep everything about a single spell together. In general only move a prop up if you're regularly checking for the same talent/trait in multiple spells.
    return [];
  }

  abilities = [];
  activeAbilities = [];
  constructor(...args) {
    super(...args);
    this.loadSpellbook(this.spellbook());
  }
  loadSpellbook(spellbook) {
    this.abilities = spellbook.map(options => new this.constructor.ABILITY_CLASS(this, options));
    this.activeAbilities = this.abilities.filter(ability => ability.enabled);
  }

  /**
   * Add an ability to the list of active abilities.
   * @param {object} options An object with all the properties and their values that gets passed to the Ability class.
   */
  add(options) {
    const ability = new this.constructor.ABILITY_CLASS(this, options);
    this.abilities.push(ability);
    this.activeAbilities.push(ability);
  }

  /**
   * Returns the first ACTIVE spellInfo with the given spellId (or undefined if there is no such spellInfo)
   */
  getAbility(spellId) {
    return this.activeAbilities.find(ability => {
      if (ability.spell instanceof Array) {
        return ability.spell.some(spell => spell.id === spellId);
      } else {
        return ability.spell.id === spellId;
      }
    });
  }

  /**
   * Returns the expected cooldown (in seconds) of the given spellId at the current timestamp (or undefined if there is no such spellInfo)
   */
  getExpectedCooldownDuration(spellId) {
    const ability = this.getAbility(spellId);
    return ability ? Math.round(ability.cooldown * 1000) : undefined;
  }

  /**
   * Returns the max charges of the given spellId, or 1 if the spell doesn't have charges (or undefined if there is no such spellInfo)
   */
  getMaxCharges(spellId) {
    const ability = this.getAbility(spellId);
    return ability ? (ability.charges || 1) : undefined;
  }

  /**
   * Returns the timeline sort index, or null if none is set. (or undefined if there is no such spellInfo)
   */
  getTimelineSortIndex(spellId) {
    const ability = this.getAbility(spellId);
    return ability ? ability.timelineSortIndex : undefined;
  }

  /**
   * Returns the buff spell Id to a given spell, or null if none is set. (or undefined if there is no such spellInfo)
   */
  getBuffSpellId(spellId) {
    const ability = this.getAbility(spellId);
    return ability ? (ability.buffSpellId || null) : undefined;
  }

  /**
  * Return the first ability that has the given SpellId set as the buff.
  */
  getSpellBuffAbility(spellId) {
    return this.activeAbilities.find(ability => {
        return ability.buffSpellId === spellId;
    });
  }

  // Validate that all spells castable by the player is in the spellbook
  on_byPlayer_cast(event) {
    if (!event.ability) {
      return;
    }
    const spellId = event.ability.guid;
    if (spellId === 1) {
      // Melee (auto attack)
      return;
    }
    const ability = this.getAbility(event.ability.guid);
    if (!ability) {
      console.warn('Ability missing from spellbook:', event.ability);
    }
  }
}

export default Abilities;
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { routeActions } from 'redux-simple-router'
import classNames from 'classnames'
import * as ACTION_TYPES from '../../constants/action_types'
import { SHORTCUT_KEYS } from '../../constants/gui_types'
import { openModal, closeModal } from '../../actions/modals'
import { addScrollObject, removeScrollObject } from '../interface/ScrollComponent'
import { addResizeObject, removeResizeObject } from '../interface/ResizeComponent'
import HelpDialog from '../dialogs/HelpDialog'
import NavbarLabel from '../navbar/NavbarLabel'
import NavbarLink from '../navbar/NavbarLink'
import NavbarMark from '../navbar/NavbarMark'
import NavbarMorePostsButton from '../navbar/NavbarMorePostsButton'
import NavbarOmniButton from '../navbar/NavbarOmniButton'
import NavbarProfile from '../navbar/NavbarProfile'
import { BoltIcon, CircleIcon, SearchIcon, SparklesIcon, StarIcon } from '../navbar/NavbarIcons'
import Mousetrap from '../../vendor/mousetrap'

class Navbar extends Component {

  constructor(props, context) {
    super(props, context)
    this.scrollYAtDirectionChange = null
    this.currentPath = null
    this.state = {
      asFixed: false,
      asHidden: false,
      asLocked: false,
      skipTransition: false,
    }
    this.logInWasClicked = ::this.logInWasClicked
    this.onLogOut = ::this.onLogOut
    this.omniButtonWasClicked = ::this.omniButtonWasClicked
    this.loadMorePostsWasClicked = ::this.loadMorePostsWasClicked
  }

  componentDidMount() {
    const { dispatch, isLoggedIn } = this.props

    if (isLoggedIn) {
      Mousetrap.bind(Object.keys(this.props.shortcuts), (event, shortcut) => {
        dispatch(routeActions.push(this.props.shortcuts[shortcut]))
      })

      Mousetrap.bind(SHORTCUT_KEYS.HELP, () => {
        const { modal } = this.props
        if (modal.payload) {
          return dispatch(closeModal())
        }
        return dispatch(openModal(<HelpDialog/>))
      })
    }

    Mousetrap.bind(SHORTCUT_KEYS.TOGGLE_LAYOUT, () => {
      const { json, pathname } = this.props
      let result = null
      if (json.pages) {
        result = json.pages[pathname]
      }
      if (result && result.mode) {
        const newMode = result.mode === 'grid' ? 'list' : 'grid'
        dispatch({
          type: ACTION_TYPES.SET_LAYOUT_MODE,
          payload: { mode: newMode },
        })
      }
    })
    addResizeObject(this)
    addScrollObject(this)
  }

  componentWillReceiveProps(nextProps) {
    const { pathname } = nextProps
    const pathnames = pathname.split('/').slice(1)
    const whitelist = [
      '',
      'discover',
      'explore',
      'find',
      'following',
      'invitations',
      'notifications',
      'onboarding',
      'search',
      'staff',
      'starred',
    ]
    const isWhitelisted = (whitelist.indexOf(pathnames[0]) >= 0 || pathnames[1] === 'post')
    const isPageChangeUpdate = pathnames[0] !== this.currentPath
    if (isPageChangeUpdate) {
      this.currentPath = pathnames[0]
      this.setState({
        asFixed: false,
        asHidden: false,
        asLocked: !isWhitelisted,
        isPageChangeUpdate: true,
        skipTransition: false,
      })
    } else {
      this.setState({ asLocked: !isWhitelisted, isPageChangeUpdate: false })
    }
  }

  // componentDidUpdate() {
  //   if (typeof window === 'undefined') {
  //     return
  //   }
  //   const { asLocked, isPageChangeUpdate, offset } = this.state
  //   if (isPageChangeUpdate && asLocked) {
  //     window.scrollTo(0, offset - 120)
  //   }
  // }

  componentWillUnmount() {
    const { isLoggedIn, shortcuts } = this.props
    if (isLoggedIn) {
      Mousetrap.unbind(Object.keys(shortcuts))
      Mousetrap.unbind(SHORTCUT_KEYS.HELP)
    }
    Mousetrap.unbind(SHORTCUT_KEYS.TOGGLE_LAYOUT)
    removeResizeObject(this)
    removeScrollObject(this)
  }

  onResize(resizeProperties) {
    const { coverOffset } = resizeProperties
    this.setState({ offset: coverOffset - 80 })
  }

  onScrollTop() {
    if (this.state.asFixed) {
      this.setState({ asFixed: false, asHidden: false, skipTransition: false })
    }
  }

  onScrollDirectionChange(scrollProperties) {
    const { scrollY } = scrollProperties
    const { offset } = this.state

    if (scrollY >= offset) {
      this.scrollYAtDirectionChange = scrollY
    }
  }

  onScroll(scrollProperties) {
    const { scrollY, scrollDirection } = scrollProperties
    const { offset } = this.state

    // Going from absolute to fixed positioning
    if (scrollY >= offset && !this.state.asFixed) {
      this.setState({ asFixed: true, asHidden: true, skipTransition: true })
    }

    // Scroll just changed directions so it's about to either be shown or hidden
    if (scrollY >= offset && this.scrollYAtDirectionChange) {
      const distance = Math.abs(scrollY - this.scrollYAtDirectionChange)
      const delay = scrollDirection === 'down' ? 20 : 80

      if (distance >= delay) {
        this.setState({ asHidden: scrollDirection === 'down', skipTransition: false })
        this.scrollYAtDirectionChange = null
      }
    }
  }

  // TODO: probably need to handle this a bit better
  onLogOut() {
    const { dispatch } = this.props
    dispatch({ type: ACTION_TYPES.AUTHENTICATION.LOGOUT })
    dispatch(routeActions.push('/'))
  }

  omniButtonWasClicked() {
  }

  loadMorePostsWasClicked() {
    const { dispatch } = this.props
    dispatch({
      type: ACTION_TYPES.ADD_NEW_IDS_TO_RESULT,
    })
  }

  logInWasClicked(e) {
    e.preventDefault()
    document.location.href = ENV.REDIRECT_URI + e.target.pathname
  }

  renderLoggedInNavbar(klassNames, hasLoadMoreButton, pathname) {
    const { profile } = this.props
    return (

        <NavbarMark />
        <NavbarOmniButton callback={ this.omniButtonWasClicked } />
        {
          hasLoadMoreButton ?
          <NavbarMorePostsButton callback={ this.loadMorePostsWasClicked } /> :
          null
        }

          <NavbarLink
            to="/discover"
            label="Discover"
            modifiers="LabelOnly"
            pathname={pathname}
            icon={ <SparklesIcon/> }
          />
          <NavbarLink
            to="/following"
            label="Following"
            modifiers="LabelOnly"
            pathname={pathname}
            icon={ <CircleIcon/> }
          />
          <NavbarLink
            to="/starred"
            label="Starred"
            modifiers=""
            pathname={pathname}
            icon={ <StarIcon/> }
          />
          <NavbarLink
            to="/notifications"
            label="Notifications"
            modifiers="IconOnly"
            pathname={pathname}
            icon={ <BoltIcon/> }
          />
          <NavbarLink
            to="/search"
            label="Search"
            modifiers="IconOnly"
            pathname={pathname}
            icon={ <SearchIcon/> }
          />

        <NavbarProfile
          avatar={ profile.avatar }
          onLogOut={ this.onLogOut }
          username={ profile.username }
        />

    )
  }

  renderLoggedOutNavbar(klassNames, hasLoadMoreButton, pathname) {
    return (

        <NavbarMark />
        <NavbarLabel />
        {
          hasLoadMoreButton ?
            <NavbarMorePostsButton callback={ this.loadMorePostsWasClicked } /> :
            null
        }

          <NavbarLink
            to="/explore"
            label="Discover"
            modifiers="LabelOnly"
            pathname={pathname}
            icon={ <SparklesIcon/> }
          />
          <NavbarLink
            to="/find"
            label="Search"
            modifiers="IconOnly"
            pathname={pathname}
            icon={ <SearchIcon/> }
          />
          <NavbarLink
            to="/enter"
            label="Log in"
            modifiers="LabelOnly"
            pathname={pathname}
            onClick={ this.logInWasClicked }
          />
          <NavbarLink
            to="/signup"
            label="Sign up"
            modifiers="LabelOnly"
            pathname={pathname}
          />

    )
  }

  render() {
    const { isLoggedIn, json, pathname } = this.props
    const klassNames = classNames(
      'Navbar',
      { asLocked: this.state.asLocked },
      { asFixed: this.state.asFixed },
      { asHidden: this.state.asHidden },
      { skipTransition: this.state.skipTransition },
    )
    const result = json.pages ? json.pages[pathname] : null
    const hasLoadMoreButton = result && result.newIds

    return isLoggedIn ?
      this.renderLoggedInNavbar(klassNames, hasLoadMoreButton, pathname) :
      this.renderLoggedOutNavbar(klassNames, hasLoadMoreButton, pathname)
  }
}

Navbar.propTypes = {
  dispatch: PropTypes.func.isRequired,
  isLoggedIn: PropTypes.bool.isRequired,
  json: PropTypes.object.isRequired,
  modal: PropTypes.any,
  pathname: PropTypes.string.isRequired,
  profile: PropTypes.object,
  shortcuts: PropTypes.object.isRequired,
}

Navbar.defaultProps = {
  shortcuts: {
    [SHORTCUT_KEYS.SEARCH]: '/search',
    [SHORTCUT_KEYS.DISCOVER]: '/discover',
    [SHORTCUT_KEYS.FOLLOWING]: '/following',
    [SHORTCUT_KEYS.ONBOARDING]: '/onboarding/communities',
  },
}

function mapStateToProps(state) {
  return {
    isLoggedIn: state.authentication.isLoggedIn,
    json: state.json,
    modal: state.modal,
    pathname: state.routing.location.pathname,
    profile: state.profile,
  }
}

export default connect(mapStateToProps)(Navbar)

<commit_msg>Turn scrollTo back on when there is a cover offset<commit_after>import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { routeActions } from 'redux-simple-router'
import classNames from 'classnames'
import * as ACTION_TYPES from '../../constants/action_types'
import { SHORTCUT_KEYS } from '../../constants/gui_types'
import { openModal, closeModal } from '../../actions/modals'
import { addScrollObject, removeScrollObject } from '../interface/ScrollComponent'
import { addResizeObject, removeResizeObject } from '../interface/ResizeComponent'
import HelpDialog from '../dialogs/HelpDialog'
import NavbarLabel from '../navbar/NavbarLabel'
import NavbarLink from '../navbar/NavbarLink'
import NavbarMark from '../navbar/NavbarMark'
import NavbarMorePostsButton from '../navbar/NavbarMorePostsButton'
import NavbarOmniButton from '../navbar/NavbarOmniButton'
import NavbarProfile from '../navbar/NavbarProfile'
import { BoltIcon, CircleIcon, SearchIcon, SparklesIcon, StarIcon } from '../navbar/NavbarIcons'
import Mousetrap from '../../vendor/mousetrap'

class Navbar extends Component {

  constructor(props, context) {
    super(props, context)
    this.scrollYAtDirectionChange = null
    this.currentPath = null
    this.state = {
      asFixed: false,
      asHidden: false,
      asLocked: false,
      skipTransition: false,
    }
    this.logInWasClicked = ::this.logInWasClicked
    this.onLogOut = ::this.onLogOut
    this.omniButtonWasClicked = ::this.omniButtonWasClicked
    this.loadMorePostsWasClicked = ::this.loadMorePostsWasClicked
  }

  componentDidMount() {
    const { dispatch, isLoggedIn } = this.props

    if (isLoggedIn) {
      Mousetrap.bind(Object.keys(this.props.shortcuts), (event, shortcut) => {
        dispatch(routeActions.push(this.props.shortcuts[shortcut]))
      })

      Mousetrap.bind(SHORTCUT_KEYS.HELP, () => {
        const { modal } = this.props
        if (modal.payload) {
          return dispatch(closeModal())
        }
        return dispatch(openModal(<HelpDialog/>))
      })
    }

    Mousetrap.bind(SHORTCUT_KEYS.TOGGLE_LAYOUT, () => {
      const { json, pathname } = this.props
      let result = null
      if (json.pages) {
        result = json.pages[pathname]
      }
      if (result && result.mode) {
        const newMode = result.mode === 'grid' ? 'list' : 'grid'
        dispatch({
          type: ACTION_TYPES.SET_LAYOUT_MODE,
          payload: { mode: newMode },
        })
      }
    })
    addResizeObject(this)
    addScrollObject(this)
  }

  componentWillReceiveProps(nextProps) {
    const { pathname } = nextProps
    const pathnames = pathname.split('/').slice(1)
    const whitelist = [
      '',
      'discover',
      'explore',
      'find',
      'following',
      'invitations',
      'notifications',
      'onboarding',
      'search',
      'staff',
      'starred',
    ]
    const isWhitelisted = (whitelist.indexOf(pathnames[0]) >= 0 || pathnames[1] === 'post')
    const isPageChangeUpdate = pathnames[0] !== this.currentPath
    if (isPageChangeUpdate) {
      this.currentPath = pathnames[0]
      this.setState({
        asFixed: false,
        asHidden: false,
        asLocked: !isWhitelisted,
        isPageChangeUpdate: true,
        skipTransition: false,
      })
    } else {
      this.setState({ asLocked: !isWhitelisted, isPageChangeUpdate: false })
    }
  }

  // TODO: This may need some tweeks once we get a little more intelligent
  // around the scroll to calls utilizing history
  componentDidUpdate() {
    if (typeof window === 'undefined') {
      return
    }
    const { asLocked, isPageChangeUpdate, offset } = this.state
    if (isPageChangeUpdate && asLocked) {
      window.scrollTo(0, offset - 120)
    }
  }

  componentWillUnmount() {
    const { isLoggedIn, shortcuts } = this.props
    if (isLoggedIn) {
      Mousetrap.unbind(Object.keys(shortcuts))
      Mousetrap.unbind(SHORTCUT_KEYS.HELP)
    }
    Mousetrap.unbind(SHORTCUT_KEYS.TOGGLE_LAYOUT)
    removeResizeObject(this)
    removeScrollObject(this)
  }

  onResize(resizeProperties) {
    const { coverOffset } = resizeProperties
    this.setState({ offset: coverOffset - 80 })
  }

  onScrollTop() {
    if (this.state.asFixed) {
      this.setState({ asFixed: false, asHidden: false, skipTransition: false })
    }
  }

  onScrollDirectionChange(scrollProperties) {
    const { scrollY } = scrollProperties
    const { offset } = this.state

    if (scrollY >= offset) {
      this.scrollYAtDirectionChange = scrollY
    }
  }

  onScroll(scrollProperties) {
    const { scrollY, scrollDirection } = scrollProperties
    const { offset } = this.state

    // Going from absolute to fixed positioning
    if (scrollY >= offset && !this.state.asFixed) {
      this.setState({ asFixed: true, asHidden: true, skipTransition: true })
    }

    // Scroll just changed directions so it's about to either be shown or hidden
    if (scrollY >= offset && this.scrollYAtDirectionChange) {
      const distance = Math.abs(scrollY - this.scrollYAtDirectionChange)
      const delay = scrollDirection === 'down' ? 20 : 80

      if (distance >= delay) {
        this.setState({ asHidden: scrollDirection === 'down', skipTransition: false })
        this.scrollYAtDirectionChange = null
      }
    }
  }

  // TODO: probably need to handle this a bit better
  onLogOut() {
    const { dispatch } = this.props
    dispatch({ type: ACTION_TYPES.AUTHENTICATION.LOGOUT })
    dispatch(routeActions.push('/'))
  }

  omniButtonWasClicked() {
  }

  loadMorePostsWasClicked() {
    const { dispatch } = this.props
    dispatch({
      type: ACTION_TYPES.ADD_NEW_IDS_TO_RESULT,
    })
  }

  logInWasClicked(e) {
    e.preventDefault()
    document.location.href = ENV.REDIRECT_URI + e.target.pathname
  }

  renderLoggedInNavbar(klassNames, hasLoadMoreButton, pathname) {
    const { profile } = this.props
    return (

        <NavbarMark />
        <NavbarOmniButton callback={ this.omniButtonWasClicked } />
        {
          hasLoadMoreButton ?
          <NavbarMorePostsButton callback={ this.loadMorePostsWasClicked } /> :
          null
        }

          <NavbarLink
            to="/discover"
            label="Discover"
            modifiers="LabelOnly"
            pathname={pathname}
            icon={ <SparklesIcon/> }
          />
          <NavbarLink
            to="/following"
            label="Following"
            modifiers="LabelOnly"
            pathname={pathname}
            icon={ <CircleIcon/> }
          />
          <NavbarLink
            to="/starred"
            label="Starred"
            modifiers=""
            pathname={pathname}
            icon={ <StarIcon/> }
          />
          <NavbarLink
            to="/notifications"
            label="Notifications"
            modifiers="IconOnly"
            pathname={pathname}
            icon={ <BoltIcon/> }
          />
          <NavbarLink
            to="/search"
            label="Search"
            modifiers="IconOnly"
            pathname={pathname}
            icon={ <SearchIcon/> }
          />

        <NavbarProfile
          avatar={ profile.avatar }
          onLogOut={ this.onLogOut }
          username={ profile.username }
        />

    )
  }

  renderLoggedOutNavbar(klassNames, hasLoadMoreButton, pathname) {
    return (

        <NavbarMark />
        <NavbarLabel />
        {
          hasLoadMoreButton ?
            <NavbarMorePostsButton callback={ this.loadMorePostsWasClicked } /> :
            null
        }

          <NavbarLink
            to="/explore"
            label="Discover"
            modifiers="LabelOnly"
            pathname={pathname}
            icon={ <SparklesIcon/> }
          />
          <NavbarLink
            to="/find"
            label="Search"
            modifiers="IconOnly"
            pathname={pathname}
            icon={ <SearchIcon/> }
          />
          <NavbarLink
            to="/enter"
            label="Log in"
            modifiers="LabelOnly"
            pathname={pathname}
            onClick={ this.logInWasClicked }
          />
          <NavbarLink
            to="/signup"
            label="Sign up"
            modifiers="LabelOnly"
            pathname={pathname}
          />

    )
  }

  render() {
    const { isLoggedIn, json, pathname } = this.props
    const klassNames = classNames(
      'Navbar',
      { asLocked: this.state.asLocked },
      { asFixed: this.state.asFixed },
      { asHidden: this.state.asHidden },
      { skipTransition: this.state.skipTransition },
    )
    const result = json.pages ? json.pages[pathname] : null
    const hasLoadMoreButton = result && result.newIds

    return isLoggedIn ?
      this.renderLoggedInNavbar(klassNames, hasLoadMoreButton, pathname) :
      this.renderLoggedOutNavbar(klassNames, hasLoadMoreButton, pathname)
  }
}

Navbar.propTypes = {
  dispatch: PropTypes.func.isRequired,
  isLoggedIn: PropTypes.bool.isRequired,
  json: PropTypes.object.isRequired,
  modal: PropTypes.any,
  pathname: PropTypes.string.isRequired,
  profile: PropTypes.object,
  shortcuts: PropTypes.object.isRequired,
}

Navbar.defaultProps = {
  shortcuts: {
    [SHORTCUT_KEYS.SEARCH]: '/search',
    [SHORTCUT_KEYS.DISCOVER]: '/discover',
    [SHORTCUT_KEYS.FOLLOWING]: '/following',
    [SHORTCUT_KEYS.ONBOARDING]: '/onboarding/communities',
  },
}

function mapStateToProps(state) {
  return {
    isLoggedIn: state.authentication.isLoggedIn,
    json: state.json,
    modal: state.modal,
    pathname: state.routing.location.pathname,
    profile: state.profile,
  }
}

export default connect(mapStateToProps)(Navbar)

<|endoftext|>


<commit_before>/*\
 * $
 [ object ]
 * Global namespace for using Kimbo functions
\*/

var _push = Array.prototype.push,
  _slice = Array.prototype.slice,
  _filter = Array.prototype.filter,
  document = window.document,
  rootContext;

/*\
 * $(…)
 * Kimbo object collection.
 * All methods called from a Kimbo collection affects all elements in it.
\*/
function Kimbo(selector, context) {
  var elem, match;

  if (!Kimbo.isKimbo(this)) {
    return new Kimbo(selector, context);
  }

  // no selector, return empty Kimbo object
  if (!selector) {
    return this;
  }

  // asume a css selector, query the dom
  if (typeof selector === 'string') {

    match = r_id.exec(selector);

    // handle $('#id');
    if (match && match[1]) {
      elem = document.getElementById(match[1]);

      if (elem) {
        this[0] = elem;
        this.length = 1;
      }

      return this;
    }

    // if no context find it on document
    // or on the specified Kimbo context
    if (!context || context.kimbo) {
      return (context || rootContext).find(selector);

    // create new Kimbo object with the specified context
    } else if (context) {
      return Kimbo(context).find(selector);
    }

  // already a dom element
  } else if (selector.nodeType) {
    this[0] = selector;
    this.length = 1;
    return this;

  // is a function, call it when DOM is ready
  } else if (Kimbo.isFunction(selector)) {
    return rootContext.ready(selector);
  }

  // handle kimbo object, plain objects or other objects
  return Kimbo.makeArray(selector, this);
}

Kimbo.fn = Kimbo.prototype = {

  /*\
   * $(…).length
   [ property ]
   * The length of the current Kimbo collection.
   = (number) Integer representing the lenght of the current collection.
   > Usage
   * Having these paragraphs:
   |
one

   |
two

   |
three

   * Grab them and check for the length property:
   | $('p').length; // 3
  \*/
  length: 0,

  /*\
   * $(…).ready
   [ method ]
   * Execute a callback after de DOM is completely loaded
   > Parameters
   - callback (function) A function to call after de DOM is ready
   = (object) The original element
   > Usage
   | $(document).ready(function () {
   |   console.log('the DOM is loaded!');
   | });
   * Or using the shortcut
   | $(function () {
   |   console.log('the DOM is loaded!);
   | });
  \*/
  ready: function (callback) {
    // first check if already loaded
    if (/t/.test(document.readyState)) {
      callback.call(document);

    // listen when it loads
    } else {
      document.addEventListener('DOMContentLoaded', callback, false);
    }

    return rootContext;
  },

  /*\
   * $(…).get
   [ method ]
   * Retrieve native DOM elements
   > Parameters
   - index (number) #optional A zero-based integer indicating which element to retrieve, supports going backwards with negative index.
   = (array|object) An array of the native DOM elements or the specified element by index matched by Kimbo.
   > Usage
   |

   |

   |

   |

   * Get standard array of elements
   | $('li').get(); // [
,
]
   * Get the first element
   | $('li').get(0); //

   * Get the last element
   | $('li').get(-1); //

  \*/
  get: function (index) {
    return (!arguments.length) ? _slice.call(this) : (index < 0 ? this[this.length + index] : Kimbo(this[index]));
  },

  // needed to have an array-like object
  splice: Array.prototype.splice
};

/*\
 * $.forEach
 [ method ]
 * Iterator function that can be used to seamlessly iterate over both objects and arrays.
 > Parameters
 - obj (object) Object or array to iterate
 - callback (function) Function that will be executed on each iteration
 = (object) The original array or object
 > Usage
 | // iterating array
 | $.forEach(['a', 'b', 'c'], function (index, value) {
 |   alert(index + ': ' + value);
 | });
 |
 | // iterating object
 | $.forEach({name: 'Denis', surname: 'Ciccale'}, function (key, value) {
 |   alert(key + ': ' + value);
 | });
\*/
Kimbo.forEach = function (obj, callback) {
  var i,
    l = obj.length,
    isObj = l === undefined || typeof obj === 'function';

  if (isObj) {
    for (i in obj) {
      if (obj.hasOwnProperty(i) && callback.call(obj[i], i, obj[i], obj) === false) {
        break;
      }
    }
  } else {
    for (i = 0; i < l; i++) {
      if (callback.call(obj[i], obj[i], i, obj) === false) {
        break;
      }
    }
  }

  // return original obj
  return obj;
};

/*\
 * $.extend
 [ method ]
 * Merge the contents of two or more objects together into the first object.
 > Parameters
 - target (object|boolean) Object that will receive the merged properties if objects are passed, or target will extend Kimbo object. If target is `true` the passed objects will be recursively merged.
 - objectN (object) #optional One or more objects with additional properties.
 = (object) The extended target or a new copy if target is an empty object.
 > Usage
 * When passing two or more objects, all properties will be merged into the target object.
 | var obj1 = { msg: 'hi', info: { from: 'Denis' }};
 | var obj2 = { msg: 'Hi!', info: { time: '22:00PM' }};
 |
 | // merge obj1 into obj2
 | $.extend(obj1, obj2);
 |
 | // now obj1 is equal to:
 | { msg: 'Hi!', info: { time: '22:00PM' }}
 * If an empty target object is passed, none of the other objects will be directly modified
 | // pass an empty target
 | var obj3 = $.extend({}, obj1, obj);
 * To do a recursive merge, pass true as first argument, then the objects to merge
 | $.extend(true, obj1, obj2);
 | // obj1 will be:
 | { msg: 'Hi!', info: { from: 'Denis', time: '22:00PM' }}
\*/
Kimbo.extend = Kimbo.fn.extend = function () {
  var objs = arguments,
    target = objs[0] || {},
    deep = (target === true),
    cut = 1;

  // check for deep copy
  if (deep) {
    target = objs[1] || {};
    cut = 2;

  // extend Kimbo itself if only one argument is passed
  } else if (objs.length === 1) {
    target = this;
    cut = 0;
  }

  // make an array from the arguments
  // removing unnecessary objects
  objs = _slice.call(objs, cut);

  // loop through the objects
  Kimbo.forEach(objs, function (source) {
    // populate target from source
    Kimbo.forEach(source, function (key, value) {
      var src;

      if (deep && (Kimbo.isObject(value) || Kimbo.isArray(value))) {
        src = target[key];
        target[key] = Kimbo.extend(deep, src, value);

      } else if (value !== undefined) {
        target[key] = value;
      }
    });
  });

  return target;
};
<commit_msg>remove DOMContentLoaded event after execution<commit_after>/*\
 * $
 [ object ]
 * Global namespace for using Kimbo functions
\*/

var _push = Array.prototype.push,
  _slice = Array.prototype.slice,
  _filter = Array.prototype.filter,
  document = window.document,
  rootContext;

/*\
 * $(…)
 * Kimbo object collection.
 * All methods called from a Kimbo collection affects all elements in it.
\*/
function Kimbo(selector, context) {
  var elem, match;

  if (!Kimbo.isKimbo(this)) {
    return new Kimbo(selector, context);
  }

  // no selector, return empty Kimbo object
  if (!selector) {
    return this;
  }

  // asume a css selector, query the dom
  if (typeof selector === 'string') {

    match = r_id.exec(selector);

    // handle $('#id');
    if (match && match[1]) {
      elem = document.getElementById(match[1]);

      if (elem) {
        this[0] = elem;
        this.length = 1;
      }

      return this;
    }

    // if no context find it on document
    // or on the specified Kimbo context
    if (!context || context.kimbo) {
      return (context || rootContext).find(selector);

    // create new Kimbo object with the specified context
    } else if (context) {
      return Kimbo(context).find(selector);
    }

  // already a dom element
  } else if (selector.nodeType) {
    this[0] = selector;
    this.length = 1;
    return this;

  // is a function, call it when DOM is ready
  } else if (Kimbo.isFunction(selector)) {
    return rootContext.ready(selector);
  }

  // handle kimbo object, plain objects or other objects
  return Kimbo.makeArray(selector, this);
}

Kimbo.fn = Kimbo.prototype = {

  /*\
   * $(…).length
   [ property ]
   * The length of the current Kimbo collection.
   = (number) Integer representing the lenght of the current collection.
   > Usage
   * Having these paragraphs:
   |
one

   |
two

   |
three

   * Grab them and check for the length property:
   | $('p').length; // 3
  \*/
  length: 0,

  /*\
   * $(…).ready
   [ method ]
   * Execute a callback after de DOM is completely loaded
   > Parameters
   - callback (function) A function to call after de DOM is ready
   = (object) The original element
   > Usage
   | $(document).ready(function () {
   |   console.log('the DOM is loaded!');
   | });
   * Or using the shortcut
   | $(function () {
   |   console.log('the DOM is loaded!);
   | });
  \*/
  ready: function (callback) {
    // first check if already loaded
    if (/t/.test(document.readyState)) {
      callback.call(document);

    // listen when it loads
    } else {
      var completed = function() {
        document.removeEventListener('DOMContentLoaded', completed, false);
        callback();
      };
      document.addEventListener('DOMContentLoaded', completed, false);
    }

    return rootContext;
  },

  /*\
   * $(…).get
   [ method ]
   * Retrieve native DOM elements
   > Parameters
   - index (number) #optional A zero-based integer indicating which element to retrieve, supports going backwards with negative index.
   = (array|object) An array of the native DOM elements or the specified element by index matched by Kimbo.
   > Usage
   |

   |

   |

   |

   * Get standard array of elements
   | $('li').get(); // [
,
]
   * Get the first element
   | $('li').get(0); //

   * Get the last element
   | $('li').get(-1); //

  \*/
  get: function (index) {
    return (!arguments.length) ? _slice.call(this) : (index < 0 ? this[this.length + index] : Kimbo(this[index]));
  },

  // unique reference for the current instance of Kimbo
  ref: 'kimbo' + ('1' + Math.random()).replace(/\D/g, ''),

  // needed to have an array-like object
  splice: Array.prototype.splice
};

/*\
 * $.forEach
 [ method ]
 * Iterator function that can be used to seamlessly iterate over both objects and arrays.
 > Parameters
 - obj (object) Object or array to iterate
 - callback (function) Function that will be executed on each iteration
 = (object) The original array or object
 > Usage
 | // iterating array
 | $.forEach(['a', 'b', 'c'], function (index, value) {
 |   alert(index + ': ' + value);
 | });
 |
 | // iterating object
 | $.forEach({name: 'Denis', surname: 'Ciccale'}, function (key, value) {
 |   alert(key + ': ' + value);
 | });
\*/
Kimbo.forEach = function (obj, callback) {
  var i,
    l = obj.length,
    isObj = l === undefined || typeof obj === 'function';

  if (isObj) {
    for (i in obj) {
      if (obj.hasOwnProperty(i) && callback.call(obj[i], i, obj[i], obj) === false) {
        break;
      }
    }
  } else {
    for (i = 0; i < l; i++) {
      if (callback.call(obj[i], obj[i], i, obj) === false) {
        break;
      }
    }
  }

  // return original obj
  return obj;
};

/*\
 * $.extend
 [ method ]
 * Merge the contents of two or more objects together into the first object.
 > Parameters
 - target (object|boolean) Object that will receive the merged properties if objects are passed, or target will extend Kimbo object. If target is `true` the passed objects will be recursively merged.
 - objectN (object) #optional One or more objects with additional properties.
 = (object) The extended target or a new copy if target is an empty object.
 > Usage
 * When passing two or more objects, all properties will be merged into the target object.
 | var obj1 = { msg: 'hi', info: { from: 'Denis' }};
 | var obj2 = { msg: 'Hi!', info: { time: '22:00PM' }};
 |
 | // merge obj1 into obj2
 | $.extend(obj1, obj2);
 |
 | // now obj1 is equal to:
 | { msg: 'Hi!', info: { time: '22:00PM' }}
 * If an empty target object is passed, none of the other objects will be directly modified
 | // pass an empty target
 | var obj3 = $.extend({}, obj1, obj);
 * To do a recursive merge, pass true as first argument, then the objects to merge
 | $.extend(true, obj1, obj2);
 | // obj1 will be:
 | { msg: 'Hi!', info: { from: 'Denis', time: '22:00PM' }}
\*/
Kimbo.extend = Kimbo.fn.extend = function () {
  var objs = arguments,
    target = objs[0] || {},
    deep = (target === true),
    cut = 1;

  // check for deep copy
  if (deep) {
    target = objs[1] || {};
    cut = 2;

  // extend Kimbo itself if only one argument is passed
  } else if (objs.length === 1) {
    target = this;
    cut = 0;
  }

  // make an array from the arguments
  // removing unnecessary objects
  objs = _slice.call(objs, cut);

  // loop through the objects
  Kimbo.forEach(objs, function (source) {
    // populate target from source
    Kimbo.forEach(source, function (key, value) {
      var src;

      if (deep && (Kimbo.isObject(value) || Kimbo.isArray(value))) {
        src = target[key];
        target[key] = Kimbo.extend(deep, src, value);

      } else if (value !== undefined) {
        target[key] = value;
      }
    });
  });

  return target;
};
<|endoftext|>


<commit_before>var FACEBOOK_PAGE_HTML = '';
var SOURCE_URL = 'http://github.com/jyro2080/TabSense'
var BUGS_URL = 'http://github.com/jyro2080/TabSense/issues'

/*
function layout_windows() {
    windowMap.sort(function(a,b) { return (b.numTabs-a.numTabs); });
    for(var i=0; i<NUMCOL; i++) {winColumns[i]=[];}

    function columnNumber(counter) {
        var r = parseInt(counter / NUMCOL);
        var c = counter % NUMCOL;
        return ( ((r % 2) == 0) ? c : (NUMCOL-1-c) );
    }

    var colCount = 0;
    for(i in windowMap) {

        colCount = columnNumber(i);

        var wh = windowMap[i].elem.height();

        if(!winColumns[colCount]) winColumns[colCount] = [];

        windowMap[i].setLocation(
            getColumnHeight(colCount),
            (colCount*windowMap[i].elem.width()+(colCount+0.5) * HMARGIN));

        winColumns[colCount].push(windowMap[i]);
    }
}
*/

function get_column(x) {
    var width;
    for(i in windowMap) {
        width = windowMap[i].elem.width();
        break;
    }
    return parseInt((x - 0.5*HMARGIN)/width);
}

function relayout_column(colnum) {
    var column = winColumns[colnum];
    var wl = column.splice(0); // copy array and empty it
    for(var i=0; i < wl.length; i++) {
        var win = wl[i];
        win.setLocation(
            getColumnHeight(colnum),
            colnum * win.elem.width() + (colnum+0.5) * HMARGIN);
        column.push(wl[i]);
    }

}

var winColumns = new Array(NUMCOL);

var tabMap = [];
var windowMap = [];
var doneWindows = 0;
var dw, dh, winw;
var HMARGIN = 30;
var VMARGIN = 20;
var NUMCOL = 3;
var CEILING = 50;

var total_window = 0;

function process_windows(windows) {
    UI.totalWindows = windows.length;
    for(var i=0; i < UI.totalWindows; i++) {
        var w = windows[i];
        UI.add_window(w);

        bgport.postMessage({ name:'listtabs',condition:'WHERE wid = '+w.wid });
    }
}

function process_tabs(tabs) {
    var t = null;
    for(var i=0; i < tabs.length; i++) {
        t = tabs[i];
        UI.add_tab(t);
    }
    if(t) { UI.restyle_window(t.wid); }
    else { console.warn('No tab for window restyle'); }

}

function reprocess_tabs(tabs) {
    if(tabs.length == 0) {
        console.warn('No tabs to reprocess'); return;
    }
    var wframe = UI.wMap[tabs[0].wid];
    $('.mtab', wframe.elem).remove();
    process_tabs(tabs);
}

var ui;
$(document).ready(function(){

    ui = UI();

    bgport.postMessage({ name:'listwindows' }); // trigger setup windows

    refresh_favorites();

    $('#topbar #search').focus(function() {
        run_query('');
    });
    $('#topbar #search').blur(function() {
        blur_all_tabs(false);
    });

    $('#topbar #search').keyup(function() {
        run_query($(this).val());
    });

    $('#creator').css('top',(UI.dh-40)+'px');
    $('#creator').css('left',(UI.dw-120)+'px');

    $('#topbar #info').click(function(ev) {
        if($.trim($('#infopanel').html()) === '') {

            var topline = $('

').attr('id','topline')
                .append('Source'+
                    '   '+
                    'Bugs/Features'+
                    '   '+
                    'Close');

            $('#infopanel')
                .append(topline)
                .append(FACEBOOK_PAGE_HTML);
        }
        $('#infopanel').show();
    });

    $('#infopanel #close').click(function() {
        $(this).hide();
    });
    $('#infopanel').click(function() {
        $(this).hide();
    });

    position_infopanel();

    $('#bagbar').css({
        'top' : (UI.dh-40)+'px',
        'width' : (UI.dw-140)+'px'
    })

    load_bag();

    chrome.extension.onRequest.addListener(
        function(request, sender, sendResponse) {
            console.log(sender.tab ?
                        "from a content script:" + sender.tab.url :
                        "from the extension");
            if (request.msg == "RELOAD") {
                window.location.reload();
            }
        }
    );
});

function load_bag() {
    var bagWinList = Bag.list();
    $('#bagbar').empty();
    var bagl = bagWinList.length;
    if(bagl > 0) {
        $('#bagbar').append($('').attr('src', WinFrame.saveIcon));
    }
    for(var i=0; i < bagl; i++) {
        var w = bagWinList[i];
        var entry = $('

')
                .attr('class','winentry')
                .attr('id', w.id)
                .text(w.title)
                .click(bagEntryClicked);
        $('#bagbar').append(entry);
    }
}

function bagEntryClicked(ev) {
    var id = $(this).attr('id');
    var saved = Bag.remove(id);

    chrome.windows.create(null,
        function(win) {
            var wf = new WinFrame(win, saved.title);
            windowMap[win.id] = wf;
            $('body').append(wf.elem);

            var totalTabs = saved.tabs.length;
            var tabTitleMap = {};
            var tabFavIconMap = {};

            for(var i=0; i<totalTabs; i++) {
                var t = saved.tabs[i];
                tabTitleMap[t.url] = t.title;
                tabFavIconMap[t.url] = t.favIconUrl;
                chrome.tabs.create({
                    windowId : win.id,
                    index : t.index,
                    url : t.url,
                    selected : t.selected
                },
                function(tab) {
                    var title = tabTitleMap[tab.url];
                    var favIconUrl = tabFavIconMap[tab.url];
                    wf.addTab(new Tab(tab, title, favIconUrl));
                    if(wf.tabArray.length == totalTabs) {
                        wf.refreshStyle();
                        layout_windows();
                    }
                });
            }

        }
    );

    load_bag();
}

function position_infopanel() {
    var ipw = 550;
    var iph = 600;
    var left = (UI.dw - ipw)/2;
    var top = (UI.dh - iph)/2;
    $('#infopanel').css({
        'left' : left+'px',
        'top' : top+'px'
    });
}

function blur_all_tabs(yes)
{
    for(i in windowMap) {
        var win = windowMap[i];
        (yes ? win.blurTabs : win.unblurTabs)();
    }
}

function run_query(query)
{
    blur_all_tabs(true);
    if($.trim(query) != '') {
        query = query.toLowerCase();
        for (i in tabMap) {
            var tabTitle = tabMap[i].title;
            if(tabTitle && tabTitle.toLowerCase().indexOf(query) >= 0) {
                tab_selector = 'tab_'+tabMap[i].tid;
                for(j in windowMap) {
                    $('#'+tab_selector, windowMap[j].elem).css({
                        'color':'#000'
                    });
                }
            }
        }
    }

}

function refresh_favorites()
{
    // Load favorite favicons
    var favlist = Fav.list();
    var fll = favlist.length;
    var favbar = $('#topbar #favbar');
    favbar.empty();
    for(var i=0; i < fll; i++) {
        if(favlist[i].favIconUrl) {
            var img = $('').attr('src',favlist[i].favIconUrl)
                                .width('24px')
                                .height('24px');
            var link = $('').attr('href', favlist[i].url);
            link.append(img);
            favbar.append(link);
        }
    }
}
<commit_msg>again. winColumns are ancient history.<commit_after>var FACEBOOK_PAGE_HTML = '';
var SOURCE_URL = 'http://github.com/jyro2080/TabSense'
var BUGS_URL = 'http://github.com/jyro2080/TabSense/issues'

/*
function layout_windows() {
    windowMap.sort(function(a,b) { return (b.numTabs-a.numTabs); });
    for(var i=0; i<NUMCOL; i++) {winColumns[i]=[];}

    function columnNumber(counter) {
        var r = parseInt(counter / NUMCOL);
        var c = counter % NUMCOL;
        return ( ((r % 2) == 0) ? c : (NUMCOL-1-c) );
    }

    var colCount = 0;
    for(i in windowMap) {

        colCount = columnNumber(i);

        var wh = windowMap[i].elem.height();

        if(!winColumns[colCount]) winColumns[colCount] = [];

        windowMap[i].setLocation(
            getColumnHeight(colCount),
            (colCount*windowMap[i].elem.width()+(colCount+0.5) * HMARGIN));

        winColumns[colCount].push(windowMap[i]);
    }
}
*/

function get_column(x) {
    var width;
    for(i in windowMap) {
        width = windowMap[i].elem.width();
        break;
    }
    return parseInt((x - 0.5*HMARGIN)/width);
}

function relayout_column(colnum) {
    var column = UI.columns[colnum];
    var wl = column.splice(0); // copy array and empty it
    for(var i=0; i < wl.length; i++) {
        var win = wl[i];
        if(!win) console.error('FLAG 2');
        win.setLocation(
            getColumnHeight(colnum),
            colnum * win.elem.width() + (colnum+0.5) * HMARGIN);
        column.push(wl[i]);
    }

}

//var winColumns = new Array(NUMCOL);

var tabMap = [];
var windowMap = [];
var doneWindows = 0;
var dw, dh, winw;
var HMARGIN = 30;
var VMARGIN = 20;
var NUMCOL = 3;
var CEILING = 50;

var total_window = 0;

function process_windows(windows) {
    UI.totalWindows = windows.length;
    for(var i=0; i < UI.totalWindows; i++) {
        var w = windows[i];
        UI.add_window(w);

        bgport.postMessage({ name:'listtabs',condition:'WHERE wid = '+w.wid });
    }
}

function process_tabs(tabs) {
    var t = null;
    for(var i=0; i < tabs.length; i++) {
        t = tabs[i];
        UI.add_tab(t);
    }
    if(t) { UI.restyle_window(t.wid); }
    else { console.warn('No tab for window restyle'); }

}

function reprocess_tabs(tabs) {
    if(tabs.length == 0) {
        console.warn('No tabs to reprocess'); return;
    }
    var wframe = UI.wMap[tabs[0].wid];
    $('.mtab', wframe.elem).remove();
    process_tabs(tabs);
}

var ui;
$(document).ready(function(){

    ui = UI();

    bgport.postMessage({ name:'listwindows' }); // trigger setup windows

    refresh_favorites();

    $('#topbar #search').focus(function() {
        run_query('');
    });
    $('#topbar #search').blur(function() {
        blur_all_tabs(false);
    });

    $('#topbar #search').keyup(function() {
        run_query($(this).val());
    });

    $('#creator').css('top',(UI.dh-40)+'px');
    $('#creator').css('left',(UI.dw-120)+'px');

    $('#topbar #info').click(function(ev) {
        if($.trim($('#infopanel').html()) === '') {

            var topline = $('

').attr('id','topline')
                .append('Source'+
                    '   '+
                    'Bugs/Features'+
                    '   '+
                    'Close');

            $('#infopanel')
                .append(topline)
                .append(FACEBOOK_PAGE_HTML);
        }
        $('#infopanel').show();
    });

    $('#infopanel #close').click(function() {
        $(this).hide();
    });
    $('#infopanel').click(function() {
        $(this).hide();
    });

    position_infopanel();

    $('#bagbar').css({
        'top' : (UI.dh-40)+'px',
        'width' : (UI.dw-140)+'px'
    })

    load_bag();

    chrome.extension.onRequest.addListener(
        function(request, sender, sendResponse) {
            console.log(sender.tab ?
                        "from a content script:" + sender.tab.url :
                        "from the extension");
            if (request.msg == "RELOAD") {
                window.location.reload();
            }
        }
    );
});

function load_bag() {
    var bagWinList = Bag.list();
    $('#bagbar').empty();
    var bagl = bagWinList.length;
    if(bagl > 0) {
        $('#bagbar').append($('').attr('src', WinFrame.saveIcon));
    }
    for(var i=0; i < bagl; i++) {
        var w = bagWinList[i];
        var entry = $('

')
                .attr('class','winentry')
                .attr('id', w.id)
                .text(w.title)
                .click(bagEntryClicked);
        $('#bagbar').append(entry);
    }
}

function bagEntryClicked(ev) {
    var id = $(this).attr('id');
    var saved = Bag.remove(id);

    chrome.windows.create(null,
        function(win) {
            var wf = new WinFrame(win, saved.title);
            windowMap[win.id] = wf;
            $('body').append(wf.elem);

            var totalTabs = saved.tabs.length;
            var tabTitleMap = {};
            var tabFavIconMap = {};

            for(var i=0; i<totalTabs; i++) {
                var t = saved.tabs[i];
                tabTitleMap[t.url] = t.title;
                tabFavIconMap[t.url] = t.favIconUrl;
                chrome.tabs.create({
                    windowId : win.id,
                    index : t.index,
                    url : t.url,
                    selected : t.selected
                },
                function(tab) {
                    var title = tabTitleMap[tab.url];
                    var favIconUrl = tabFavIconMap[tab.url];
                    wf.addTab(new Tab(tab, title, favIconUrl));
                    if(wf.tabArray.length == totalTabs) {
                        wf.refreshStyle();
                        layout_windows();
                    }
                });
            }

        }
    );

    load_bag();
}

function position_infopanel() {
    var ipw = 550;
    var iph = 600;
    var left = (UI.dw - ipw)/2;
    var top = (UI.dh - iph)/2;
    $('#infopanel').css({
        'left' : left+'px',
        'top' : top+'px'
    });
}

function blur_all_tabs(yes)
{
    for(i in windowMap) {
        var win = windowMap[i];
        (yes ? win.blurTabs : win.unblurTabs)();
    }
}

function run_query(query)
{
    blur_all_tabs(true);
    if($.trim(query) != '') {
        query = query.toLowerCase();
        for (i in tabMap) {
            var tabTitle = tabMap[i].title;
            if(tabTitle && tabTitle.toLowerCase().indexOf(query) >= 0) {
                tab_selector = 'tab_'+tabMap[i].tid;
                for(j in windowMap) {
                    $('#'+tab_selector, windowMap[j].elem).css({
                        'color':'#000'
                    });
                }
            }
        }
    }

}

function refresh_favorites()
{
    // Load favorite favicons
    var favlist = Fav.list();
    var fll = favlist.length;
    var favbar = $('#topbar #favbar');
    favbar.empty();
    for(var i=0; i < fll; i++) {
        if(favlist[i].favIconUrl) {
            var img = $('').attr('src',favlist[i].favIconUrl)
                                .width('24px')
                                .height('24px');
            var link = $('').attr('href', favlist[i].url);
            link.append(img);
            favbar.append(link);
        }
    }
}
<|endoftext|>


<commit_before>window.addEvent('domready', function() {
	// defines the event that we will use to load results when the hash changes
	window.addEvent('hashchange', function(newhash) {
		if (location.hash.substr(3) !== $('q').get('value')) {
			$('q').set('value', location.hash.substr(3));
		}
		input_req.get({
			'q'   : location.hash.substr(3) ,
			'num' : $('items_per_page').get('value')
		});
		$('num').set('value', $('items_per_page').get('value'));
		$('skipped').set('value', 0);
	});

	// the request that will be triggered after the content of the search box
	// is modified. we create it outside the addEvent so we can cancel previous
	// requests before they are completed using the parameter link:
	var input_req = new Request({
		method: 'get',
		url: 'ajax/search.php',
		link: 'cancel',
		onRequest: function() {
			$('news-container').setStyle('opacity', '0.5');
		},
		onComplete: function(response) {
			if (response !== undefined) {
				timeout = setTimeout(function () {
					$('news-container').set('html', response);
					$('news-container').setStyle('opacity', '1');
				}, 0);
			}
		},
		onFailure: function(response) {
			if (response.status == 0) {
				alert('Please check your internet connectivity or reload the page.');
			} else {
				alert('Error ' + response.status + '. Please reload the page or contact javier@javiercejudo.com');
			}
			$('news-container').setStyle('opacity', '1');
			clearInterval(timer);
		}
	});

	// handles the initial hash, if it exists
	// it will use input_req, so it must be after that is defined
	if (location.hash.length > 3) {
		window.fireEvent('hashchange');
	}

	// the request that will be triggered after asking for more items
	// we create it outside the addEvent so we can ignore following
	// requests before they are completed using the parameter link:
	var more_req = new Request({
		method: 'get',
		url: 'ajax/more.php',
		link: 'ignore',
		onRequest: function() {
			$$('.more_link').setStyle('display', 'none');
			$('more_loading').setStyle('display', 'block');
		},
		onComplete: function(response) {
			if (response !== undefined) {
				timeout = setTimeout(function () {
					var auxElement = new Element('ul', {'class': 'news-feed'});
					auxElement.set('html', response);
					auxElement.inject($('more-items-container'), 'before');
					var num_top = parseInt($('num').get('value'), 10) + parseInt($('items_per_page').get('value'), 10);
					var num_tot = parseInt($('num_total').get('html'), 10);
					var num_sho;
					$('more_loading').setStyle('display', 'none');
					console.log("Number showing: " + num_top);
					console.log("NUM: " + parseInt($('num').get('value'), 10));
					console.log("IPP: " + parseInt($('items_per_page').get('value'), 10));
					console.log("Number total: " + num_tot);
					if (num_top >= num_tot) {
						num_sho = num_tot;
						$$('.more_link').setStyle('display', 'none');
					} else {
						num_sho = num_top;
						$$('.more_link').setStyle('display', 'block');
					}
					$('num').set('value', num_top);
					$('skipped').set('value', parseInt($('skipped').get('value'), 10) + parseInt($('items_per_page').get('value'), 10));
					$('num_showing').set('html', num_sho);
				}, 0);
			}
		},
		onFailure: function(response) {
			if (response.status == 0) {
				alert('Please check your internet connectivity or reload the page.');
			} else {
				alert('Error ' + response.status + '. Please reload the page or contact javier@javiercejudo.com');
			}
			$$('.more_link').setStyle('display', 'block');
			$('more_loading').setStyle('display', 'none');
			clearInterval(timer);
		}
	});

	// changes hash, what will trigger the request whenever we tap a key that
	// actually modifies the content of the search box
	$('q').addEvent('input', function(event){
		window.sethash("#q=" + $('q').get('value'));
	});

	// when the suggestion is clicked, this brings it into the search box
	// and refreshes the results
	$$('.outer-container').addEvent('click:relay(a.top_suggestion_link)', function(event, target){
		if(event) { event.preventDefault(); }
		window.sethash("#q=" + target.get('html'));
	});

	// allows the tab button to mimic the behavior of a click on a suggestion
	// this is because the suggestion is the next item that can be focused
	$$('.outer-container').addEvent('focus:relay(a.top_suggestion_link)', function(event, target){
		$$('.outer-container').fireEvent('click:relay(a.top_suggestion_link)', [event, target]);
	});

	// triggers the request to load more items when the link is clicked
	$$('.outer-container').addEvent('click:relay(a.more_link)', function(event, target){
		if(event) { event.preventDefault(); }
		if ($('num_total') != undefined && parseInt($('skipped').get('value')) + parseInt($('items_per_page').get('value')) < parseInt($('num_total').get('html'))){
			console.log("Searching for: " + $('q').get('value'));
			console.log("Number of stories: " + $('num').get('value'));
			console.log("Skipping: " + $('skipped').get('value'));
			more_req.get({
				'q'    : $('q').get('value'),
				'num'  : $('items_per_page').get('value'),
				'skip' : $('skipped').get('value')
			});
		} else {
			$$('.more_link').setStyle('display', 'none');
		}
	});

	// this is the test to see if we need to load more items
	// basically, if we are close to the bottom, we mimic the user
	// clicking the link to load more items
	var infiniteScroll = function(){
		var elt = document.body;
		if (elt.getScroll().y >= elt.getScrollSize().y - 2*elt.getSize().y) {
			$$('.outer-container').fireEvent('click:relay(a.more_link)');
		}
	};
	// this defines the frequency of the test
	var timer = infiniteScroll.periodical(300);
});
<commit_msg>focus even removed for top suggestion<commit_after>window.addEvent('domready', function() {
	// defines the event that we will use to load results when the hash changes
	window.addEvent('hashchange', function(newhash) {
		if (location.hash.substr(3) !== $('q').get('value')) {
			$('q').set('value', location.hash.substr(3));
		}
		input_req.get({
			'q'   : location.hash.substr(3) ,
			'num' : $('items_per_page').get('value')
		});
		$('num').set('value', $('items_per_page').get('value'));
		$('skipped').set('value', 0);
	});

	// the request that will be triggered after the content of the search box
	// is modified. we create it outside the addEvent so we can cancel previous
	// requests before they are completed using the parameter link:
	var input_req = new Request({
		method: 'get',
		url: 'ajax/search.php',
		link: 'cancel',
		onRequest: function() {
			$('news-container').setStyle('opacity', '0.5');
		},
		onComplete: function(response) {
			if (response !== undefined) {
				timeout = setTimeout(function () {
					$('news-container').set('html', response);
					$('news-container').setStyle('opacity', '1');
				}, 0);
			}
		},
		onFailure: function(response) {
			if (response.status == 0) {
				alert('Please check your internet connectivity or reload the page.');
			} else {
				alert('Error ' + response.status + '. Please reload the page or contact javier@javiercejudo.com');
			}
			$('news-container').setStyle('opacity', '1');
			clearInterval(timer);
		}
	});

	// handles the initial hash, if it exists
	// it will use input_req, so it must be after that is defined
	if (location.hash.length > 3) {
		window.fireEvent('hashchange');
	}

	// the request that will be triggered after asking for more items
	// we create it outside the addEvent so we can ignore following
	// requests before they are completed using the parameter link:
	var more_req = new Request({
		method: 'get',
		url: 'ajax/more.php',
		link: 'ignore',
		onRequest: function() {
			$$('.more_link').setStyle('display', 'none');
			$('more_loading').setStyle('display', 'block');
		},
		onComplete: function(response) {
			if (response !== undefined) {
				timeout = setTimeout(function () {
					var auxElement = new Element('ul', {'class': 'news-feed'});
					auxElement.set('html', response);
					auxElement.inject($('more-items-container'), 'before');
					var num_top = parseInt($('num').get('value'), 10) + parseInt($('items_per_page').get('value'), 10);
					var num_tot = parseInt($('num_total').get('html'), 10);
					var num_sho;
					$('more_loading').setStyle('display', 'none');
					console.log("Number showing: " + num_top);
					console.log("NUM: " + parseInt($('num').get('value'), 10));
					console.log("IPP: " + parseInt($('items_per_page').get('value'), 10));
					console.log("Number total: " + num_tot);
					if (num_top >= num_tot) {
						num_sho = num_tot;
						$$('.more_link').setStyle('display', 'none');
					} else {
						num_sho = num_top;
						$$('.more_link').setStyle('display', 'block');
					}
					$('num').set('value', num_top);
					$('skipped').set('value', parseInt($('skipped').get('value'), 10) + parseInt($('items_per_page').get('value'), 10));
					$('num_showing').set('html', num_sho);
				}, 0);
			}
		},
		onFailure: function(response) {
			if (response.status == 0) {
				alert('Please check your internet connectivity or reload the page.');
			} else {
				alert('Error ' + response.status + '. Please reload the page or contact javier@javiercejudo.com');
			}
			$$('.more_link').setStyle('display', 'block');
			$('more_loading').setStyle('display', 'none');
			clearInterval(timer);
		}
	});

	// changes hash, what will trigger the request whenever we tap a key that
	// actually modifies the content of the search box
	$('q').addEvent('input', function(event){
		window.sethash("#q=" + $('q').get('value'));
	});

	// when the suggestion is clicked, this brings it into the search box
	// and refreshes the results
	$$('.outer-container').addEvent('click:relay(a.top_suggestion_link)', function(event, target){
		if(event) { event.preventDefault(); }
		window.sethash("#q=" + target.get('html'));
	});

	// allows the tab button to mimic the behavior of a click on a suggestion
	// this is because the suggestion is the next item that can be focused
	// $$('.outer-container').addEvent('focus:relay(a.top_suggestion_link)', function(event, target){
	// 	$$('.outer-container').fireEvent('click:relay(a.top_suggestion_link)', [event, target]);
	// });

	// triggers the request to load more items when the link is clicked
	$$('.outer-container').addEvent('click:relay(a.more_link)', function(event, target){
		if(event) { event.preventDefault(); }
		if ($('num_total') != undefined && parseInt($('skipped').get('value')) + parseInt($('items_per_page').get('value')) < parseInt($('num_total').get('html'))){
			console.log("Searching for: " + $('q').get('value'));
			console.log("Number of stories: " + $('num').get('value'));
			console.log("Skipping: " + $('skipped').get('value'));
			more_req.get({
				'q'    : $('q').get('value'),
				'num'  : $('items_per_page').get('value'),
				'skip' : $('skipped').get('value')
			});
		} else {
			$$('.more_link').setStyle('display', 'none');
		}
	});

	// this is the test to see if we need to load more items
	// basically, if we are close to the bottom, we mimic the user
	// clicking the link to load more items
	var infiniteScroll = function(){
		var elt = document.body;
		if (elt.getScroll().y >= elt.getScrollSize().y - 2*elt.getSize().y) {
			$$('.outer-container').fireEvent('click:relay(a.more_link)');
		}
	};
	// this defines the frequency of the test
	var timer = infiniteScroll.periodical(300);
});
<|endoftext|>


<commit_before>var config = require('./protractor-shared.conf').config;

config.allScriptsTimeout = 30000;
config.seleniumArgs = [ '-browserTimeout=60' ];

config.capabilities = {
    browserName: 'phantomjs'
};

exports.config = config;
<commit_msg>Use karma phantomjs in travis e2e-tests<commit_after>var config = require('./protractor-shared.conf').config;

config.allScriptsTimeout = 30000;
config.seleniumArgs = [ '-browserTimeout=60' ];

config.capabilities = {
    browserName: 'phantomjs',
    'phantomjs.binary.path': './node_modules/karma-phantomjs-launcher/node_modules/phantomjs/bin/phantomjs'
};

exports.config = config;
<|endoftext|>