<commit_before>;(function ($, window, document, undefined) {
  'use strict';

  Foundation.libs.dropdown = {
    name : 'dropdown',

    version : '{{VERSION}}',

    settings : {
      active_class: 'open',
      disabled_class: 'disabled',
      mega_class: 'mega',
      align: 'bottom',
      is_hover: false,
      hover_timeout: 150,
      opened: function(){},
      closed: function(){}
    },

    init : function (scope, method, options) {
      Foundation.inherit(this, 'throttle');

      $.extend(true, this.settings, method, options);
      this.bindings(method, options);
    },

    events : function (scope) {
      var self = this,
          S = self.S;

      S(this.scope)
        .off('.dropdown')
        .on('click.fndtn.dropdown', '[' + this.attr_name() + ']', function (e) {
          var settings = S(this).data(self.attr_name(true) + '-init') || self.settings;
          if (!settings.is_hover || Modernizr.touch) {
            e.preventDefault();
            if (S(this).parent('[data-reveal-id]')) {
              e.stopPropagation();
            }
            self.toggle($(this));
          }
        })
        .on('mouseenter.fndtn.dropdown', '[' + this.attr_name() + '], [' + this.attr_name() + '-content]', function (e) {
          var $this = S(this),
              dropdown,
              target;

          clearTimeout(self.timeout);

          if ($this.data(self.data_attr())) {
            dropdown = S('#' + $this.data(self.data_attr()));
            target = $this;
          } else {
            dropdown = $this;
            target = S('[' + self.attr_name() + '="' + dropdown.attr('id') + '"]');
          }

          var settings = target.data(self.attr_name(true) + '-init') || self.settings;

          if(S(e.currentTarget).data(self.data_attr()) && settings.is_hover) {
            self.closeall.call(self);
          }

          if (settings.is_hover) self.open.apply(self, [dropdown, target]);
        })
        .on('mouseleave.fndtn.dropdown', '[' + this.attr_name() + '], [' + this.attr_name() + '-content]', function (e) {
          var $this = S(this);
          var settings;

          if ($this.data(self.data_attr())) {
              settings = $this.data(self.data_attr(true) + '-init') || self.settings;
          }
          else {
              var target   = S('[' + self.attr_name() + '="' + S(this).attr('id') + '"]'),
                  settings = target.data(self.attr_name(true) + '-init') || self.settings;
          }

          self.timeout = setTimeout(function () {
            if ($this.data(self.data_attr())) {
              if (settings.is_hover) self.close.call(self, S('#' + $this.data(self.data_attr())));
            } else {
              if (settings.is_hover) self.close.call(self, $this);
            }
          }.bind(this), settings.hover_timeout);
        })
        .on('click.fndtn.dropdown', function (e) {
          var parent = S(e.target).closest('[' + self.attr_name() + '-content]');
          var links  = parent.find('a');

          if (links.length > 0 && parent.attr('aria-autoclose') !== "false") {
              self.close.call(self, S('[' + self.attr_name() + '-content]'));
          }

          if (S(e.target).closest('[' + self.attr_name() + ']').length > 0) {
            return;
          }

          if (!(S(e.target).data('revealId')) &&
            (parent.length > 0 && (S(e.target).is('[' + self.attr_name() + '-content]') ||
              $.contains(parent.first()[0], e.target)))) {
            e.stopPropagation();
            return;
          }

          self.close.call(self, S('[' + self.attr_name() + '-content]'));
        })
        .on('opened.fndtn.dropdown', '[' + self.attr_name() + '-content]', function () {
          self.settings.opened.call(this);
        })
        .on('closed.fndtn.dropdown', '[' + self.attr_name() + '-content]', function () {
          self.settings.closed.call(this);
        });

      S(window)
        .off('.dropdown')
        .on('resize.fndtn.dropdown', self.throttle(function () {
          self.resize.call(self);
        }, 50));

      this.resize();
    },

    close: function (dropdown) {
      var self = this;
      dropdown.each(function () {
        var original_target = $('[' + self.attr_name() + '=' + dropdown[0].id + ']') || $('aria-controls=' + dropdown[0].id+ ']');
        original_target.attr('aria-expanded', 'false');
        if (self.S(this).hasClass(self.settings.active_class)) {
          self.S(this)
            .css(Foundation.rtl ? 'right':'left', '-99999px')
            .attr('aria-hidden', 'true')
            .removeClass(self.settings.active_class)
            .prev('[' + self.attr_name() + ']')
            .removeClass(self.settings.active_class)
            .removeData('target');

          self.S(this).trigger('closed').trigger('closed.fndtn.dropdown', [dropdown]);
        }
      });
      dropdown.removeClass('f-open-' + this.attr_name(true));
    },

    closeall: function() {
      var self = this;
      $.each(self.S('.f-open-' + this.attr_name(true)), function() {
        self.close.call(self, self.S(this));
      });
    },

    open: function (dropdown, target) {
      this
        .css(dropdown
        .addClass(this.settings.active_class), target);
      dropdown.prev('[' + this.attr_name() + ']').addClass(this.settings.active_class);
      dropdown.data('target', target.get(0)).trigger('opened').trigger('opened.fndtn.dropdown', [dropdown, target]);
      dropdown.attr('aria-hidden', 'false');
      target.attr('aria-expanded', 'true');
      dropdown.focus();
      dropdown.addClass('f-open-' + this.attr_name(true));
    },

    data_attr: function () {
      if (this.namespace.length > 0) {
        return this.namespace + '-' + this.name;
      }

      return this.name;
    },

    toggle : function (target) {
      if (target.hasClass(this.settings.disabled_class)) {
        return;
      }
      var dropdown = this.S('#' + target.data(this.data_attr()));
      if (dropdown.length === 0) {
        // No dropdown found, not continuing
        return;
      }

      this.close.call(this, this.S('[' + this.attr_name() + '-content]').not(dropdown));

      if (dropdown.hasClass(this.settings.active_class)) {
        this.close.call(this, dropdown);
        if (dropdown.data('target') !== target.get(0))
          this.open.call(this, dropdown, target);
      } else {
        this.open.call(this, dropdown, target);
      }
    },

    resize : function () {
      var dropdown = this.S('[' + this.attr_name() + '-content].open'),
          target = this.S('[' + this.attr_name() + '="' + dropdown.attr('id') + '"]');

      if (dropdown.length && target.length) {
        this.css(dropdown, target);
      }
    },

    css : function (dropdown, target) {
      var left_offset = Math.max((target.width() - dropdown.width()) / 2, 8),
          settings = target.data(this.attr_name(true) + '-init') || this.settings;

      this.clear_idx();

      if (this.small()) {
        var p = this.dirs.bottom.call(dropdown, target, settings);

        dropdown.attr('style', '').removeClass('drop-left drop-right drop-top').css({
          position : 'absolute',
          width: '95%',
          'max-width': 'none',
          top: p.top
        });

        dropdown.css(Foundation.rtl ? 'right':'left', left_offset);
      } else {

        this.style(dropdown, target, settings);
      }

      return dropdown;
    },

    style : function (dropdown, target, settings) {
      var css = $.extend({position: 'absolute'},
        this.dirs[settings.align].call(dropdown, target, settings));

      dropdown.attr('style', '').css(css);
    },

    // return CSS property object
    // `this` is the dropdown
    dirs : {
      // Calculate target offset
      _base : function (t) {
        var o_p = this.offsetParent(),
            o = o_p.offset(),
            p = t.offset();

        p.top -= o.top;
        p.left -= o.left;

        //set some flags on the p object to pass along
        p.missRight = false;
        p.missTop = false;
        p.missLeft = false;
        p.leftRightFlag = false;

        //lets see if the panel will be off the screen
        //get the actual width of the page and store it
        var actualBodyWidth;
        if (document.getElementsByClassName('row')[0]) {
          actualBodyWidth = document.getElementsByClassName('row')[0].clientWidth;
        } else {
          actualBodyWidth = window.outerWidth;
        }

        var actualMarginWidth = (window.outerWidth - actualBodyWidth) / 2;
        var actualBoundary = actualBodyWidth;

        if (!this.hasClass('mega')) {
          //miss top
          if (t.offset().top <= this.outerHeight()) {
            p.missTop = true;
            actualBoundary = window.outerWidth - actualMarginWidth;
            p.leftRightFlag = true;
          }

          //miss right
          if (t.offset().left + this.outerWidth() > t.offset().left + actualMarginWidth && t.offset().left - actualMarginWidth > this.outerWidth()) {
            p.missRight = true;
            p.missLeft = false;
          }

          //miss left
          if (t.offset().left - this.outerWidth() <= 0) {
            p.missLeft = true;
            p.missRight = false;
          }
        }

        return p;
      },

      top: function (t, s) {
        var self = Foundation.libs.dropdown,
            p = self.dirs._base.call(this, t);

        this.addClass('drop-top');

        if (p.missTop == true) {
          p.top = p.top + t.outerHeight() + this.outerHeight();
          this.removeClass('drop-top');
        }

        if (p.missRight == true) {
          p.left = p.left - this.outerWidth() + t.outerWidth();
        }

        if (t.outerWidth() < this.outerWidth() || self.small() || this.hasClass(s.mega_menu)) {
          self.adjust_pip(this,t,s,p);
        }

        if (Foundation.rtl) {
          return {left: p.left - this.outerWidth() + t.outerWidth(),
            top: p.top - this.outerHeight()};
        }

        return {left: p.left, top: p.top - this.outerHeight()};
      },

      bottom: function (t,s) {
        var self = Foundation.libs.dropdown,
            p = self.dirs._base.call(this, t);

        if (p.missRight == true) {
          p.left = p.left - this.outerWidth() + t.outerWidth();
        }

        if (t.outerWidth() < this.outerWidth() || self.small() || this.hasClass(s.mega_menu)) {
          self.adjust_pip(this,t,s,p);
        }

        if (self.rtl) {
          return {left: p.left - this.outerWidth() + t.outerWidth(), top: p.top + t.outerHeight()};
        }

        return {left: p.left, top: p.top + t.outerHeight()};
      },

      left: function (t, s) {
        var p = Foundation.libs.dropdown.dirs._base.call(this, t);

        this.addClass('drop-left');

        if (p.missLeft == true) {
          p.left =  p.left + this.outerWidth();
          p.top = p.top + t.outerHeight();
          this.removeClass('drop-left');
        }

        return {left: p.left - this.outerWidth(), top: p.top};
      },

      right: function (t, s) {
        var p = Foundation.libs.dropdown.dirs._base.call(this, t);

        this.addClass('drop-right');

        if (p.missRight == true) {
          p.left = p.left - this.outerWidth();
          p.top = p.top + t.outerHeight();
          this.removeClass('drop-right');
        } else {
          p.triggeredRight = true;
        }

        var self = Foundation.libs.dropdown;

        if (t.outerWidth() < this.outerWidth() || self.small() || this.hasClass(s.mega_menu)) {
          self.adjust_pip(this,t,s,p);
        }

        return {left: p.left + t.outerWidth(), top: p.top};
      }
    },

    // Insert rule to style psuedo elements
    adjust_pip : function (dropdown,target,settings,position) {
      var sheet = Foundation.stylesheet,
          pip_offset_base = 8;

      if (dropdown.hasClass(settings.mega_class)) {
        pip_offset_base = position.left + (target.outerWidth()/2) - 8;
      }
      else if (this.small()) {
        pip_offset_base += position.left - 8;
      }

      this.rule_idx = sheet.cssRules.length;

      //default
      var sel_before = '.f-dropdown.open:before',
          sel_after  = '.f-dropdown.open:after',
          css_before = 'left: ' + pip_offset_base + 'px;',
          css_after  = 'left: ' + (pip_offset_base - 1) + 'px;';

      if (position.missRight == true) {
        pip_offset_base = dropdown.outerWidth() - 23;
        sel_before = '.f-dropdown.open:before',
        sel_after  = '.f-dropdown.open:after',
        css_before = 'left: ' + pip_offset_base + 'px;',
        css_after  = 'left: ' + (pip_offset_base - 1) + 'px;';
      }

      //just a case where right is fired, but its not missing right
      if (position.triggeredRight == true) {
        sel_before = '.f-dropdown.open:before',
        sel_after  = '.f-dropdown.open:after',
        css_before = 'left:-12px;',
        css_after  = 'left:-14px;';
      }

      if (sheet.insertRule) {
        sheet.insertRule([sel_before, '{', css_before, '}'].join(' '), this.rule_idx);
        sheet.insertRule([sel_after, '{', css_after, '}'].join(' '), this.rule_idx + 1);
      } else {
        sheet.addRule(sel_before, css_before, this.rule_idx);
        sheet.addRule(sel_after, css_after, this.rule_idx + 1);
      }
    },

    // Remove old dropdown rule index
    clear_idx : function () {
      var sheet = Foundation.stylesheet;

      if (typeof this.rule_idx !== 'undefined') {
        sheet.deleteRule(this.rule_idx);
        sheet.deleteRule(this.rule_idx);
        delete this.rule_idx;
      }
    },

    small : function () {
      return matchMedia(Foundation.media_queries.small).matches &&
        !matchMedia(Foundation.media_queries.medium).matches;
    },

    off: function () {
      this.S(this.scope).off('.fndtn.dropdown');
      this.S('html, body').off('.fndtn.dropdown');
      this.S(window).off('.fndtn.dropdown');
      this.S('[data-dropdown-content]').off('.fndtn.dropdown');
    },

    reflow : function () {}
  };
}(jQuery, window, window.document));
<commit_msg>Don't close dropdowns on element clicks outside the DOM. Fix #6174<commit_after>;(function ($, window, document, undefined) {
  'use strict';

  Foundation.libs.dropdown = {
    name : 'dropdown',

    version : '{{VERSION}}',

    settings : {
      active_class: 'open',
      disabled_class: 'disabled',
      mega_class: 'mega',
      align: 'bottom',
      is_hover: false,
      hover_timeout: 150,
      opened: function(){},
      closed: function(){}
    },

    init : function (scope, method, options) {
      Foundation.inherit(this, 'throttle');

      $.extend(true, this.settings, method, options);
      this.bindings(method, options);
    },

    events : function (scope) {
      var self = this,
          S = self.S;

      S(this.scope)
        .off('.dropdown')
        .on('click.fndtn.dropdown', '[' + this.attr_name() + ']', function (e) {
          var settings = S(this).data(self.attr_name(true) + '-init') || self.settings;
          if (!settings.is_hover || Modernizr.touch) {
            e.preventDefault();
            if (S(this).parent('[data-reveal-id]')) {
              e.stopPropagation();
            }
            self.toggle($(this));
          }
        })
        .on('mouseenter.fndtn.dropdown', '[' + this.attr_name() + '], [' + this.attr_name() + '-content]', function (e) {
          var $this = S(this),
              dropdown,
              target;

          clearTimeout(self.timeout);

          if ($this.data(self.data_attr())) {
            dropdown = S('#' + $this.data(self.data_attr()));
            target = $this;
          } else {
            dropdown = $this;
            target = S('[' + self.attr_name() + '="' + dropdown.attr('id') + '"]');
          }

          var settings = target.data(self.attr_name(true) + '-init') || self.settings;

          if(S(e.currentTarget).data(self.data_attr()) && settings.is_hover) {
            self.closeall.call(self);
          }

          if (settings.is_hover) self.open.apply(self, [dropdown, target]);
        })
        .on('mouseleave.fndtn.dropdown', '[' + this.attr_name() + '], [' + this.attr_name() + '-content]', function (e) {
          var $this = S(this);
          var settings;

          if ($this.data(self.data_attr())) {
              settings = $this.data(self.data_attr(true) + '-init') || self.settings;
          }
          else {
              var target   = S('[' + self.attr_name() + '="' + S(this).attr('id') + '"]'),
                  settings = target.data(self.attr_name(true) + '-init') || self.settings;
          }

          self.timeout = setTimeout(function () {
            if ($this.data(self.data_attr())) {
              if (settings.is_hover) self.close.call(self, S('#' + $this.data(self.data_attr())));
            } else {
              if (settings.is_hover) self.close.call(self, $this);
            }
          }.bind(this), settings.hover_timeout);
        })
        .on('click.fndtn.dropdown', function (e) {
          var parent = S(e.target).closest('[' + self.attr_name() + '-content]');
          var links  = parent.find('a');

          if (links.length > 0 && parent.attr('aria-autoclose') !== "false") {
              self.close.call(self, S('[' + self.attr_name() + '-content]'));
          }

          if (e.target !== document.body && !$.contains(e.target, document.body)) {
            return;
          }

          if (S(e.target).closest('[' + self.attr_name() + ']').length > 0) {
            return;
          }

          if (!(S(e.target).data('revealId')) &&
            (parent.length > 0 && (S(e.target).is('[' + self.attr_name() + '-content]') ||
              $.contains(parent.first()[0], e.target)))) {
            e.stopPropagation();
            return;
          }

          self.close.call(self, S('[' + self.attr_name() + '-content]'));
        })
        .on('opened.fndtn.dropdown', '[' + self.attr_name() + '-content]', function () {
          self.settings.opened.call(this);
        })
        .on('closed.fndtn.dropdown', '[' + self.attr_name() + '-content]', function () {
          self.settings.closed.call(this);
        });

      S(window)
        .off('.dropdown')
        .on('resize.fndtn.dropdown', self.throttle(function () {
          self.resize.call(self);
        }, 50));

      this.resize();
    },

    close: function (dropdown) {
      var self = this;
      dropdown.each(function () {
        var original_target = $('[' + self.attr_name() + '=' + dropdown[0].id + ']') || $('aria-controls=' + dropdown[0].id+ ']');
        original_target.attr('aria-expanded', 'false');
        if (self.S(this).hasClass(self.settings.active_class)) {
          self.S(this)
            .css(Foundation.rtl ? 'right':'left', '-99999px')
            .attr('aria-hidden', 'true')
            .removeClass(self.settings.active_class)
            .prev('[' + self.attr_name() + ']')
            .removeClass(self.settings.active_class)
            .removeData('target');

          self.S(this).trigger('closed').trigger('closed.fndtn.dropdown', [dropdown]);
        }
      });
      dropdown.removeClass('f-open-' + this.attr_name(true));
    },

    closeall: function() {
      var self = this;
      $.each(self.S('.f-open-' + this.attr_name(true)), function() {
        self.close.call(self, self.S(this));
      });
    },

    open: function (dropdown, target) {
      this
        .css(dropdown
        .addClass(this.settings.active_class), target);
      dropdown.prev('[' + this.attr_name() + ']').addClass(this.settings.active_class);
      dropdown.data('target', target.get(0)).trigger('opened').trigger('opened.fndtn.dropdown', [dropdown, target]);
      dropdown.attr('aria-hidden', 'false');
      target.attr('aria-expanded', 'true');
      dropdown.focus();
      dropdown.addClass('f-open-' + this.attr_name(true));
    },

    data_attr: function () {
      if (this.namespace.length > 0) {
        return this.namespace + '-' + this.name;
      }

      return this.name;
    },

    toggle : function (target) {
      if (target.hasClass(this.settings.disabled_class)) {
        return;
      }
      var dropdown = this.S('#' + target.data(this.data_attr()));
      if (dropdown.length === 0) {
        // No dropdown found, not continuing
        return;
      }

      this.close.call(this, this.S('[' + this.attr_name() + '-content]').not(dropdown));

      if (dropdown.hasClass(this.settings.active_class)) {
        this.close.call(this, dropdown);
        if (dropdown.data('target') !== target.get(0))
          this.open.call(this, dropdown, target);
      } else {
        this.open.call(this, dropdown, target);
      }
    },

    resize : function () {
      var dropdown = this.S('[' + this.attr_name() + '-content].open'),
          target = this.S('[' + this.attr_name() + '="' + dropdown.attr('id') + '"]');

      if (dropdown.length && target.length) {
        this.css(dropdown, target);
      }
    },

    css : function (dropdown, target) {
      var left_offset = Math.max((target.width() - dropdown.width()) / 2, 8),
          settings = target.data(this.attr_name(true) + '-init') || this.settings;

      this.clear_idx();

      if (this.small()) {
        var p = this.dirs.bottom.call(dropdown, target, settings);

        dropdown.attr('style', '').removeClass('drop-left drop-right drop-top').css({
          position : 'absolute',
          width: '95%',
          'max-width': 'none',
          top: p.top
        });

        dropdown.css(Foundation.rtl ? 'right':'left', left_offset);
      } else {

        this.style(dropdown, target, settings);
      }

      return dropdown;
    },

    style : function (dropdown, target, settings) {
      var css = $.extend({position: 'absolute'},
        this.dirs[settings.align].call(dropdown, target, settings));

      dropdown.attr('style', '').css(css);
    },

    // return CSS property object
    // `this` is the dropdown
    dirs : {
      // Calculate target offset
      _base : function (t) {
        var o_p = this.offsetParent(),
            o = o_p.offset(),
            p = t.offset();

        p.top -= o.top;
        p.left -= o.left;

        //set some flags on the p object to pass along
        p.missRight = false;
        p.missTop = false;
        p.missLeft = false;
        p.leftRightFlag = false;

        //lets see if the panel will be off the screen
        //get the actual width of the page and store it
        var actualBodyWidth;
        if (document.getElementsByClassName('row')[0]) {
          actualBodyWidth = document.getElementsByClassName('row')[0].clientWidth;
        } else {
          actualBodyWidth = window.outerWidth;
        }

        var actualMarginWidth = (window.outerWidth - actualBodyWidth) / 2;
        var actualBoundary = actualBodyWidth;

        if (!this.hasClass('mega')) {
          //miss top
          if (t.offset().top <= this.outerHeight()) {
            p.missTop = true;
            actualBoundary = window.outerWidth - actualMarginWidth;
            p.leftRightFlag = true;
          }

          //miss right
          if (t.offset().left + this.outerWidth() > t.offset().left + actualMarginWidth && t.offset().left - actualMarginWidth > this.outerWidth()) {
            p.missRight = true;
            p.missLeft = false;
          }

          //miss left
          if (t.offset().left - this.outerWidth() <= 0) {
            p.missLeft = true;
            p.missRight = false;
          }
        }

        return p;
      },

      top: function (t, s) {
        var self = Foundation.libs.dropdown,
            p = self.dirs._base.call(this, t);

        this.addClass('drop-top');

        if (p.missTop == true) {
          p.top = p.top + t.outerHeight() + this.outerHeight();
          this.removeClass('drop-top');
        }

        if (p.missRight == true) {
          p.left = p.left - this.outerWidth() + t.outerWidth();
        }

        if (t.outerWidth() < this.outerWidth() || self.small() || this.hasClass(s.mega_menu)) {
          self.adjust_pip(this,t,s,p);
        }

        if (Foundation.rtl) {
          return {left: p.left - this.outerWidth() + t.outerWidth(),
            top: p.top - this.outerHeight()};
        }

        return {left: p.left, top: p.top - this.outerHeight()};
      },

      bottom: function (t,s) {
        var self = Foundation.libs.dropdown,
            p = self.dirs._base.call(this, t);

        if (p.missRight == true) {
          p.left = p.left - this.outerWidth() + t.outerWidth();
        }

        if (t.outerWidth() < this.outerWidth() || self.small() || this.hasClass(s.mega_menu)) {
          self.adjust_pip(this,t,s,p);
        }

        if (self.rtl) {
          return {left: p.left - this.outerWidth() + t.outerWidth(), top: p.top + t.outerHeight()};
        }

        return {left: p.left, top: p.top + t.outerHeight()};
      },

      left: function (t, s) {
        var p = Foundation.libs.dropdown.dirs._base.call(this, t);

        this.addClass('drop-left');

        if (p.missLeft == true) {
          p.left =  p.left + this.outerWidth();
          p.top = p.top + t.outerHeight();
          this.removeClass('drop-left');
        }

        return {left: p.left - this.outerWidth(), top: p.top};
      },

      right: function (t, s) {
        var p = Foundation.libs.dropdown.dirs._base.call(this, t);

        this.addClass('drop-right');

        if (p.missRight == true) {
          p.left = p.left - this.outerWidth();
          p.top = p.top + t.outerHeight();
          this.removeClass('drop-right');
        } else {
          p.triggeredRight = true;
        }

        var self = Foundation.libs.dropdown;

        if (t.outerWidth() < this.outerWidth() || self.small() || this.hasClass(s.mega_menu)) {
          self.adjust_pip(this,t,s,p);
        }

        return {left: p.left + t.outerWidth(), top: p.top};
      }
    },

    // Insert rule to style psuedo elements
    adjust_pip : function (dropdown,target,settings,position) {
      var sheet = Foundation.stylesheet,
          pip_offset_base = 8;

      if (dropdown.hasClass(settings.mega_class)) {
        pip_offset_base = position.left + (target.outerWidth()/2) - 8;
      }
      else if (this.small()) {
        pip_offset_base += position.left - 8;
      }

      this.rule_idx = sheet.cssRules.length;

      //default
      var sel_before = '.f-dropdown.open:before',
          sel_after  = '.f-dropdown.open:after',
          css_before = 'left: ' + pip_offset_base + 'px;',
          css_after  = 'left: ' + (pip_offset_base - 1) + 'px;';

      if (position.missRight == true) {
        pip_offset_base = dropdown.outerWidth() - 23;
        sel_before = '.f-dropdown.open:before',
        sel_after  = '.f-dropdown.open:after',
        css_before = 'left: ' + pip_offset_base + 'px;',
        css_after  = 'left: ' + (pip_offset_base - 1) + 'px;';
      }

      //just a case where right is fired, but its not missing right
      if (position.triggeredRight == true) {
        sel_before = '.f-dropdown.open:before',
        sel_after  = '.f-dropdown.open:after',
        css_before = 'left:-12px;',
        css_after  = 'left:-14px;';
      }

      if (sheet.insertRule) {
        sheet.insertRule([sel_before, '{', css_before, '}'].join(' '), this.rule_idx);
        sheet.insertRule([sel_after, '{', css_after, '}'].join(' '), this.rule_idx + 1);
      } else {
        sheet.addRule(sel_before, css_before, this.rule_idx);
        sheet.addRule(sel_after, css_after, this.rule_idx + 1);
      }
    },

    // Remove old dropdown rule index
    clear_idx : function () {
      var sheet = Foundation.stylesheet;

      if (typeof this.rule_idx !== 'undefined') {
        sheet.deleteRule(this.rule_idx);
        sheet.deleteRule(this.rule_idx);
        delete this.rule_idx;
      }
    },

    small : function () {
      return matchMedia(Foundation.media_queries.small).matches &&
        !matchMedia(Foundation.media_queries.medium).matches;
    },

    off: function () {
      this.S(this.scope).off('.fndtn.dropdown');
      this.S('html, body').off('.fndtn.dropdown');
      this.S(window).off('.fndtn.dropdown');
      this.S('[data-dropdown-content]').off('.fndtn.dropdown');
    },

    reflow : function () {}
  };
}(jQuery, window, window.document));
<|endoftext|>


<commit_before>/* eslint-disable  no-var */
/* eslint-disable  vars-on-top */
/* eslint-disable  no-use-before-define */
function klaroOnload() {
  var klaro = document.querySelector('#klaro div.klaro')
  function noVisibleKlaro() {
    return !klaro || window.getComputedStyle(klaro).display === 'none' || klaro.querySelector('a') === null
  }
  if (noVisibleKlaro()) {
    return
  }
  if (klaro !== document.body.firstElementChild) {
    document.body.insertBefore(klaro, document.body.firstElementChild)
  }

  const getNextFocusableElement = function _getNextFocusableElement(element, step) {
    var elements = Array.prototype.slice.call(
      klaro.querySelectorAll('a:not([disabled]), button:not([disabled]), input:not([disabled])')
    )
    if (!elements) return null

    elements = elements.filter((e) => window.getComputedStyle(e).display !== 'none')
    for (var i = 0; i < elements.length; i++) {
      if (elements[i] === element) {
        if (i < elements.length - step) {
          return elements[i + step]
        }
        return elements[0]
      }
    }
    return elements[0]
  }

  const resetActiveLI = function _resetActiveLI() {
    var elements = Array.prototype.slice.call(klaro.querySelectorAll('li.active'))
    if (!elements) return
    elements.forEach((e) => e.classList.remove('active'))
  }
  // eslint-disable-next-line no-shadow
  const handleKeydown = (event) => {
    if (noVisibleKlaro()) {
      window.removeEventListener('keydown', handleKeydown)
      return
    }
    if (event.key === 'Tab') {
      const nextElement = getNextFocusableElement(event.target, event.shiftKey ? -1 : 1)
      if (nextElement) {
        event.preventDefault()
        nextElement.focus()
        resetActiveLI()
        if (nextElement.tagName === 'INPUT') {
          nextElement.parentElement.closest('li').classList.add('active')
        }
      }
    } else if (event.key === 'Escape') {
      const closeButton = klaro.querySelector('div.klaro .cookie-modal button.hide')
      if (closeButton) {
        closeButton.click()
      }
    }
  }
  window.addEventListener('keydown', handleKeydown)

  const config = { childList: true, subtree: true, attributes: true }
  const callback = function _callback(mutationsList) {
    for (const mutation of mutationsList) {
      if (mutation.type === 'childList') {
        if (mutation.addedNodes.length > 0) {
          for (const node of mutation.addedNodes) {
            if (node.nodeType === 1) {
              if (node.classList.contains('cookie-modal')) {
                var inputs = Array.prototype.slice.call(node.querySelectorAll('input'))
                for (const input of inputs) {
                  input.setAttribute('tabindex', '0')
                }
              }
            }
          }
        } else if (mutation.removedNodes.length > 0) {
          for (const node of mutation.removedNodes) {
            if (node.nodeType === 1) {
              if (node.classList.contains('cn-body') || node.classList.contains('cookie-modal')) {
                window.removeEventListener('keydown', handleKeydown)
              }
            }
          }
        }
      } else if (mutation.attributeName === 'style') {
        if (window.getComputedStyle(klaro).getPropertyValue('display') === 'none') {
          window.removeEventListener('keydown', handleKeydown)
        }
      }
    }
  }

  const observer = new MutationObserver(callback)
  observer.observe(klaro, config)
}

window.addEventListener('load', () => {
  klaroOnload()
})

window.addEventListener('load', () => {
  // If no mobile menu containers are present, skip it all togehter
  if (
    !document.getElementById('mobileMenuWrapper') ||
    !document.getElementById('mobileMenuList') ||
    !document.getElementById('mobileMenuContent') ||
    !document.getElementById('nav-icon')
  ) {
    return
  }
  var mainMenu = document.getElementById('mainMenu') // Get main menu
  var megaMenu = document.getElementById('megaMenu') // Get mega menu
  var secondaryMenu = document.querySelector('.secondaryMenu') // Get secondary menu

  if (!mainMenu && !megaMenu && !secondaryMenu) {
    document.getElementById('mobileMenuWrapper').style.display = 'none' // Hide mobile menu wrapper/container if there is no menus
  }

  if (mainMenu) {
    // For main menu items, copy a link if it exists, otherwise copy the link content
    createMobileMenuItems(mainMenu.querySelectorAll('li'), true)
  } else if (megaMenu) {
    // For mega menu items, copy only the the first alinks
    createMobileMenuItems(megaMenu.querySelectorAll('li'), false)
  }
  if (secondaryMenu) {
    createMobileMenuItemsForSecondaryMenu(secondaryMenu.querySelectorAll('li a'))
  }

  function createMobileMenuItems(menuItems, includeInline) {
    for (var i = 0; i < menuItems.length; i++) {
      var linkToCopy = menuItems[i].querySelector('a')
      if (linkToCopy || includeInline) {
        var listItem = document.createElement('li')
        listItem.className = menuItems[i].className !== '' ? menuItems[i].className : 'nav-item'
        if (linkToCopy) {
          var link = document.createElement('a')
          link.href = linkToCopy.href
          link.text = linkToCopy.text
          link.className = linkToCopy.className
          listItem.appendChild(link)
        } else if (includeInline) {
          listItem.innerHTML = menuItems[i].innerHTML
        }
        document.getElementById('mobileMenuList').appendChild(listItem)
      }
    }
  }

  function createMobileMenuItemsForSecondaryMenu(menuItems) {
    var secondaryMenuWrapper = document.createElement('li')
    secondaryMenuWrapper.className = 'secondaryMenuWrap'
    var secondaryMenuContainer = document.createElement('ul')
    secondaryMenuContainer.className = 'secondaryMenuContainer'

    for (var i = 0; i < menuItems.length; i++) {
      var link = document.createElement('a')
      link.className = 'secondaryMenu'
      link.href = menuItems[i].href
      link.text = menuItems[i].text
      if (menuItems[i].hreflang) {
        link.hreflang = menuItems[i].hreflang
      }
      var secondaryMenuListItem = document.createElement('li')
      secondaryMenuListItem.className = 'item nav-item secondaryItem'

      secondaryMenuListItem.appendChild(link)
      secondaryMenuContainer.appendChild(secondaryMenuListItem)
    }

    secondaryMenuWrapper.appendChild(secondaryMenuContainer)
    document.getElementById('mobileMenuList').appendChild(secondaryMenuWrapper)
  }

  // Mega menu icon toggler.
  document.getElementById('nav-icon').onclick = function _onclick() {
    if (document.body.style.overflow !== 'hidden') {
      document.body.style.overflow = 'hidden'
    } else {
      document.body.style.overflow = 'initial'
    }

    document.getElementById('mobileMenuContent').classList.toggle('show')
    this.classList.toggle('open')
  }
})

window.addEventListener('load', () => {
  if (!document.querySelector('#mainMenu')) return
  const stickyMainMenu = (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        document.getElementById('mainMenu').classList.add('sticky-menu')
      } else {
        document.getElementById('mainMenu').classList.remove('sticky-menu')
      }
    })
  }
  const options = {
    root: null,
    rootMargin: '0px',
    threshold: 1.0,
  }

  const observer = new IntersectionObserver(stickyMainMenu, options)
  const target = document.querySelector('#mainMenu ul.nav.nav-list:last-of-type')
  if (target) {
    observer.observe(target)
  }
})
<commit_msg>Different approach to handle long sticky menu, intersection observer was triggered also when scrolling causing too much flickering<commit_after>/* eslint-disable  no-var */
/* eslint-disable  vars-on-top */
/* eslint-disable  no-use-before-define */
function klaroOnload() {
  var klaro = document.querySelector('#klaro div.klaro')
  function noVisibleKlaro() {
    return !klaro || window.getComputedStyle(klaro).display === 'none' || klaro.querySelector('a') === null
  }
  if (noVisibleKlaro()) {
    return
  }
  if (klaro !== document.body.firstElementChild) {
    document.body.insertBefore(klaro, document.body.firstElementChild)
  }

  const getNextFocusableElement = function _getNextFocusableElement(element, step) {
    var elements = Array.prototype.slice.call(
      klaro.querySelectorAll('a:not([disabled]), button:not([disabled]), input:not([disabled])')
    )
    if (!elements) return null

    elements = elements.filter((e) => window.getComputedStyle(e).display !== 'none')
    for (var i = 0; i < elements.length; i++) {
      if (elements[i] === element) {
        if (i < elements.length - step) {
          return elements[i + step]
        }
        return elements[0]
      }
    }
    return elements[0]
  }

  const resetActiveLI = function _resetActiveLI() {
    var elements = Array.prototype.slice.call(klaro.querySelectorAll('li.active'))
    if (!elements) return
    elements.forEach((e) => e.classList.remove('active'))
  }
  // eslint-disable-next-line no-shadow
  const handleKeydown = (event) => {
    if (noVisibleKlaro()) {
      window.removeEventListener('keydown', handleKeydown)
      return
    }
    if (event.key === 'Tab') {
      const nextElement = getNextFocusableElement(event.target, event.shiftKey ? -1 : 1)
      if (nextElement) {
        event.preventDefault()
        nextElement.focus()
        resetActiveLI()
        if (nextElement.tagName === 'INPUT') {
          nextElement.parentElement.closest('li').classList.add('active')
        }
      }
    } else if (event.key === 'Escape') {
      const closeButton = klaro.querySelector('div.klaro .cookie-modal button.hide')
      if (closeButton) {
        closeButton.click()
      }
    }
  }
  window.addEventListener('keydown', handleKeydown)

  const config = { childList: true, subtree: true, attributes: true }
  const callback = function _callback(mutationsList) {
    for (const mutation of mutationsList) {
      if (mutation.type === 'childList') {
        if (mutation.addedNodes.length > 0) {
          for (const node of mutation.addedNodes) {
            if (node.nodeType === 1) {
              if (node.classList.contains('cookie-modal')) {
                var inputs = Array.prototype.slice.call(node.querySelectorAll('input'))
                for (const input of inputs) {
                  input.setAttribute('tabindex', '0')
                }
              }
            }
          }
        } else if (mutation.removedNodes.length > 0) {
          for (const node of mutation.removedNodes) {
            if (node.nodeType === 1) {
              if (node.classList.contains('cn-body') || node.classList.contains('cookie-modal')) {
                window.removeEventListener('keydown', handleKeydown)
              }
            }
          }
        }
      } else if (mutation.attributeName === 'style') {
        if (window.getComputedStyle(klaro).getPropertyValue('display') === 'none') {
          window.removeEventListener('keydown', handleKeydown)
        }
      }
    }
  }

  const observer = new MutationObserver(callback)
  observer.observe(klaro, config)
}

window.addEventListener('load', () => {
  klaroOnload()
})

window.addEventListener('load', () => {
  // If no mobile menu containers are present, skip it all togehter
  if (
    !document.getElementById('mobileMenuWrapper') ||
    !document.getElementById('mobileMenuList') ||
    !document.getElementById('mobileMenuContent') ||
    !document.getElementById('nav-icon')
  ) {
    return
  }
  var mainMenu = document.getElementById('mainMenu') // Get main menu
  var megaMenu = document.getElementById('megaMenu') // Get mega menu
  var secondaryMenu = document.querySelector('.secondaryMenu') // Get secondary menu

  if (!mainMenu && !megaMenu && !secondaryMenu) {
    document.getElementById('mobileMenuWrapper').style.display = 'none' // Hide mobile menu wrapper/container if there is no menus
  }

  if (mainMenu) {
    // For main menu items, copy a link if it exists, otherwise copy the link content
    createMobileMenuItems(mainMenu.querySelectorAll('li'), true)
  } else if (megaMenu) {
    // For mega menu items, copy only the the first alinks
    createMobileMenuItems(megaMenu.querySelectorAll('li'), false)
  }
  if (secondaryMenu) {
    createMobileMenuItemsForSecondaryMenu(secondaryMenu.querySelectorAll('li a'))
  }

  function createMobileMenuItems(menuItems, includeInline) {
    for (var i = 0; i < menuItems.length; i++) {
      var linkToCopy = menuItems[i].querySelector('a')
      if (linkToCopy || includeInline) {
        var listItem = document.createElement('li')
        listItem.className = menuItems[i].className !== '' ? menuItems[i].className : 'nav-item'
        if (linkToCopy) {
          var link = document.createElement('a')
          link.href = linkToCopy.href
          link.text = linkToCopy.text
          link.className = linkToCopy.className
          listItem.appendChild(link)
        } else if (includeInline) {
          listItem.innerHTML = menuItems[i].innerHTML
        }
        document.getElementById('mobileMenuList').appendChild(listItem)
      }
    }
  }

  function createMobileMenuItemsForSecondaryMenu(menuItems) {
    var secondaryMenuWrapper = document.createElement('li')
    secondaryMenuWrapper.className = 'secondaryMenuWrap'
    var secondaryMenuContainer = document.createElement('ul')
    secondaryMenuContainer.className = 'secondaryMenuContainer'

    for (var i = 0; i < menuItems.length; i++) {
      var link = document.createElement('a')
      link.className = 'secondaryMenu'
      link.href = menuItems[i].href
      link.text = menuItems[i].text
      if (menuItems[i].hreflang) {
        link.hreflang = menuItems[i].hreflang
      }
      var secondaryMenuListItem = document.createElement('li')
      secondaryMenuListItem.className = 'item nav-item secondaryItem'

      secondaryMenuListItem.appendChild(link)
      secondaryMenuContainer.appendChild(secondaryMenuListItem)
    }

    secondaryMenuWrapper.appendChild(secondaryMenuContainer)
    document.getElementById('mobileMenuList').appendChild(secondaryMenuWrapper)
  }

  // Mega menu icon toggler.
  document.getElementById('nav-icon').onclick = function _onclick() {
    if (document.body.style.overflow !== 'hidden') {
      document.body.style.overflow = 'hidden'
    } else {
      document.body.style.overflow = 'initial'
    }

    document.getElementById('mobileMenuContent').classList.toggle('show')
    this.classList.toggle('open')
  }
})

function stickyMainMenu() {
  if (!document.querySelector('#mainMenu')) return

  function isInViewport(element) {
    const rect = element.getBoundingClientRect()

    return (
      rect.top >= 0 &&
      rect.left >= 0 &&
      rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
      rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    )
  }

  if (isInViewport(document.querySelector('#mainMenu ul.nav.nav-list:last-of-type')?.lastElementChild)) {
    document.querySelector('#mainMenu').classList.add('sticky-menu')
  } else {
    document.querySelector('#mainMenu').classList.remove('sticky-menu')
  }
}
window.addEventListener('load', stickyMainMenu)
window.addEventListener('resize', stickyMainMenu)
<|endoftext|>


<commit_before>'use strict';

var viewport = $( "#viewport" );
var viewimg = $( "#canvas" );
var filenameDiv = $( "#filename" );
var canvas = viewimg[0];
var photoIndex = 0;
var rotation = 0;
var rotationChange = 0;
var photoImg = new Image();
var flipHorz = false;
var flipVert = false;
var modalOn = false;

function render() {
	while ( rotation < 0 ) {
		rotation += 4;
	}
	rotation %= 4;
	var sideways = ( rotation % 2 ) === 1;
	var ctx = canvas.getContext("2d");
	ctx.clearRect( 0, 0, canvas.width, canvas.height );
	canvas.height = sideways ? photoImg.width : photoImg.height;
	canvas.width = sideways ? photoImg.height : photoImg.width;
	ctx = canvas.getContext("2d");
	ctx.save();
	ctx.translate(canvas.width/2, canvas.height/2);
	if ( flipHorz || flipVert ) {
		ctx.scale( flipHorz ? -1 : 1, flipVert ? -1 : 1 );
	}
	ctx.rotate( rotation * ( Math.PI / 2 ) );
	ctx.drawImage(photoImg, -photoImg.width/2, -photoImg.height/2);
	ctx.restore();
	viewimg.show();
	set_body_height();
}

function setPhotoData( data ) {
	console.log( "setPhotoData" );
	photoImg.onload = function() {
		console.log( "setPhotoData.onload" );
		rotation = data.imageMeta.rotation;
		rotationChange = 0;
		flipHorz = false;
		flipVert = false;
		switch ( data.image.orientation ) {
			case 2:
				flipHorz = true;
				break;
			case 3: // upside down
				rotation += 2;
				break;
			case 4:
				rotation += 2;
				flipHorz = true;
				break;
			case 5:
				rotation += 3;  // 90* anticlockwise
				flipVert = true;
				break;
			case 6:
				rotation += 3;
				break;
			case 7:
				rotation += 1; // 90* clockwise - needs to be rotated 270* to correct
				flipVert = true;
				break;
			case 8:
				rotation += 1;
				break;
		}
		render();
		hideLoadingDialog();
	};
	photoImg.src = data.image.src;
}

function setFilename( data ) {
	filenameDiv[0].innerHTML = data.imageMeta.path;
}

function showLoadingDialog() {
	$( "#dialog-loading" ).dialog( {
		closeOnEscape: false,
		height: 95,
		close: function( event, ui ) {
			modalOn = false;
		},
		open: function(event, ui) {
			modalOn = true;
			$(".ui-dialog-titlebar-close", ui.dialog | ui).hide();
			$( "#progressbar" ).progressbar({
				value: false
			});
		}
	} );
}

function hideLoadingDialog() {
	$( "#dialog-loading" ).dialog( "close" );
}

function loadNextPhoto( next, deleted ) {
	console.log( "loadNextPhoto" );
	var previousPhotoData;
	var nextIndex = 0;
	if ( next !== undefined ) {
		nextIndex = photoIndex + ( next ? 1 : -1 );
	}
	$.getJSON( '/next', { next: { index: nextIndex }, previous: { index: photoIndex, rotationChange: rotationChange, deleted: deleted } }, function( data ) {
		setPhotoData( data );
		setFilename( data );
		photoIndex = nextIndex;
	} );
	showLoadingDialog();
}

function set_body_height() { // set body height = window height
	console.log( "set_body_height" );
	viewport.height($(window).height());
	console.log( "canvas w=" + canvas.width + " h=" + canvas.height );
	// set aspect ratio of canvas
	if ( canvas.width > canvas.height ) {
		canvas.style.width = '100%';
		var ar = canvas.height / canvas.width;
		canvas.style.height = Math.floor( ar * 100 ) + '%';
		console.log( "ar=" + ar );
	} else {
		canvas.style.height = '100%';
                var ar = canvas.width / canvas.height;
                canvas.style.width = Math.floor( ar * 100 ) + '%';
		console.log( "ar=" + ar );
	}
}

function inc( base, diff ) {
	base += diff;
	if ( base > 3 ) {
		base = 0;
	}
	if ( base < 0 ) {
		base = 3;
	}
	return base;
}

function rotate( clockwise ) {
	var diff = clockwise ? 1 : -1;
	rotation = inc( rotation, diff );
	rotationChange = inc( rotationChange, diff );
	render();
}

function deletePicture() {
	modalOn = true;
	$( "#dialog-confirm" ).dialog({
		resizable: false,
		height: 200,
		modal: true,
		close: function( event, ui ) {
			modalOn = false;
		},
		buttons: {
			"Delete picture": function() {
				loadNextPhoto( true, true );
				$( this ).dialog( "close" );
			},
			Cancel: function() {
				$( this ).dialog( "close" );
			}
		}
	});
}

function doCommit() {
	showLoadingDialog();
	$.getJSON( '/commit', {}, function() {
		hideLoadingDialog();
	} );
}

function doCommitConfirmation() {
	modalOn = true;
	$( "#dialog-confirm-commit" ).dialog({
		resizable: false,
		height: 200,
		modal: true,
		close: function( event, ui ) {
			modalOn = false;
		},
		buttons: {
			"Commit": function() {
				doCommit();
				$( this ).dialog( "close" );
			},
			Cancel: function() {
				$( this ).dialog( "close" );
			}
		}
	});
}

function onKeyPress( evt ) {
	console.log( evt.which );
	console.log( "onKeyPress" );
	$( "#lastkeypress" ).text( evt.which.toString() );
	console.log( "modalOn", modalOn );
	if ( !modalOn ) {
		// key presses only allowed when a modal dialog is not displayed
		switch ( evt.which ) {
			case 37: // left
				loadNextPhoto( false );
				break;
			case 13: // enter
			case 39: // right
				loadNextPhoto( true );
				break;
			case 40: // down
				rotate( true );
				break;
			case 38: // up
				rotate( false );
				break;
			case 46: // delete
				deletePicture();
				break;
			case 67: // C
				doCommitConfirmation();
				break;
		}
	}
	return true;
}

window.onload = function() {
	console.log( "window.onload" );
	var win = $(window);
	win.bind('resize', set_body_height);
	win.keydown( onKeyPress );
	loadNextPhoto();
	set_body_height();
};

<commit_msg>Fixed aspect ratio<commit_after>'use strict';

var viewport = $( "#viewport" );
var viewimg = $( "#canvas" );
var filenameDiv = $( "#filename" );
var canvas = viewimg[0];
var photoIndex = 0;
var rotation = 0;
var rotationChange = 0;
var photoImg = new Image();
var flipHorz = false;
var flipVert = false;
var modalOn = false;

function render() {
	while ( rotation < 0 ) {
		rotation += 4;
	}
	rotation %= 4;
	var sideways = ( rotation % 2 ) === 1;
	var ctx = canvas.getContext("2d");
	ctx.clearRect( 0, 0, canvas.width, canvas.height );
	canvas.height = sideways ? photoImg.width : photoImg.height;
	canvas.width = sideways ? photoImg.height : photoImg.width;
	ctx = canvas.getContext("2d");
	ctx.save();
	ctx.translate(canvas.width/2, canvas.height/2);
	if ( flipHorz || flipVert ) {
		ctx.scale( flipHorz ? -1 : 1, flipVert ? -1 : 1 );
	}
	ctx.rotate( rotation * ( Math.PI / 2 ) );
	ctx.drawImage(photoImg, -photoImg.width/2, -photoImg.height/2);
	ctx.restore();
	viewimg.show();
	set_body_height();
}

function setPhotoData( data ) {
	console.log( "setPhotoData" );
	photoImg.onload = function() {
		console.log( "setPhotoData.onload" );
		rotation = data.imageMeta.rotation;
		rotationChange = 0;
		flipHorz = false;
		flipVert = false;
		switch ( data.image.orientation ) {
			case 2:
				flipHorz = true;
				break;
			case 3: // upside down
				rotation += 2;
				break;
			case 4:
				rotation += 2;
				flipHorz = true;
				break;
			case 5:
				rotation += 3;  // 90* anticlockwise
				flipVert = true;
				break;
			case 6:
				rotation += 3;
				break;
			case 7:
				rotation += 1; // 90* clockwise - needs to be rotated 270* to correct
				flipVert = true;
				break;
			case 8:
				rotation += 1;
				break;
		}
		render();
		hideLoadingDialog();
	};
	photoImg.src = data.image.src;
}

function setFilename( data ) {
	filenameDiv[0].innerHTML = data.imageMeta.path;
}

function showLoadingDialog() {
	$( "#dialog-loading" ).dialog( {
		closeOnEscape: false,
		height: 95,
		close: function( event, ui ) {
			modalOn = false;
		},
		open: function(event, ui) {
			modalOn = true;
			$(".ui-dialog-titlebar-close", ui.dialog | ui).hide();
			$( "#progressbar" ).progressbar({
				value: false
			});
		}
	} );
}

function hideLoadingDialog() {
	$( "#dialog-loading" ).dialog( "close" );
}

function loadNextPhoto( next, deleted ) {
	console.log( "loadNextPhoto" );
	var previousPhotoData;
	var nextIndex = 0;
	if ( next !== undefined ) {
		nextIndex = photoIndex + ( next ? 1 : -1 );
	}
	$.getJSON( '/next', { next: { index: nextIndex }, previous: { index: photoIndex, rotationChange: rotationChange, deleted: deleted } }, function( data ) {
		setPhotoData( data );
		setFilename( data );
		photoIndex = nextIndex;
	} );
	showLoadingDialog();
}

function set_body_height() { // set body height = window height
	console.log( "set_body_height" );
	viewport.height($(window).height());
	console.log( "canvas w=" + canvas.width + " h=" + canvas.height );
	var canvasAr = canvas.width / canvas.height;
	var windowAr = viewport.innerWidth() / viewport.innerHeight();
	// set aspect ratio of canvas
	if ( canvasAr > windowAr ) {
		canvas.style.width = viewport.innerWidth() + 'px';
		var ar = canvas.height / canvas.width;
		canvas.style.height = Math.floor( viewport.innerWidth() * ar ) + 'px';
		console.log( "ar=" + ar );
	} else {
		canvas.style.height = viewport.innerHeight() + 'px';
                var ar = canvas.width / canvas.height;
                canvas.style.width = Math.floor( viewport.innerHeight() * ar ) + 'px';
		console.log( "ar=" + ar );
	}
}

function inc( base, diff ) {
	base += diff;
	if ( base > 3 ) {
		base = 0;
	}
	if ( base < 0 ) {
		base = 3;
	}
	return base;
}

function rotate( clockwise ) {
	var diff = clockwise ? 1 : -1;
	rotation = inc( rotation, diff );
	rotationChange = inc( rotationChange, diff );
	render();
}

function deletePicture() {
	modalOn = true;
	$( "#dialog-confirm" ).dialog({
		resizable: false,
		height: 200,
		modal: true,
		close: function( event, ui ) {
			modalOn = false;
		},
		buttons: {
			"Delete picture": function() {
				loadNextPhoto( true, true );
				$( this ).dialog( "close" );
			},
			Cancel: function() {
				$( this ).dialog( "close" );
			}
		}
	});
}

function doCommit() {
	showLoadingDialog();
	$.getJSON( '/commit', {}, function() {
		hideLoadingDialog();
	} );
}

function doCommitConfirmation() {
	modalOn = true;
	$( "#dialog-confirm-commit" ).dialog({
		resizable: false,
		height: 200,
		modal: true,
		close: function( event, ui ) {
			modalOn = false;
		},
		buttons: {
			"Commit": function() {
				doCommit();
				$( this ).dialog( "close" );
			},
			Cancel: function() {
				$( this ).dialog( "close" );
			}
		}
	});
}

function onKeyPress( evt ) {
	console.log( evt.which );
	console.log( "onKeyPress" );
	$( "#lastkeypress" ).text( evt.which.toString() );
	console.log( "modalOn", modalOn );
	if ( !modalOn ) {
		// key presses only allowed when a modal dialog is not displayed
		switch ( evt.which ) {
			case 37: // left
				loadNextPhoto( false );
				break;
			case 13: // enter
			case 39: // right
				loadNextPhoto( true );
				break;
			case 40: // down
				rotate( true );
				break;
			case 38: // up
				rotate( false );
				break;
			case 46: // delete
				deletePicture();
				break;
			case 67: // C
				doCommitConfirmation();
				break;
		}
	}
	return true;
}

window.onload = function() {
	console.log( "window.onload" );
	var win = $(window);
	win.bind('resize', set_body_height);
	win.keydown( onKeyPress );
	loadNextPhoto();
	set_body_height();
};

<|endoftext|>


<commit_before>/* global $, config, interfaceConfig, APP */

/* eslint-disable no-unused-vars */
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
import { VideoTrack } from '../../../react/features/base/media';
import {
    getAvatarURLByParticipantId
} from '../../../react/features/base/participants';
import { updateSettings } from '../../../react/features/base/settings';
import { shouldDisplayTileView } from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */

const logger = require('jitsi-meet-logger').getLogger(__filename);

import UIEvents from '../../../service/UI/UIEvents';
import SmallVideo from './SmallVideo';

/**
 *
 */
function LocalVideo(VideoLayout, emitter, streamEndedCallback) {
    this.videoSpanId = 'localVideoContainer';
    this.streamEndedCallback = streamEndedCallback;
    this.container = this.createContainer();
    this.$container = $(this.container);
    this.updateDOMLocation();

    this.localVideoId = null;
    this.bindHoverHandler();
    if (config.enableLocalVideoFlip) {
        this._buildContextMenu();
    }
    this.isLocal = true;
    this.emitter = emitter;
    this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
        ? 'left top' : 'top center';

    Object.defineProperty(this, 'id', {
        get() {
            return APP.conference.getMyUserId();
        }
    });
    this.initBrowserSpecificProperties();

    SmallVideo.call(this, VideoLayout);

    // Set default display name.
    this.setDisplayName();

    // Initialize the avatar display with an avatar url selected from the redux
    // state. Redux stores the local user with a hardcoded participant id of
    // 'local' if no id has been assigned yet.
    this.avatarChanged(
        getAvatarURLByParticipantId(APP.store.getState(), this.id));

    this.addAudioLevelIndicator();
    this.updateIndicators();

    this.container.onclick = this._onContainerClick.bind(this);
}

LocalVideo.prototype = Object.create(SmallVideo.prototype);
LocalVideo.prototype.constructor = LocalVideo;

LocalVideo.prototype.createContainer = function() {
    const containerSpan = document.createElement('span');

    containerSpan.classList.add('videocontainer');
    containerSpan.id = this.videoSpanId;

    containerSpan.innerHTML = `

`;

    return containerSpan;
};

/**
 * Sets the display name for the given video span id.
 */
LocalVideo.prototype.setDisplayName = function(displayName) {
    if (!this.container) {
        logger.warn(
                `Unable to set displayName - ${this.videoSpanId
                } does not exist`);

        return;
    }

    this.updateDisplayName({
        allowEditing: APP.store.getState()['features/base/jwt'].isGuest,
        displayName,
        displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
        elementID: 'localDisplayName',
        participantID: this.id
    });
};

LocalVideo.prototype.changeVideo = function(stream) {
    this.videoStream = stream;

    this.localVideoId = `localVideo_${stream.getId()}`;

    this._updateVideoElement();

    // eslint-disable-next-line eqeqeq
    const isVideo = stream.videoType != 'desktop';
    const settings = APP.store.getState()['features/base/settings'];

    this._enableDisableContextMenu(isVideo);
    this.setFlipX(isVideo ? settings.localFlipX : false);

    const endedHandler = () => {
        const localVideoContainer
            = document.getElementById('localVideoWrapper');

        // Only remove if there is no video and not a transition state.
        // Previous non-react logic created a new video element with each track
        // removal whereas react reuses the video component so it could be the
        // stream ended but a new one is being used.
        if (localVideoContainer && this.videoStream.isEnded()) {
            ReactDOM.unmountComponentAtNode(localVideoContainer);
        }

        this._notifyOfStreamEnded();
        stream.off(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
    };

    stream.on(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
};

/**
 * Notify any subscribers of the local video stream ending.
 *
 * @private
 * @returns {void}
 */
LocalVideo.prototype._notifyOfStreamEnded = function() {
    if (this.streamEndedCallback) {
        this.streamEndedCallback(this.id);
    }
};

/**
 * Shows or hides the local video container.
 * @param {boolean} true to make the local video container visible, false
 * otherwise
 */
LocalVideo.prototype.setVisible = function(visible) {

    // We toggle the hidden class as an indication to other interested parties
    // that this container has been hidden on purpose.
    this.$container.toggleClass('hidden');

    // We still show/hide it as we need to overwrite the style property if we
    // want our action to take effect. Toggling the display property through
    // the above css class didn't succeed in overwriting the style.
    if (visible) {
        this.$container.show();
    } else {
        this.$container.hide();
    }
};

/**
 * Sets the flipX state of the video.
 * @param val {boolean} true for flipped otherwise false;
 */
LocalVideo.prototype.setFlipX = function(val) {
    this.emitter.emit(UIEvents.LOCAL_FLIPX_CHANGED, val);
    if (!this.localVideoId) {
        return;
    }
    if (val) {
        this.selectVideoElement().addClass('flipVideoX');
    } else {
        this.selectVideoElement().removeClass('flipVideoX');
    }
};

/**
 * Builds the context menu for the local video.
 */
LocalVideo.prototype._buildContextMenu = function() {
    $.contextMenu({
        selector: `#${this.videoSpanId}`,
        zIndex: 10000,
        items: {
            flip: {
                name: 'Flip',
                callback: () => {
                    const { store } = APP;
                    const val = !store.getState()['features/base/settings']
                    .localFlipX;

                    this.setFlipX(val);
                    store.dispatch(updateSettings({
                        localFlipX: val
                    }));
                }
            }
        },
        events: {
            show(options) {
                options.items.flip.name
                    = APP.translation.generateTranslationHTML(
                        'videothumbnail.flip');
            }
        }
    });
};

/**
 * Enables or disables the context menu for the local video.
 * @param enable {boolean} true for enable, false for disable
 */
LocalVideo.prototype._enableDisableContextMenu = function(enable) {
    if (this.$container.contextMenu) {
        this.$container.contextMenu(enable);
    }
};

/**
 * Places the {@code LocalVideo} in the DOM based on the current video layout.
 *
 * @returns {void}
 */
LocalVideo.prototype.updateDOMLocation = function() {
    if (!this.container) {
        return;
    }

    if (this.container.parentElement) {
        this.container.parentElement.removeChild(this.container);
    }

    const appendTarget = shouldDisplayTileView(APP.store.getState())
        ? document.getElementById('localVideoTileViewContainer')
        : document.getElementById('filmstripLocalVideoThumbnail');

    appendTarget && appendTarget.appendChild(this.container);

    this._updateVideoElement();
};

/**
 * Callback invoked when the thumbnail is clicked. Will directly call
 * VideoLayout to handle thumbnail click if certain elements have not been
 * clicked.
 *
 * @param {MouseEvent} event - The click event to intercept.
 * @private
 * @returns {void}
 */
LocalVideo.prototype._onContainerClick = function(event) {
    // TODO Checking the classes is a workround to allow events to bubble into
    // the DisplayName component if it was clicked. React's synthetic events
    // will fire after jQuery handlers execute, so stop propogation at this
    // point will prevent DisplayName from getting click events. This workaround
    // should be removeable once LocalVideo is a React Component because then
    // the components share the same eventing system.
    const $source = $(event.target || event.srcElement);
    const { classList } = event.target;

    const clickedOnDisplayName
        = $source.parents('.displayNameContainer').length > 0;
    const clickedOnPopover = $source.parents('.popover').length > 0
            || classList.contains('popover');
    const ignoreClick = clickedOnDisplayName || clickedOnPopover;

    if (event.stopPropagation && !ignoreClick) {
        event.stopPropagation();
    }

    if (!ignoreClick) {
        this._togglePin();
    }
};

/**
 * Renders the React Element for displaying video in {@code LocalVideo}.
 *
 */
LocalVideo.prototype._updateVideoElement = function() {
    const localVideoContainer = document.getElementById('localVideoWrapper');

    ReactDOM.render(
        <Provider store = { APP.store }>
            <VideoTrack
                id = 'localVideo_container'
                videoTrack = {{ jitsiTrack: this.videoStream }} />
        </Provider>,
        localVideoContainer
    );

    // Ensure the video gets play() called on it. This may be necessary in the
    // case where the local video container was moved and re-attached, in which
    // case video does not autoplay.
    const video = this.container.querySelector('video');

    video && video.play();
};

export default LocalVideo;
<commit_msg>fix(tile-view): prevent local participant being selected on pin exit<commit_after>/* global $, config, interfaceConfig, APP */

/* eslint-disable no-unused-vars */
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
import { VideoTrack } from '../../../react/features/base/media';
import {
    getAvatarURLByParticipantId
} from '../../../react/features/base/participants';
import { updateSettings } from '../../../react/features/base/settings';
import { getLocalVideoTrack } from '../../../react/features/base/tracks';
import { shouldDisplayTileView } from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */

const logger = require('jitsi-meet-logger').getLogger(__filename);

import UIEvents from '../../../service/UI/UIEvents';
import SmallVideo from './SmallVideo';

/**
 *
 */
function LocalVideo(VideoLayout, emitter, streamEndedCallback) {
    this.videoSpanId = 'localVideoContainer';
    this.streamEndedCallback = streamEndedCallback;
    this.container = this.createContainer();
    this.$container = $(this.container);
    this.updateDOMLocation();

    this.localVideoId = null;
    this.bindHoverHandler();
    if (config.enableLocalVideoFlip) {
        this._buildContextMenu();
    }
    this.isLocal = true;
    this.emitter = emitter;
    this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
        ? 'left top' : 'top center';

    Object.defineProperty(this, 'id', {
        get() {
            return APP.conference.getMyUserId();
        }
    });
    this.initBrowserSpecificProperties();

    SmallVideo.call(this, VideoLayout);

    // Set default display name.
    this.setDisplayName();

    // Initialize the avatar display with an avatar url selected from the redux
    // state. Redux stores the local user with a hardcoded participant id of
    // 'local' if no id has been assigned yet.
    this.avatarChanged(
        getAvatarURLByParticipantId(APP.store.getState(), this.id));

    this.addAudioLevelIndicator();
    this.updateIndicators();

    this.container.onclick = this._onContainerClick.bind(this);
}

LocalVideo.prototype = Object.create(SmallVideo.prototype);
LocalVideo.prototype.constructor = LocalVideo;

LocalVideo.prototype.createContainer = function() {
    const containerSpan = document.createElement('span');

    containerSpan.classList.add('videocontainer');
    containerSpan.id = this.videoSpanId;

    containerSpan.innerHTML = `

`;

    return containerSpan;
};

/**
 * Sets the display name for the given video span id.
 */
LocalVideo.prototype.setDisplayName = function(displayName) {
    if (!this.container) {
        logger.warn(
                `Unable to set displayName - ${this.videoSpanId
                } does not exist`);

        return;
    }

    this.updateDisplayName({
        allowEditing: APP.store.getState()['features/base/jwt'].isGuest,
        displayName,
        displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
        elementID: 'localDisplayName',
        participantID: this.id
    });
};

LocalVideo.prototype.changeVideo = function(stream) {
    this.videoStream = stream;

    this.localVideoId = `localVideo_${stream.getId()}`;

    this._updateVideoElement();

    // eslint-disable-next-line eqeqeq
    const isVideo = stream.videoType != 'desktop';
    const settings = APP.store.getState()['features/base/settings'];

    this._enableDisableContextMenu(isVideo);
    this.setFlipX(isVideo ? settings.localFlipX : false);

    const endedHandler = () => {
        const localVideoContainer
            = document.getElementById('localVideoWrapper');

        // Only remove if there is no video and not a transition state.
        // Previous non-react logic created a new video element with each track
        // removal whereas react reuses the video component so it could be the
        // stream ended but a new one is being used.
        if (localVideoContainer && this.videoStream.isEnded()) {
            ReactDOM.unmountComponentAtNode(localVideoContainer);
        }

        this._notifyOfStreamEnded();
        stream.off(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
    };

    stream.on(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
};

/**
 * Notify any subscribers of the local video stream ending.
 *
 * @private
 * @returns {void}
 */
LocalVideo.prototype._notifyOfStreamEnded = function() {
    if (this.streamEndedCallback) {
        this.streamEndedCallback(this.id);
    }
};

/**
 * Shows or hides the local video container.
 * @param {boolean} true to make the local video container visible, false
 * otherwise
 */
LocalVideo.prototype.setVisible = function(visible) {

    // We toggle the hidden class as an indication to other interested parties
    // that this container has been hidden on purpose.
    this.$container.toggleClass('hidden');

    // We still show/hide it as we need to overwrite the style property if we
    // want our action to take effect. Toggling the display property through
    // the above css class didn't succeed in overwriting the style.
    if (visible) {
        this.$container.show();
    } else {
        this.$container.hide();
    }
};

/**
 * Sets the flipX state of the video.
 * @param val {boolean} true for flipped otherwise false;
 */
LocalVideo.prototype.setFlipX = function(val) {
    this.emitter.emit(UIEvents.LOCAL_FLIPX_CHANGED, val);
    if (!this.localVideoId) {
        return;
    }
    if (val) {
        this.selectVideoElement().addClass('flipVideoX');
    } else {
        this.selectVideoElement().removeClass('flipVideoX');
    }
};

/**
 * Builds the context menu for the local video.
 */
LocalVideo.prototype._buildContextMenu = function() {
    $.contextMenu({
        selector: `#${this.videoSpanId}`,
        zIndex: 10000,
        items: {
            flip: {
                name: 'Flip',
                callback: () => {
                    const { store } = APP;
                    const val = !store.getState()['features/base/settings']
                    .localFlipX;

                    this.setFlipX(val);
                    store.dispatch(updateSettings({
                        localFlipX: val
                    }));
                }
            }
        },
        events: {
            show(options) {
                options.items.flip.name
                    = APP.translation.generateTranslationHTML(
                        'videothumbnail.flip');
            }
        }
    });
};

/**
 * Enables or disables the context menu for the local video.
 * @param enable {boolean} true for enable, false for disable
 */
LocalVideo.prototype._enableDisableContextMenu = function(enable) {
    if (this.$container.contextMenu) {
        this.$container.contextMenu(enable);
    }
};

/**
 * Places the {@code LocalVideo} in the DOM based on the current video layout.
 *
 * @returns {void}
 */
LocalVideo.prototype.updateDOMLocation = function() {
    if (!this.container) {
        return;
    }

    if (this.container.parentElement) {
        this.container.parentElement.removeChild(this.container);
    }

    const appendTarget = shouldDisplayTileView(APP.store.getState())
        ? document.getElementById('localVideoTileViewContainer')
        : document.getElementById('filmstripLocalVideoThumbnail');

    appendTarget && appendTarget.appendChild(this.container);

    this._updateVideoElement();
};

/**
 * Callback invoked when the thumbnail is clicked. Will directly call
 * VideoLayout to handle thumbnail click if certain elements have not been
 * clicked.
 *
 * @param {MouseEvent} event - The click event to intercept.
 * @private
 * @returns {void}
 */
LocalVideo.prototype._onContainerClick = function(event) {
    // TODO Checking the classes is a workround to allow events to bubble into
    // the DisplayName component if it was clicked. React's synthetic events
    // will fire after jQuery handlers execute, so stop propogation at this
    // point will prevent DisplayName from getting click events. This workaround
    // should be removeable once LocalVideo is a React Component because then
    // the components share the same eventing system.
    const $source = $(event.target || event.srcElement);
    const { classList } = event.target;

    const clickedOnDisplayName
        = $source.parents('.displayNameContainer').length > 0;
    const clickedOnPopover = $source.parents('.popover').length > 0
            || classList.contains('popover');
    const ignoreClick = clickedOnDisplayName || clickedOnPopover;

    if (event.stopPropagation && !ignoreClick) {
        event.stopPropagation();
    }

    if (!ignoreClick) {
        this._togglePin();
    }
};

/**
 * Renders the React Element for displaying video in {@code LocalVideo}.
 *
 */
LocalVideo.prototype._updateVideoElement = function() {
    const localVideoContainer = document.getElementById('localVideoWrapper');
    const videoTrack
        = getLocalVideoTrack(APP.store.getState()['features/base/tracks']);

    ReactDOM.render(
        <Provider store = { APP.store }>
            <VideoTrack
                id = 'localVideo_container'
                videoTrack = { videoTrack } />
        </Provider>,
        localVideoContainer
    );

    // Ensure the video gets play() called on it. This may be necessary in the
    // case where the local video container was moved and re-attached, in which
    // case video does not autoplay.
    const video = this.container.querySelector('video');

    video && video.play();
};

export default LocalVideo;
<|endoftext|>


<commit_before>'use strict';

var assert = require('chai').assert;
var sinon = require("sinon");
var fs = require('fs');
var path = require('path');
var Mode = require('stat-mode');
var rm = require('rimraf').sync;
var equal = require('assert-dir-equal');
var exec = require('child_process').exec;
var fixture = path.resolve.bind(path, __dirname, 'fixtures');

var Config = require('../lib/utils/config');

sinon.assert.expose(assert, { prefix: "" });

describe('Config', function(){

    describe('constructor', function(){
        it('should provide a DEFAULTS object', function(){
            assert.isObject(Config.DEFAULTS);
        });

        it('should initialize config with DEFAULTS values', function(){
            var config = new Config();
            Object.keys(Config.DEFAULTS).map(function(prop){
                assert.ok(config[prop] == Config.DEFAULTS[prop]);
            });
        });

        it('should create a filename getter', function(){
            var config = new Config();
            assert.ok(config.filename);
        });

        it('should define a filename setter', function(){
            var config = new Config;
            config.filename = '.peperonerc';
            assert.ok(config.baseName, 'peperone');
        });

        it('should create a "configLoader" method', function(){
            var config = new Config
            assert.isFunction(config.configLoader);
        });
    });

    describe('load', function(){
        it('should create a "config" object', function(){
            var expected = {a:1, b:2, c:3};
            var config = new Config({
                configLoader: function(){
                    return expected;
                }
            });
            config.load();
            assert.equal(config.config, expected);
        });

        xit('should set "baseName" if provided', function(){
            var config = new Config({
                configLoader: function(){ return {}; }
            });
            var expected = 'myBaseName';
            config.load(expected);
            assert.equal(config.baseName, expected);
        });
    });

    describe('save', function(){
        it('should no "target" parameter set it should write to "defaultTarget"', function(){
            var expected = 'myDefaultTarget';
            var config = new Config({
                defaultTarget: expected,
                write: function(target){
                    assert.ok(target, expected);
                }
            });
            config.save({}, expected);
        });

        it('should merge provided "conf" parameter with existing "config" prop', function(){
            var conf = {b: 2, c: 3};
            var expected = {a: 1, b: 2, c: 3};
            var config = new Config({
                config: {a: 1},
                write: function(target){}
            });
            config.save(conf);
            assert.ok(config.config, expected);
        });

        it('should work without arguments', function(){
            var expected = {a: 1, b: 2, c: 3};
            var config = new Config({
                config: expected,
                write: function(target){}
            });
            config.save();
            assert.ok(config.config, expected);
        });
    });

    describe('write', function(){
        it('should exclude "deleteOnWrite" properties from config output', function(){
            var expected = {a: 1};
            var config = new Config({
                config: {a: 1, b: 2, c: 3},
                deleteOnWrite: ['b', 'c'],
                writeFile: function(_, output){
                    assert.ok(output, expected);
                }
            });
            config.write();
        });

        it('should call "configStringify" with config object', function(){
            var expected = {a: 1, b: 2, c: 3};
            var config = new Config({
                config: expected,
                configStringify: function(output){
                    assert.ok(output, expected);
                }
            });
            config.write();
        });

        xit('should generate expected file format', function(){
            var expected = {a: 1, b: 2, c: 3};
            var config = new Config({
                config: expected,
                configStringify: function(output){
                    assert.ok(output, expected);
                }
            });
            config.write();
        });
    });

    describe('getOutputPath', function(){
        it('should return a default path', function(){
            var config = new Config();
            var expected = path.join(config.defaultTarget, config.filename);
            assert.ok(config.getOutputPath(), expected);
        });
        it('should use getOutputPath to determine where to write', function(){
            var config = new Config();
            var spy = sinon.spy(config, 'getOutputPath');
            config.write('target');
            assert.calledOnce(spy);
        });
    });
});<commit_msg>TEST: config wip<commit_after>'use strict';

var assert = require('chai').assert;
var sinon = require('sinon');
var fs = require('fs');
var path = require('path');
var Mode = require('stat-mode');
var rm = require('rimraf').sync;
var equal = require('assert-dir-equal');
var exec = require('child_process').exec;
var fixture = path.resolve.bind(path, __dirname, 'fixtures');
var ini = require('ini');

var Config = require('../lib/utils/config');

Config.DEFAULTS.baseName = 'config_test';

sinon.assert.expose(assert, { prefix: "" });

describe('Config', function(){

    describe('constructor', function(){
        it('should provide a DEFAULTS object', function(){
            assert.isObject(Config.DEFAULTS);
        });

        it('should initialize config with DEFAULTS values', function(){
            var config = new Config();
            Object.keys(Config.DEFAULTS).map(function(prop){
                assert.ok(config[prop] == Config.DEFAULTS[prop]);
            });
        });

        it('should create a filename getter', function(){
            var config = new Config();
            assert.ok(config.filename);
        });

        it('should define a filename setter', function(){
            var config = new Config;
            config.filename = '.peperonerc';
            assert.ok(config.baseName, 'peperone');
        });

        it('should create a "configLoader" method', function(){
            var config = new Config
            assert.isFunction(config.configLoader);
        });
    });

    describe('load', function(){
        it('should create a "config" object', function(){
            var expected = {a:1, b:2, c:3};
            var config = new Config({
                configLoader: function(){
                    return expected;
                }
            });
            config.load();
            assert.equal(config.config, expected);
        });

        it('should load a config object', function(done){
            var expected = {a: 1, b: 2, c: 3};
            var config = new Config({
                config: expected,
                defaultTarget: fixture('write')
            });

            config.write();

            fs.readFile(fixture('write', 'expected.ini'), 'utf-8', function(err, data){
                assert.ok(expected, ini.parse(data));
                done();
            });
        });

        xit('should set "baseName" if provided', function(){
            var config = new Config({
                configLoader: function(){ return {}; }
            });
            var expected = 'myBaseName';
            config.load(expected);
            assert.equal(config.baseName, expected);
        });
    });

    describe('save', function(){
        it('should no "target" parameter set it should write to "defaultTarget"', function(){
            var expected = 'myDefaultTarget';
            var config = new Config({
                defaultTarget: expected,
                write: function(target){
                    assert.ok(target, expected);
                }
            });
            config.save({}, expected);
        });

        it('should merge provided "conf" parameter with existing "config" prop', function(){
            var conf = {b: 2, c: 3};
            var expected = {a: 1, b: 2, c: 3};
            var config = new Config({
                config: {a: 1},
                write: function(target){}
            });
            config.save(conf);
            assert.ok(config.config, expected);
        });

        it('should work without arguments', function(){
            var expected = {a: 1, b: 2, c: 3};
            var config = new Config({
                config: expected,
                write: function(target){}
            });
            config.save();
            assert.ok(config.config, expected);
        });
    });

    describe('write', function(){
        it('should exclude "deleteOnWrite" properties from config output', function(){
            var expected = {a: 1};
            var config = new Config({
                config: {a: 1, b: 2, c: 3},
                deleteOnWrite: ['b', 'c'],
                defaultTarget: fixture('write'),
                writeFile: function(_, output){
                    assert.ok(output, expected);
                }
            });
            config.write();
        });

        it('should call "configStringify" with config object', function(){
            var expected = {a: 1, b: 2, c: 3};
            var config = new Config({
                config: expected,
                defaultTarget: fixture('write'),
                configStringify: function(output){
                    assert.ok(output, expected);
                }
            });
            config.write();
        });

        it('should generate expected file format', function(done){
            var expected = {a: 1, b: 2, c: 3};
            var config = new Config({
                config: expected,
                defaultTarget: fixture('write')
            });

            config.write();

            fs.readFile(fixture('write', 'expected.ini'), 'utf-8', function(err, data){
                assert.ok(expected, ini.parse(data));
                done();
            });
        });
    });

    describe('getOutputPath', function(){
        it('should return a default path', function(){
            var config = new Config();
            var expected = path.join(config.defaultTarget, config.filename);
            assert.ok(config.getOutputPath(), expected);
        });

        it('should use getOutputPath to determine where to write', function(){
            var config = new Config();
            var spy = sinon.spy(config, 'getOutputPath');
            config.write('target');
            assert.calledOnce(spy);
        });

    });
});<|endoftext|>


<commit_before>/*global jQuery */
/*!
* FitLines.js 1.0
*
* Copyright 2014, Derek Wheelden http://github.com/frxnz
* Released under the MIT license
*
*/

(function ($) {

    $.fn.fitLines = function (options) {

        // Setup options
        var settings = $.extend({
                'createLines' : true,
                'threshold'   : 0
            }, options);

        return this.each(function () {

            // Store the object
            var $this = $(this),
                lines;

            if (settings.createLines) {
              lines = $this.text().split('\n');
              $this.empty();

              $(lines).each(function () {
                  if (this.match(/\w/g)) {
                      $this.append('
' + this.trim() + '
');
                  }
              });
            } else {
              lines = $this.find('div');

              $(lines).each(function () {
                  $(this).html('' + $(this).text().trim() + '');
              });
            }

            // Resizer() resizes items based on the object width divided by the compressor * 10
            var resizer = function () {
                var $lines = $this.find('div'),
                    maxWidth = $this.width();

                $lines.each(function () {
                    var $el = $('span', this),
                        width = $el.width(),
                        fontSize = $this.css('fontSize');

                    if (fontSize < settings.minFontSize) {
                        $el.css('fontSize', minFontSize);
                    }

                    while (width < maxWidth - settings.threshold) {

                        $el.css('fontSize', '+=1');
                        width = $el.width();

                    }

                });
            };

            // Call once to set.
            resizer();

            // Call on resize. Opera debounces their resize by default.
            $(window).on('resize.fitlines orientationchange.fitlines', resizer);

        });

    };

})(jQuery);
<commit_msg>Fixing attribution<commit_after>/*global jQuery */
/*!
* FitLines.js 1.0
*
* Copyright 2014, Dirigo Design & Development http://github.com/dirigodev
* Released under the MIT license
*
*/

(function ($) {

    $.fn.fitLines = function (options) {

        // Setup options
        var settings = $.extend({
                'createLines' : true,
                'threshold'   : 0
            }, options);

        return this.each(function () {

            // Store the object
            var $this = $(this),
                lines;

            if (settings.createLines) {
              lines = $this.text().split('\n');
              $this.empty();

              $(lines).each(function () {
                  if (this.match(/\w/g)) {
                      $this.append('
' + this.trim() + '
');
                  }
              });
            } else {
              lines = $this.find('div');

              $(lines).each(function () {
                  $(this).html('' + $(this).text().trim() + '');
              });
            }

            // Resizer() resizes items based on the object width divided by the compressor * 10
            var resizer = function () {
                var $lines = $this.find('div'),
                    maxWidth = $this.width();

                $lines.each(function () {
                    var $el = $('span', this),
                        width = $el.width(),
                        fontSize = $this.css('fontSize');

                    if (fontSize < settings.minFontSize) {
                        $el.css('fontSize', minFontSize);
                    }

                    while (width < maxWidth - settings.threshold) {

                        $el.css('fontSize', '+=1');
                        width = $el.width();

                    }

                });
            };

            // Call once to set.
            resizer();

            // Call on resize. Opera debounces their resize by default.
            $(window).on('resize.fitlines orientationchange.fitlines', resizer);

        });

    };

})(jQuery);
<|endoftext|>