<commit_before>(function() {

    var overlay = $('#pay'),
        paymentsTemplate = template($('#pay-template').html()),
        product,
        purchaseInProgress = true,
        $def,
        message = $('#purchased'),
        messageTemplate = template(message.html()),
        data = {'currency': $('body').data('user').currency};

    function beginPurchase(prod) {
        if (!prod) return;
        if ($def && $def.state() == 'pending') {
            $def.reject(product, 'collision');
            return;
        }
        $def = $.Deferred();
        product = prod;

        // If the user is pre-authed, just call PayPal right away.
        if (z.pre_auth) {
            startPayment();
            return $def.promise();
        }

        if (product.currencies) {
            initCurrencies(JSON.parse(product.currencies));
        }

        overlay.html(paymentsTemplate(product));
        overlay.addClass('show');

        // Let's set user's default currency unless he/she changes it.
        $('#preapproval input').val(data.currency);

        // Guess and set the payment overlay height.
        setTimeout(function() {
            overlay.find('section').css('height',
                $('#pay section > div').outerHeight() + 38 +'px');
        }, 0);

        $(window).bind('keypress.payments', function(e) {
            if (e.keyCode == z.keys.ESCAPE) {
                cancelPurchase();
            }
        }).bind('overlay_dismissed', function(e) {
            cancelPurchase();
        });

        overlay.on('click.payments', '#pre-approval', beginPreApproval);
        overlay.on('click.payments', '#payment-confirm', startPayment);
        overlay.on('click.payments', '#pay .close', cancelPurchase);

        return $def.promise();
    }

    function initCurrencies(clist) {
        var $list = $('

'),
            $label = $(format('
{0}
', gettext('Currency'))),
            $item;

        for (var currency in clist) {
            $item = $(format('
{0} ({1})
', currency,
                             clist[currency]));
            $list.append($item);
        }

        $label.prepend($list);

        // Yea...(race condition).
        setTimeout(function() {
            $('.product-details .price').addClass('has-currency')
                                        .append($label);
            initCurrencyEvents($list);
        }, 0);
    }

    function initCurrencyEvents($list) {
        var $price = $('#price-display'),
            $items = $list.find('li');

        $('#pay .has-currency').unbind('click').click(function(e) {
            if ($price.hasClass('expanded')) {
                $price.removeClass('expanded');
                $list.css('height', 0);
            } else {
                var myHeight = $items.length * $items.outerHeight();
                $price.addClass('expanded');
                $list.css('height', myHeight + 'px');
            }
        });

        $('#pay').unbind('click.currency')
                 .on('click.currency', _pd(function(e) {
            var $targ = $(e.target);

            if ($targ.parents('#currency-list').length ||
                $targ.parent('.price').length) {
                var sel = $targ.html().split(' ');

                if (sel.length == 2) {
                    sel[1] = sel[1].replace('\(', '').replace('\)', '');

                    // Feel free to remove.
                    console.log('Setting currency to: ', sel[0]);
                    data.currency = sel[0];
                    $('#preapproval input').val(sel[0]);
                    $price.removeClass('expanded').html(sel[1]);
                    $list.css('height', 0);
                }
            } else {
                $price.removeClass('expanded');
                $list.css('height', 0);
            }
        }));
    }

    function beginPreApproval(e) {
        localStorage.setItem('toInstall', product.manifestUrl);
        $('#preapproval').submit();
    }

    function cancelPurchase(e) {
        if (e && e.preventDefault) e.preventDefault();
        $def.reject(product, 'cancelled');
        $(window).unbind('.payments');
        overlay.unbind('.payments');
        overlay.removeClass('show');
    }

    function completePurchase() {
        $(window).unbind('.payments');
        overlay.unbind('.payments');
        overlay.removeClass('show');
        message.html(messageTemplate(product));
        message.toggle();
        $def.resolve(product);
    }

    function startPayment(e) {
        if (e && e.preventDefault) e.preventDefault();
        $.when(doPaypal(product))
         .then(completePurchase);
    }

    function doPaypal() {
        var $def = $.Deferred();
        $(window).bind('purchasecomplete.payments', function() {
            $def.resolve();
        });

        $(window).bind('purchaseerror.payments', function(e, p, error) {
            $('#pay-error').show().find('div').text(error);
            cancelPurchase();
        });

        $.post(product.purchase, data, function(response) {
            if (response.error) {
                $(window).trigger('purchaseerror', [product, response.error]);
            }
            else if (response.status == 'COMPLETED') {
                // If the response from pre-auth was good,
                // then just jump to the next step.
                $def.resolve();
            } else {
                // This will show if the user is not pre-authed
                // or for some reason the pre-auth failed.
                dgFlow = new PAYPAL.apps.DGFlow({trigger: '#page'});
                dgFlow.startFlow(response.url);
                overlay.removeClass('show');
                // Scroll to top of PayPal modal.
                var $frame = $('[name=PPDGFrame]');
                if ($frame.length) {
                    var offset = $frame.offset().top;
                    if (offset > 9) {
                        $(document.documentElement).animate({scrollTop: offset}, 1000);
                    }
                }
                // When PayPal modal gets dismissed, reset install button.
                var intVal = setInterval(function() {
                    if (!dgFlow.isOpen()) {
                        clearInterval(intVal);
                        cancelPurchase();
                        return;
                    }
                }, 1000);
            }
        });
        return $def.promise();
    }

    z.payments = {
        'purchase': beginPurchase
    };
})();
<commit_msg>do not override the CSRF token when setting user's currency during pre-auth (bug 749066)<commit_after>(function() {

    var overlay = $('#pay'),
        paymentsTemplate = template($('#pay-template').html()),
        product,
        purchaseInProgress = true,
        $def,
        message = $('#purchased'),
        messageTemplate = template(message.html()),
        data = {'currency': $('body').data('user').currency};

    function beginPurchase(prod) {
        if (!prod) return;
        if ($def && $def.state() == 'pending') {
            $def.reject(product, 'collision');
            return;
        }
        $def = $.Deferred();
        product = prod;

        // If the user is pre-authed, just call PayPal right away.
        if (z.pre_auth) {
            startPayment();
            return $def.promise();
        }

        if (product.currencies) {
            initCurrencies(JSON.parse(product.currencies));
        }

        overlay.html(paymentsTemplate(product));
        overlay.addClass('show');

        // Let's set user's default currency unless he/she changes it.
        if (data.currency) {
            $('#preapproval input[name=currency]').val(data.currency);
        }

        // Guess and set the payment overlay height.
        setTimeout(function() {
            overlay.find('section').css('height',
                $('#pay section > div').outerHeight() + 38 +'px');
        }, 0);

        $(window).bind('keypress.payments', function(e) {
            if (e.keyCode == z.keys.ESCAPE) {
                cancelPurchase();
            }
        }).bind('overlay_dismissed', function(e) {
            cancelPurchase();
        });

        overlay.on('click.payments', '#pre-approval', beginPreApproval);
        overlay.on('click.payments', '#payment-confirm', startPayment);
        overlay.on('click.payments', '#pay .close', cancelPurchase);

        return $def.promise();
    }

    function initCurrencies(clist) {
        var $list = $('

'),
            $label = $(format('
{0}
', gettext('Currency'))),
            $item;

        for (var currency in clist) {
            $item = $(format('
{0} ({1})
', currency,
                             clist[currency]));
            $list.append($item);
        }

        $label.prepend($list);

        // Yea...(race condition).
        setTimeout(function() {
            $('.product-details .price').addClass('has-currency')
                                        .append($label);
            initCurrencyEvents($list);
        }, 0);
    }

    function initCurrencyEvents($list) {
        var $price = $('#price-display'),
            $items = $list.find('li');

        $('#pay .has-currency').unbind('click').click(function(e) {
            if ($price.hasClass('expanded')) {
                $price.removeClass('expanded');
                $list.css('height', 0);
            } else {
                var myHeight = $items.length * $items.outerHeight();
                $price.addClass('expanded');
                $list.css('height', myHeight + 'px');
            }
        });

        $('#pay').unbind('click.currency')
                 .on('click.currency', _pd(function(e) {
            var $targ = $(e.target);

            if ($targ.parents('#currency-list').length ||
                $targ.parent('.price').length) {
                var sel = $targ.html().split(' ');

                if (sel.length == 2) {
                    sel[1] = sel[1].replace('\(', '').replace('\)', '');

                    // Feel free to remove.
                    console.log('Setting currency to: ', sel[0]);
                    data.currency = sel[0];
                    $('#preapproval input').val(sel[0]);
                    $price.removeClass('expanded').html(sel[1]);
                    $list.css('height', 0);
                }
            } else {
                $price.removeClass('expanded');
                $list.css('height', 0);
            }
        }));
    }

    function beginPreApproval(e) {
        localStorage.setItem('toInstall', product.manifestUrl);
        $('#preapproval').submit();
    }

    function cancelPurchase(e) {
        if (e && e.preventDefault) e.preventDefault();
        $def.reject(product, 'cancelled');
        $(window).unbind('.payments');
        overlay.unbind('.payments');
        overlay.removeClass('show');
    }

    function completePurchase() {
        $(window).unbind('.payments');
        overlay.unbind('.payments');
        overlay.removeClass('show');
        message.html(messageTemplate(product));
        message.toggle();
        $def.resolve(product);
    }

    function startPayment(e) {
        if (e && e.preventDefault) e.preventDefault();
        $.when(doPaypal(product))
         .then(completePurchase);
    }

    function doPaypal() {
        var $def = $.Deferred();
        $(window).bind('purchasecomplete.payments', function() {
            $def.resolve();
        });

        $(window).bind('purchaseerror.payments', function(e, p, error) {
            $('#pay-error').show().find('div').text(error);
            cancelPurchase();
        });

        $.post(product.purchase, data, function(response) {
            if (response.error) {
                $(window).trigger('purchaseerror', [product, response.error]);
            }
            else if (response.status == 'COMPLETED') {
                // If the response from pre-auth was good,
                // then just jump to the next step.
                $def.resolve();
            } else {
                // This will show if the user is not pre-authed
                // or for some reason the pre-auth failed.
                dgFlow = new PAYPAL.apps.DGFlow({trigger: '#page'});
                dgFlow.startFlow(response.url);
                overlay.removeClass('show');
                // Scroll to top of PayPal modal.
                var $frame = $('[name=PPDGFrame]');
                if ($frame.length) {
                    var offset = $frame.offset().top;
                    if (offset > 9) {
                        $(document.documentElement).animate({scrollTop: offset}, 1000);
                    }
                }
                // When PayPal modal gets dismissed, reset install button.
                var intVal = setInterval(function() {
                    if (!dgFlow.isOpen()) {
                        clearInterval(intVal);
                        cancelPurchase();
                        return;
                    }
                }, 1000);
            }
        });
        return $def.promise();
    }

    z.payments = {
        'purchase': beginPurchase
    };
})();
<|endoftext|>


<commit_before>/**
 * DropdownMenu module.
 * @module foundation.dropdown-menu
 * @requires foundation.util.keyboard
 * @requires foundation.util.box
 * @requires foundation.util.nest
 */
!function($, Foundation){
  'use strict';

  /**
   * Creates a new instance of DropdownMenu.
   * @class
   * @fires DropdownMenu#init
   * @param {jQuery} element - jQuery object to make into a dropdown menu.
   * @param {Object} options - Overrides to the default plugin settings.
   */
  function DropdownMenu(element, options){
    this.$element = element;
    this.options = $.extend({}, DropdownMenu.defaults, this.$element.data(), options);

    Foundation.Nest.Feather(this.$element, 'dropdown');
    this._init();

    Foundation.registerPlugin(this, 'DropdownMenu');
    Foundation.Keyboard.register('DropdownMenu', {
      'ENTER': 'open',
      'SPACE': 'open',
      'ARROW_RIGHT': 'next',
      'ARROW_UP': 'up',
      'ARROW_DOWN': 'down',
      'ARROW_LEFT': 'previous',
      'ESCAPE': 'close'
    });
  }

  /**
   * Default settings for plugin
   */
  DropdownMenu.defaults = {
    /**
     * Disallows hover events from opening submenus
     * @option
     * @example false
     */
    disableHover: false,
    /**
     * Allow a submenu to automatically close on a mouseleave event, if not clicked open.
     * @option
     * @example true
     */
    autoclose: true,
    /**
     * Amount of time to delay opening a submenu on hover event.
     * @option
     * @example 50
     */
    hoverDelay: 50,
    /**
     * Allow a submenu to open/remain open on parent click event. Allows cursor to move away from menu.
     * @option
     * @example true
     */
    clickOpen: false,
    /**
     * Amount of time to delay closing a submenu on a mouseleave event.
     * @option
     * @example 500
     */

    closingTime: 500,
    /**
     * Position of the menu relative to what direction the submenus should open. Handled by JS.
     * @option
     * @example 'left'
     */
    alignment: 'left',
    /**
     * Allow clicks on the body to close any open submenus.
     * @option
     * @example true
     */
    closeOnClick: true,
    /**
     * Class applied to vertical oriented menus, Foundation default is `vertical`. Update this if using your own class.
     * @option
     * @example 'vertical'
     */
    verticalClass: 'vertical',
    /**
     * Class applied to right-side oriented menus, Foundation default is `align-right`. Update this if using your own class.
     * @option
     * @example 'align-right'
     */
    rightClass: 'align-right',
    /**
     * Boolean to force overide the clicking of links to perform default action, on second touch event for mobile.
     * @option
     * @example false
     */
    forceFollow: true
  };
  /**
   * Initializes the plugin, and calls _prepareMenu
   * @private
   * @function
   */
  DropdownMenu.prototype._init = function(){
    var subs = this.$element.find('li.is-dropdown-submenu-parent');
    this.$element.children('.is-dropdown-submenu-parent').children('.is-dropdown-submenu').addClass('first-sub');

    this.$menuItems = this.$element.find('[role="menuitem"]');
    this.$tabs = this.$element.children('[role="menuitem"]');
    this.isVert = this.$element.hasClass(this.options.verticalClass);
    this.$tabs.find('ul.is-dropdown-submenu').addClass(this.options.verticalClass);

    if(this.$element.hasClass(this.options.rightClass) || this.options.alignment === 'right' || Foundation.rtl()){
      this.options.alignment = 'right';
      subs.addClass('is-left-arrow opens-left');
    }else{
      subs.addClass('is-right-arrow opens-right');
    }
    if(!this.isVert){
      this.$tabs.filter('.is-dropdown-submenu-parent').removeClass('is-right-arrow is-left-arrow opens-right opens-left')
          .addClass('is-down-arrow');
    }
    this.changed = false;
    this._events();
  };
  /**
   * Adds event listeners to elements within the menu
   * @private
   * @function
   */
  DropdownMenu.prototype._events = function(){
    var _this = this,
        hasTouch = 'ontouchstart' in window || (typeof window.ontouchstart !== 'undefined'),
        parClass = 'is-dropdown-submenu-parent';

    if(this.options.clickOpen || hasTouch){
      this.$menuItems.on('click.zf.dropdownmenu touchstart.zf.dropdownmenu', function(e){
        var $elem = $(e.target).parentsUntil('ul', '.' + parClass),
            hasSub = $elem.hasClass(parClass),
            hasClicked = $elem.attr('data-is-click') === 'true',
            $sub = $elem.children('.is-dropdown-submenu');

        if(hasSub){
          if(hasClicked){
            if(!_this.options.closeOnClick || (!_this.options.clickOpen && !hasTouch) || (_this.options.forceFollow && hasTouch)){ return; }
            else{
              e.stopImmediatePropagation();
              e.preventDefault();
              _this._hide($elem);
            }
          }else{
            e.preventDefault();
            e.stopImmediatePropagation();
            _this._show($elem.children('.is-dropdown-submenu'));
            $elem.add($elem.parentsUntil(_this.$element, '.' + parClass)).attr('data-is-click', true);
          }
        }else{ return; }
      });
    }

    if(!this.options.disableHover){
      this.$menuItems.on('mouseenter.zf.dropdownmenu', function(e){
        e.stopImmediatePropagation();
        var $elem = $(this),
            hasSub = $elem.hasClass(parClass);

        if(hasSub){
          clearTimeout(_this.delay);
          _this.delay = setTimeout(function(){
            _this._show($elem.children('.is-dropdown-submenu'));
          }, _this.options.hoverDelay);
        }
      }).on('mouseleave.zf.dropdownmenu', function(e){
        var $elem = $(this),
            hasSub = $elem.hasClass(parClass);
        if(hasSub && _this.options.autoclose){
          if($elem.attr('data-is-click') === 'true' && _this.options.clickOpen){ return false; }

          clearTimeout(_this.delay);
          _this.delay = setTimeout(function(){
            _this._hide($elem);
          }, _this.options.closingTime);
        }
      });
    }
    this.$menuItems.on('keydown.zf.dropdownmenu', function(e){
      var $element = $(e.target).parentsUntil('ul', '[role="menuitem"]'),
          isTab = _this.$tabs.index($element) > -1,
          $elements = isTab ? _this.$tabs : $element.siblings('li').add($element),
          $prevElement,
          $nextElement;

      $elements.each(function(i) {
        if ($(this).is($element)) {
          $prevElement = $elements.eq(i-1);
          $nextElement = $elements.eq(i+1);
          return;
        }
      });

      var nextSibling = function() {
        if (!$element.is(':last-child')) $nextElement.children('a:first').focus();
      }, prevSibling = function() {
        $prevElement.children('a:first').focus();
      }, openSub = function() {
        var $sub = $element.children('ul.is-dropdown-submenu');
        if($sub.length){
          _this._show($sub);
          $element.find('li > a:first').focus();
        }else{ return; }
      }, closeSub = function() {
        //if ($element.is(':first-child')) {
        var close = $element.parent('ul').parent('li');
          close.children('a:first').focus();
          _this._hide(close);
        //}
      };
      var functions = {
        open: openSub,
        close: function() {
          _this._hide(_this.$element);
          _this.$menuItems.find('a:first').focus(); // focus to first element
        },
        handled: function() {
          e.preventDefault();
          e.stopImmediatePropagation();
        }
      };

      if (isTab) {
        if (_this.vertical) { // vertical menu
          if (_this.options.alignment === 'left') { // left aligned
            $.extend(functions, {
              down: nextSibling,
              up: prevSibling,
              next: openSub,
              previous: closeSub
            });
          } else { // right aligned
            $.extend(functions, {
              down: nextSibling,
              up: prevSibling,
              next: closeSub,
              previous: openSub
            });
          }
        } else { // horizontal menu
          $.extend(functions, {
            next: nextSibling,
            previous: prevSibling,
            down: openSub,
            up: closeSub
          });
        }
      } else { // not tabs -> one sub
        if (_this.options.alignment === 'left') { // left aligned
          $.extend(functions, {
            next: openSub,
            previous: closeSub,
            down: nextSibling,
            up: prevSibling
          });
        } else { // right aligned
          $.extend(functions, {
            next: closeSub,
            previous: openSub,
            down: nextSibling,
            up: prevSibling
          });
        }
      }
      Foundation.Keyboard.handleKey(e, 'DropdownMenu', functions);

    });
  };
  /**
   * Adds an event handler to the body to close any dropdowns on a click.
   * @function
   * @private
   */
  DropdownMenu.prototype._addBodyHandler = function(){
    var $body = $(document.body),
        _this = this;
    $body.off('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu')
         .on('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu', function(e){
           var $link = _this.$element.find(e.target);
           if($link.length){ return; }

           _this._hide();
           $body.off('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu');
         });
  };
  /**
   * Opens a dropdown pane, and checks for collisions first.
   * @param {jQuery} $sub - ul element that is a submenu to show
   * @function
   * @private
   * @fires DropdownMenu#show
   */
  DropdownMenu.prototype._show = function($sub){
    var idx = this.$tabs.index(this.$tabs.filter(function(i, el){
      return $(el).find($sub).length > 0;
    }));
    var $sibs = $sub.parent('li.is-dropdown-submenu-parent').siblings('li.is-dropdown-submenu-parent');
    this._hide($sibs, idx);
    $sub.css('visibility', 'hidden').addClass('js-dropdown-active').attr({'aria-hidden': false})
        .parent('li.is-dropdown-submenu-parent').addClass('is-active')
        .attr({'aria-expanded': true});
    var clear = Foundation.Box.ImNotTouchingYou($sub, null, true);
    if(!clear){
      var oldClass = this.options.alignment === 'left' ? '-right' : '-left',
          $parentLi = $sub.parent('.is-dropdown-submenu-parent');
      $parentLi.removeClass('opens' + oldClass).addClass('opens-' + this.options.alignment);
      clear = Foundation.Box.ImNotTouchingYou($sub, null, true);
      if(!clear){
        $parentLi.removeClass('opens-' + this.options.alignment).addClass('opens-inner');
      }
      this.changed = true;
    }
    $sub.css('visibility', '');
    if(this.options.closeOnClick){ this._addBodyHandler(); }
    /**
     * Fires when the new dropdown pane is visible.
     * @event DropdownMenu#show
     */
    this.$element.trigger('show.zf.dropdownmenu', [$sub]);
  };
  /**
   * Hides a single, currently open dropdown pane, if passed a parameter, otherwise, hides everything.
   * @function
   * @param {jQuery} $elem - element with a submenu to hide
   * @param {Number} idx - index of the $tabs collection to hide
   * @private
   */
  DropdownMenu.prototype._hide = function($elem, idx){
    var $toClose;
    if($elem && $elem.length){
      $toClose = $elem;
    }else if(idx !== undefined){
      $toClose = this.$tabs.not(function(i, el){
        return i === idx;
      });
    }
    else{
      $toClose = this.$element;
    }
    var somethingToClose = $toClose.hasClass('is-active') || $toClose.find('.is-active').length > 0;

    if(somethingToClose){
      $toClose.find('li.is-active').add($toClose).attr({
        'aria-expanded': false,
        'data-is-click': false
      }).removeClass('is-active');

      $toClose.find('ul.js-dropdown-active').attr({
        'aria-hidden': true
      }).removeClass('js-dropdown-active');

      if(this.changed || $toClose.find('opens-inner').length){
        var oldClass = this.options.alignment === 'left' ? 'right' : 'left';
        $toClose.find('li.is-dropdown-submenu-parent').add($toClose)
                .removeClass('opens-inner opens-' + this.options.alignment)
                .addClass('opens-' + oldClass);
        this.changed = false;
      }
      /**
       * Fires when the open menus are closed.
       * @event DropdownMenu#hide
       */
      this.$element.trigger('hide.zf.dropdownmenu', [$toClose]);
    }
  };
  /**
   * Destroys the plugin.
   * @function
   */
  DropdownMenu.prototype.destroy = function(){
    this.$menuItems.off('.zf.dropdownmenu').removeAttr('data-is-click')
        .removeClass('is-right-arrow is-left-arrow is-down-arrow opens-right opens-left opens-inner');
    $(document.body).off('.zf.dropdownmenu');
    Foundation.Nest.Burn(this.$element, 'dropdown');
    Foundation.unregisterPlugin(this);
  };

  Foundation.plugin(DropdownMenu, 'DropdownMenu');
}(jQuery, window.Foundation);
<commit_msg>Converted Dropdown Menu string concatenations to template literals<commit_after>/**
 * DropdownMenu module.
 * @module foundation.dropdown-menu
 * @requires foundation.util.keyboard
 * @requires foundation.util.box
 * @requires foundation.util.nest
 */
!function($, Foundation){
  'use strict';

  /**
   * Creates a new instance of DropdownMenu.
   * @class
   * @fires DropdownMenu#init
   * @param {jQuery} element - jQuery object to make into a dropdown menu.
   * @param {Object} options - Overrides to the default plugin settings.
   */
  function DropdownMenu(element, options){
    this.$element = element;
    this.options = $.extend({}, DropdownMenu.defaults, this.$element.data(), options);

    Foundation.Nest.Feather(this.$element, 'dropdown');
    this._init();

    Foundation.registerPlugin(this, 'DropdownMenu');
    Foundation.Keyboard.register('DropdownMenu', {
      'ENTER': 'open',
      'SPACE': 'open',
      'ARROW_RIGHT': 'next',
      'ARROW_UP': 'up',
      'ARROW_DOWN': 'down',
      'ARROW_LEFT': 'previous',
      'ESCAPE': 'close'
    });
  }

  /**
   * Default settings for plugin
   */
  DropdownMenu.defaults = {
    /**
     * Disallows hover events from opening submenus
     * @option
     * @example false
     */
    disableHover: false,
    /**
     * Allow a submenu to automatically close on a mouseleave event, if not clicked open.
     * @option
     * @example true
     */
    autoclose: true,
    /**
     * Amount of time to delay opening a submenu on hover event.
     * @option
     * @example 50
     */
    hoverDelay: 50,
    /**
     * Allow a submenu to open/remain open on parent click event. Allows cursor to move away from menu.
     * @option
     * @example true
     */
    clickOpen: false,
    /**
     * Amount of time to delay closing a submenu on a mouseleave event.
     * @option
     * @example 500
     */

    closingTime: 500,
    /**
     * Position of the menu relative to what direction the submenus should open. Handled by JS.
     * @option
     * @example 'left'
     */
    alignment: 'left',
    /**
     * Allow clicks on the body to close any open submenus.
     * @option
     * @example true
     */
    closeOnClick: true,
    /**
     * Class applied to vertical oriented menus, Foundation default is `vertical`. Update this if using your own class.
     * @option
     * @example 'vertical'
     */
    verticalClass: 'vertical',
    /**
     * Class applied to right-side oriented menus, Foundation default is `align-right`. Update this if using your own class.
     * @option
     * @example 'align-right'
     */
    rightClass: 'align-right',
    /**
     * Boolean to force overide the clicking of links to perform default action, on second touch event for mobile.
     * @option
     * @example false
     */
    forceFollow: true
  };
  /**
   * Initializes the plugin, and calls _prepareMenu
   * @private
   * @function
   */
  DropdownMenu.prototype._init = function(){
    var subs = this.$element.find('li.is-dropdown-submenu-parent');
    this.$element.children('.is-dropdown-submenu-parent').children('.is-dropdown-submenu').addClass('first-sub');

    this.$menuItems = this.$element.find('[role="menuitem"]');
    this.$tabs = this.$element.children('[role="menuitem"]');
    this.isVert = this.$element.hasClass(this.options.verticalClass);
    this.$tabs.find('ul.is-dropdown-submenu').addClass(this.options.verticalClass);

    if(this.$element.hasClass(this.options.rightClass) || this.options.alignment === 'right' || Foundation.rtl()){
      this.options.alignment = 'right';
      subs.addClass('is-left-arrow opens-left');
    }else{
      subs.addClass('is-right-arrow opens-right');
    }
    if(!this.isVert){
      this.$tabs.filter('.is-dropdown-submenu-parent').removeClass('is-right-arrow is-left-arrow opens-right opens-left')
          .addClass('is-down-arrow');
    }
    this.changed = false;
    this._events();
  };
  /**
   * Adds event listeners to elements within the menu
   * @private
   * @function
   */
  DropdownMenu.prototype._events = function(){
    var _this = this,
        hasTouch = 'ontouchstart' in window || (typeof window.ontouchstart !== 'undefined'),
        parClass = 'is-dropdown-submenu-parent';

    if(this.options.clickOpen || hasTouch){
      this.$menuItems.on('click.zf.dropdownmenu touchstart.zf.dropdownmenu', function(e){
        var $elem = $(e.target).parentsUntil('ul', `.${parClass}`),
            hasSub = $elem.hasClass(parClass),
            hasClicked = $elem.attr('data-is-click') === 'true',
            $sub = $elem.children('.is-dropdown-submenu');

        if(hasSub){
          if(hasClicked){
            if(!_this.options.closeOnClick || (!_this.options.clickOpen && !hasTouch) || (_this.options.forceFollow && hasTouch)){ return; }
            else{
              e.stopImmediatePropagation();
              e.preventDefault();
              _this._hide($elem);
            }
          }else{
            e.preventDefault();
            e.stopImmediatePropagation();
            _this._show($elem.children('.is-dropdown-submenu'));
            $elem.add($elem.parentsUntil(_this.$element, `.${parClass}`)).attr('data-is-click', true);
          }
        }else{ return; }
      });
    }

    if(!this.options.disableHover){
      this.$menuItems.on('mouseenter.zf.dropdownmenu', function(e){
        e.stopImmediatePropagation();
        var $elem = $(this),
            hasSub = $elem.hasClass(parClass);

        if(hasSub){
          clearTimeout(_this.delay);
          _this.delay = setTimeout(function(){
            _this._show($elem.children('.is-dropdown-submenu'));
          }, _this.options.hoverDelay);
        }
      }).on('mouseleave.zf.dropdownmenu', function(e){
        var $elem = $(this),
            hasSub = $elem.hasClass(parClass);
        if(hasSub && _this.options.autoclose){
          if($elem.attr('data-is-click') === 'true' && _this.options.clickOpen){ return false; }

          clearTimeout(_this.delay);
          _this.delay = setTimeout(function(){
            _this._hide($elem);
          }, _this.options.closingTime);
        }
      });
    }
    this.$menuItems.on('keydown.zf.dropdownmenu', function(e){
      var $element = $(e.target).parentsUntil('ul', '[role="menuitem"]'),
          isTab = _this.$tabs.index($element) > -1,
          $elements = isTab ? _this.$tabs : $element.siblings('li').add($element),
          $prevElement,
          $nextElement;

      $elements.each(function(i) {
        if ($(this).is($element)) {
          $prevElement = $elements.eq(i-1);
          $nextElement = $elements.eq(i+1);
          return;
        }
      });

      var nextSibling = function() {
        if (!$element.is(':last-child')) $nextElement.children('a:first').focus();
      }, prevSibling = function() {
        $prevElement.children('a:first').focus();
      }, openSub = function() {
        var $sub = $element.children('ul.is-dropdown-submenu');
        if($sub.length){
          _this._show($sub);
          $element.find('li > a:first').focus();
        }else{ return; }
      }, closeSub = function() {
        //if ($element.is(':first-child')) {
        var close = $element.parent('ul').parent('li');
          close.children('a:first').focus();
          _this._hide(close);
        //}
      };
      var functions = {
        open: openSub,
        close: function() {
          _this._hide(_this.$element);
          _this.$menuItems.find('a:first').focus(); // focus to first element
        },
        handled: function() {
          e.preventDefault();
          e.stopImmediatePropagation();
        }
      };

      if (isTab) {
        if (_this.vertical) { // vertical menu
          if (_this.options.alignment === 'left') { // left aligned
            $.extend(functions, {
              down: nextSibling,
              up: prevSibling,
              next: openSub,
              previous: closeSub
            });
          } else { // right aligned
            $.extend(functions, {
              down: nextSibling,
              up: prevSibling,
              next: closeSub,
              previous: openSub
            });
          }
        } else { // horizontal menu
          $.extend(functions, {
            next: nextSibling,
            previous: prevSibling,
            down: openSub,
            up: closeSub
          });
        }
      } else { // not tabs -> one sub
        if (_this.options.alignment === 'left') { // left aligned
          $.extend(functions, {
            next: openSub,
            previous: closeSub,
            down: nextSibling,
            up: prevSibling
          });
        } else { // right aligned
          $.extend(functions, {
            next: closeSub,
            previous: openSub,
            down: nextSibling,
            up: prevSibling
          });
        }
      }
      Foundation.Keyboard.handleKey(e, 'DropdownMenu', functions);

    });
  };
  /**
   * Adds an event handler to the body to close any dropdowns on a click.
   * @function
   * @private
   */
  DropdownMenu.prototype._addBodyHandler = function(){
    var $body = $(document.body),
        _this = this;
    $body.off('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu')
         .on('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu', function(e){
           var $link = _this.$element.find(e.target);
           if($link.length){ return; }

           _this._hide();
           $body.off('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu');
         });
  };
  /**
   * Opens a dropdown pane, and checks for collisions first.
   * @param {jQuery} $sub - ul element that is a submenu to show
   * @function
   * @private
   * @fires DropdownMenu#show
   */
  DropdownMenu.prototype._show = function($sub){
    var idx = this.$tabs.index(this.$tabs.filter(function(i, el){
      return $(el).find($sub).length > 0;
    }));
    var $sibs = $sub.parent('li.is-dropdown-submenu-parent').siblings('li.is-dropdown-submenu-parent');
    this._hide($sibs, idx);
    $sub.css('visibility', 'hidden').addClass('js-dropdown-active').attr({'aria-hidden': false})
        .parent('li.is-dropdown-submenu-parent').addClass('is-active')
        .attr({'aria-expanded': true});
    var clear = Foundation.Box.ImNotTouchingYou($sub, null, true);
    if(!clear){
      var oldClass = this.options.alignment === 'left' ? '-right' : '-left',
          $parentLi = $sub.parent('.is-dropdown-submenu-parent');
      $parentLi.removeClass(`opens${oldClass}`).addClass(`opens-${this.options.alignment}`);
      clear = Foundation.Box.ImNotTouchingYou($sub, null, true);
      if(!clear){
        $parentLi.removeClass(`opens-${this.options.alignment}`).addClass('opens-inner');
      }
      this.changed = true;
    }
    $sub.css('visibility', '');
    if(this.options.closeOnClick){ this._addBodyHandler(); }
    /**
     * Fires when the new dropdown pane is visible.
     * @event DropdownMenu#show
     */
    this.$element.trigger('show.zf.dropdownmenu', [$sub]);
  };
  /**
   * Hides a single, currently open dropdown pane, if passed a parameter, otherwise, hides everything.
   * @function
   * @param {jQuery} $elem - element with a submenu to hide
   * @param {Number} idx - index of the $tabs collection to hide
   * @private
   */
  DropdownMenu.prototype._hide = function($elem, idx){
    var $toClose;
    if($elem && $elem.length){
      $toClose = $elem;
    }else if(idx !== undefined){
      $toClose = this.$tabs.not(function(i, el){
        return i === idx;
      });
    }
    else{
      $toClose = this.$element;
    }
    var somethingToClose = $toClose.hasClass('is-active') || $toClose.find('.is-active').length > 0;

    if(somethingToClose){
      $toClose.find('li.is-active').add($toClose).attr({
        'aria-expanded': false,
        'data-is-click': false
      }).removeClass('is-active');

      $toClose.find('ul.js-dropdown-active').attr({
        'aria-hidden': true
      }).removeClass('js-dropdown-active');

      if(this.changed || $toClose.find('opens-inner').length){
        var oldClass = this.options.alignment === 'left' ? 'right' : 'left';
        $toClose.find('li.is-dropdown-submenu-parent').add($toClose)
                .removeClass(`opens-inner opens-${this.options.alignment}`)
                .addClass(`opens-${oldClass}`);
        this.changed = false;
      }
      /**
       * Fires when the open menus are closed.
       * @event DropdownMenu#hide
       */
      this.$element.trigger('hide.zf.dropdownmenu', [$toClose]);
    }
  };
  /**
   * Destroys the plugin.
   * @function
   */
  DropdownMenu.prototype.destroy = function(){
    this.$menuItems.off('.zf.dropdownmenu').removeAttr('data-is-click')
        .removeClass('is-right-arrow is-left-arrow is-down-arrow opens-right opens-left opens-inner');
    $(document.body).off('.zf.dropdownmenu');
    Foundation.Nest.Burn(this.$element, 'dropdown');
    Foundation.unregisterPlugin(this);
  };

  Foundation.plugin(DropdownMenu, 'DropdownMenu');
}(jQuery, window.Foundation);
<|endoftext|>


<commit_before>import find from 'lodash/find';
import unified from 'unified';
import markdownToRemark from 'remark-parse';
import remarkToRehype from 'remark-rehype';
import rehypeToHtml from 'rehype-stringify';
import htmlToRehype from 'rehype-parse';
import rehypeToRemark from 'rehype-remark';
import remarkToMarkdown from 'remark-stringify';
import rehypeSanitize from 'rehype-sanitize';
import rehypeMinifyWhitespace from 'rehype-minify-whitespace';

const remarkParseConfig = { fences: true };
const remarkStringifyConfig = { listItemIndent: '1', fences: true };
const rehypeParseConfig = { fragment: true };

const rehypeRemoveEmpty = () => {
  const isVoidElement = node => ['img', 'hr'].includes(node.tagName);
  const isNonEmptyText = node => node.type === 'text' && node.value;
  const isNonEmptyNode =  node => {
    return isVoidElement(node) || isNonEmptyText(node) || find(node.children, isNonEmptyNode);
  };

  const transform = node => {
    if (isVoidElement(node) || isNonEmptyText(node)) {
      return node;
    }
    if (node.children) {
      node.children = node.children.reduce((acc, childNode) => {
        if (isVoidElement(childNode) || isNonEmptyText(childNode)) {
          return acc.concat(childNode);
        }
        return find(childNode.children, isNonEmptyNode) ? acc.concat(transform(childNode)) : acc;
      }, []);
    }
    return node;
  };
  return transform;
};

const rehypePaperEmoji = () => {
  const transform = node => {
    if (node.tagName === 'img' && node.properties.dataEmojiCh) {
      return { type: 'text', value: node.properties.dataEmojiCh };
    }
    node.children = node.children ? node.children.map(transform) : node.children;
    return node;
  };
  return transform;
};

export const markdownToHtml = markdown => {
  console.log('markdownToHtml input', markdown);
  const result = unified()
    .use(markdownToRemark, remarkParseConfig)
    .use(remarkToRehype)
    .use(rehypeToHtml)
    .processSync(markdown)
    .contents;
  console.log('markdownToHtml output', result);
  return result;
}

export const htmlToMarkdown = html => {
  console.log('htmlToMarkdown input', html);
  const result = unified()
    .use(htmlToRehype, rehypeParseConfig)
    .use(rehypeToRemark)
    .use(remarkToMarkdown, remarkStringifyConfig)
    .processSync(html)
    .contents;
  console.log('htmlToMarkdown output', result);
  return result;
};
<commit_msg>improve Dropbox Paper paste handling<commit_after>import find from 'lodash/find';
import unified from 'unified';
import markdownToRemark from 'remark-parse';
import remarkToRehype from 'remark-rehype';
import rehypeToHtml from 'rehype-stringify';
import htmlToRehype from 'rehype-parse';
import rehypeToRemark from 'rehype-remark';
import remarkToMarkdown from 'remark-stringify';
import rehypeSanitize from 'rehype-sanitize';
import rehypeMinifyWhitespace from 'rehype-minify-whitespace';

const remarkParseConfig = { fences: true };
const remarkStringifyConfig = { listItemIndent: '1', fences: true };
const rehypeParseConfig = { fragment: true };

/**
 * Remove empty nodes, including the top level parents of deeply nested empty nodes.
 */
const rehypeRemoveEmpty = () => {
  const isVoidElement = node => ['img', 'hr'].includes(node.tagName);
  const isNonEmptyText = node => node.type === 'text' && node.value;
  const isNonEmptyNode =  node => {
    return isVoidElement(node) || isNonEmptyText(node) || find(node.children, isNonEmptyNode);
  };

  const transform = node => {
    if (isVoidElement(node) || isNonEmptyText(node)) {
      return node;
    }
    if (node.children) {
      node.children = node.children.reduce((acc, childNode) => {
        if (isVoidElement(childNode) || isNonEmptyText(childNode)) {
          return acc.concat(childNode);
        }
        return find(childNode.children, isNonEmptyNode) ? acc.concat(transform(childNode)) : acc;
      }, []);
    }
    return node;
  };
  return transform;
};

/**
 * If the first child of a list item is a list, include it in the previous list
 * item. Otherwise it translates to markdown as having two bullets. When
 * rehype-remark processes a list and finds children that are not list items, it
 * wraps them in list items, which leads to the condition this plugin addresses.
 * Dropbox Paper currently outputs this kind of HTML, which is invalid. We have
 * a support issue open for it, and this plugin can potentially be removed when
 * that's resolved.
 */
const remarkNestedList = () => {
  const transform = node => {
    if (node.type === 'list' && node.children && node.children.length > 1) {
      node.children = node.children.reduce((acc, childNode, index) => {
        if (index && childNode.children && childNode.children[0].type === 'list') {
          acc[acc.length - 1].children.push(transform(childNode.children.shift()))
          if (childNode.children.length) {
            acc.push(transform(childNode));
          }
        } else {
          acc.push(transform(childNode));
        }
        return acc;
      }, []);
      return node;
    }
    if (node.children) {
      node.children = node.children.map(transform);
    }
    return node;
  };
  return transform;
};

/**
 * Dropbox Paper outputs emoji characters as images, and stores the actual
 * emoji character in a `data-emoji-ch` attribute on the image. This plugin
 * replaces the images with the emoji characters.
 */
const rehypePaperEmoji = () => {
  const transform = node => {
    if (node.tagName === 'img' && node.properties.dataEmojiCh) {
      return { type: 'text', value: node.properties.dataEmojiCh };
    }
    node.children = node.children ? node.children.map(transform) : node.children;
    return node;
  };
  return transform;
};

export const markdownToHtml = markdown => {
  const result = unified()
    .use(markdownToRemark, remarkParseConfig)
    .use(remarkToRehype)
    .use(rehypeRemoveEmpty)
    .use(rehypeSanitize)
    .use(rehypeMinifyWhitespace)
    .use(rehypeToHtml)
    .processSync(markdown)
    .contents;
  return result;
}

export const htmlToMarkdown = html => {
  const result = unified()
    .use(htmlToRehype, rehypeParseConfig)
    .use(rehypePaperEmoji)
    .use(rehypeSanitize)
    .use(rehypeRemoveEmpty)
    .use(rehypeMinifyWhitespace)
    .use(rehypeToRemark)
    .use(remarkNestedList)
    .use(remarkToMarkdown, remarkStringifyConfig)
    .processSync(html)
    .contents;
  return result;
};
<|endoftext|>


<commit_before>/*
---

name: Mif.Tree
description: Mif.Tree base Class
license: MIT-Style License (http://mifjs.net/license.txt)
copyright: Anton Samoylov (http://mifjs.net)
authors: Anton Samoylov (http://mifjs.net)
requires: core:1.2.4:*
provides: [Mif.Tree.Node, Mif.Tree.Load, Mif.Tree.Draw, Mif.Tree.Hover, Mif.Tree.Selection, Mif.Tree.Checkbox, Mif.Tree.CookieStorage, Mif.Tree.Drag, Mif.Tree.Drag.Element, Mif.Tree.KeyNav, Mif.Tree.Rename, Mif.Tree.Sort, Mif.Tree.Transform]

...
*/

if(!Mif) var Mif={};
if(!Mif.ids) Mif.ids={};
if(!Mif.id) Mif.id=function(id){
	return Mif.ids[id];
}

Mif.Tree = new Class({

	Implements: [new Events, new Options],

	options:{
		types: {},
		forest: false,
		animateScroll: true,
		height: 18,
		expandTo: true,
		selectable: ['input']
	},

	initialize: function(options) {
		this.setOptions(options);
		$extend(this, {
			types: this.options.types,
			forest: this.options.forest,
			animateScroll: this.options.animateScroll,
			dfltType: this.options.dfltType,
			height: this.options.height,
			container: $(options.container),
			UID: 0,
			key: {},
			expanded: []
		});
		this.defaults={
			name: '',
			cls: '',
			openIcon: 'mif-tree-empty-icon',
			closeIcon: 'mif-tree-empty-icon',
			loadable: false,
			hidden: false
		};
		this.dfltState={
			open: false
		};
		this.$index=[];
		this.updateOpenState();
		if(this.options.expandTo) this.initExpandTo();
		Mif.Tree.UID++;
		this.DOMidPrefix='mif-tree-';
		this.wrapper=new Element('div').addClass('mif-tree-wrapper').injectInside(this.container);
		this.initEvents();
		this.initScroll();
		this.initSelection();
		this.initHover();
		this.addEvent('drawChildren', function(parent){
			var nodes=parent._toggle||[];
			for(var i=0, l=nodes.length; i<l; i++){
				nodes[i].drawToggle();
			}
			parent._toggle=[];
		});
		if (MooTools.version>='1.2.2' && this.options.initialize) this.options.initialize.call(this);
	},

	initEvents: function(){
		this.wrapper.addEvents({
			mousemove: this.mouse.bindWithEvent(this),
			mouseover: this.mouse.bindWithEvent(this),
			mouseout: this.mouse.bindWithEvent(this),
			mouseleave: this.mouseleave.bind(this),
			mousedown: function(event){
				this.fireEvent('mousedown');
				return this.stopSelection(event);
			}.bind(this),
			click: this.toggleClick.bindWithEvent(this),
			dblclick: this.toggleDblclick.bindWithEvent(this)
		});
		if(Browser.Engine.trident){
			this.wrapper.addEvent('selectstart', this.stopSelection.bind(this));
		}
		this.container.addEvent('click', this.focus.bind(this));
		document.addEvent('click', this.blurOnClick.bind(this));
		document.addEvents({
			keydown: this.keyDown.bindWithEvent(this),
			keyup: this.keyUp.bindWithEvent(this)
		});
    },

	stopSelection: function(event){
		var target=$(event.target);
		var selectable=this.options.selectable;
		for(var i=0, l=selectable.length;i<l;i++){
			if(target.match(selectable[i])) return true;
		}
		return false;
	},

	blurOnClick: function(event){
		var target=event.target;
		while(target){
			if(target==this.container) return;
			target=target.parentNode;
		}
		this.blur();
	},

	focus: function(){
		if(this.focused) return this;
		this.focused=true;
		this.container.addClass('mif-tree-focused');
		return this.fireEvent('focus');
	},

	blur: function(){
		if(!this.focused) return this;
		this.focused=false;
		this.container.removeClass('mif-tree-focused');
		return this.fireEvent('blur');
	},

	$getIndex: function(){//return array of visible nodes.
		this.$index=[];
		var node=this.forest ? this.root.getFirst() : this.root;
		var previous=node;
		while(node){
			if(!(previous.hidden && previous.contains(node))){
				if(!node.hidden) this.$index.push(node);
				previous=node;
			}
			node=node._getNextVisible();
		}
	},

	mouseleave: function(){
		this.mouse.coords={x:null,y:null};
		this.mouse.target=false;
		this.mouse.node=false;
		if(this.hover) this.hover();
	},

	mouse: function(event){
		this.mouse.coords=this.getCoords(event);
		var target=this.getTarget(event);
		this.mouse.target=target.target;
		this.mouse.node	= target.node;
	},

	getTarget: function(event){
		var target=event.target, node;
		while(!/mif-tree/.test(target.className)){
			target=target.parentNode;
		}
		var test=target.className.match(/mif-tree-(gadjet)-[^n]|mif-tree-(icon)|mif-tree-(name)|mif-tree-(checkbox)/);
		if(!test){
			var y=this.mouse.coords.y;
			if(y==-1||!this.$index) {
				node=false;
			}else{
				node=this.$index[((y)/this.height).toInt()];
			}
			return {
				node: node,
				target: 'node'
			};
		}
		for(var i=5;i>0;i--){
			if(test[i]){
				var type=test[i];
				break;
			}
		}
		return {
			node: Mif.Tree.Nodes[target.getAttribute('uid')],
			target: type
		};
	},

	getCoords: function(event){
		var position=this.wrapper.getPosition();
		var x=event.client.x-position.x;
		var y=event.client.y-position.y;
		var wrapper=this.wrapper;
		if((y-wrapper.scrollTop>wrapper.clientHeight)||(x-wrapper.scrollLeft>wrapper.clientWidth)){//scroll line
			y=-1;
		};
		return {x:x, y:y};
	},

	keyDown: function(event){
		this.key=event;
		this.key.state='down';
		if(this.focused) this.fireEvent('keydown', [event]);
	},

	keyUp: function(event){
		this.key={};
		this.key.state='up';
		if(this.focused) this.fireEvent('keyup', [event]);
	},

	toggleDblclick: function(event){
		var target=this.mouse.target;
		if(!(target=='name'||target=='icon')) return;
		this.mouse.node.toggle();
	},

	toggleClick: function(event){
		if(this.mouse.target!='gadjet') return;
		this.mouse.node.toggle();
	},

	initScroll: function(){
		this.scroll=new Fx.Scroll(this.wrapper, {link: 'cancel'});
	},

	scrollTo: function(node){
		var position=node.getVisiblePosition();
		var top=position*this.height;
		var up=top<this.wrapper.scrollTop;
		var down=top>(this.wrapper.scrollTop+this.wrapper.clientHeight-this.height);
		if(position==-1 || ( !up && !down ) ) {
			this.scroll.fireEvent('complete');
			return false;
		}
		if(this.animateScroll){
			this.scroll.start(this.wrapper.scrollLeft, top-(down ? this.wrapper.clientHeight-this.height : this.height));
		}else{
			this.scroll.set(this.wrapper.scrollLeft, top-(down ? this.wrapper.clientHeight-this.height : this.height));
			this.scroll.fireEvent('complete');
		}
	},

	updateOpenState: function(){
		this.addEvents({
			'drawChildren': function(parent){
				var children=parent.children;
				for(var i=0, l=children.length; i<l; i++){
					children[i].updateOpenState();
				}
			},
			'drawRoot': function(){
				this.root.updateOpenState();
			}
		});
	},

	expandTo: function(node){
		if (!node) return this;
		var path = [];
		while( !node.isRoot() && !(this.forest && node.getParent().isRoot()) ){
			node=node.getParent();
			if(!node) break;
			path.unshift(node);
		};
		path.each(function(el){
			el.toggle(true)
		});
		return this;
	},

	initExpandTo: function(){
		this.addEvent('loadChildren', function(parent){
			if(!parent) return;
			var children=parent.children;
			for( var i=children.length; i--; ){
				var child=children[i];
				if(child.expandTo) this.expanded.push(child);
			}
		});
		function expand(){
			this.expanded.each(function(node){
				this.expandTo(node);
			}, this);
			this.expanded=[];
		};
		this.addEvents({
			'load': expand.bind(this),
			'loadNode': expand.bind(this)
		});
	}

});
Mif.Tree.UID=0;

Mif.Tree.version='1.2dev';

Array.implement({

	inject: function(added, current, where){//inject added after or before current;
		var pos=this.indexOf(current)+(where=='before' ? 0 : 1);
		for(var i=this.length-1;i>=pos;i--){
			this[i+1]=this[i];
		}
		this[pos]=added;
		return this;
	}

});
<commit_msg>fix version<commit_after>/*
---

name: Mif.Tree
description: Mif.Tree base Class
license: MIT-Style License (http://mifjs.net/license.txt)
copyright: Anton Samoylov (http://mifjs.net)
authors: Anton Samoylov (http://mifjs.net)
requires: core:1.2.4:*
provides: [Mif.Tree.Node, Mif.Tree.Load, Mif.Tree.Draw, Mif.Tree.Hover, Mif.Tree.Selection, Mif.Tree.Checkbox, Mif.Tree.CookieStorage, Mif.Tree.Drag, Mif.Tree.Drag.Element, Mif.Tree.KeyNav, Mif.Tree.Rename, Mif.Tree.Sort, Mif.Tree.Transform]

...
*/

if(!Mif) var Mif={};
if(!Mif.ids) Mif.ids={};
if(!Mif.id) Mif.id=function(id){
	return Mif.ids[id];
}

Mif.Tree = new Class({

	version: 1.2,

	Implements: [new Events, new Options],

	options:{
		types: {},
		forest: false,
		animateScroll: true,
		height: 18,
		expandTo: true,
		selectable: ['input']
	},

	initialize: function(options) {
		this.setOptions(options);
		$extend(this, {
			types: this.options.types,
			forest: this.options.forest,
			animateScroll: this.options.animateScroll,
			dfltType: this.options.dfltType,
			height: this.options.height,
			container: $(options.container),
			UID: 0,
			key: {},
			expanded: []
		});
		this.defaults={
			name: '',
			cls: '',
			openIcon: 'mif-tree-empty-icon',
			closeIcon: 'mif-tree-empty-icon',
			loadable: false,
			hidden: false
		};
		this.dfltState={
			open: false
		};
		this.$index=[];
		this.updateOpenState();
		if(this.options.expandTo) this.initExpandTo();
		Mif.Tree.UID++;
		this.DOMidPrefix='mif-tree-';
		this.wrapper=new Element('div').addClass('mif-tree-wrapper').injectInside(this.container);
		this.initEvents();
		this.initScroll();
		this.initSelection();
		this.initHover();
		this.addEvent('drawChildren', function(parent){
			var nodes=parent._toggle||[];
			for(var i=0, l=nodes.length; i<l; i++){
				nodes[i].drawToggle();
			}
			parent._toggle=[];
		});
		if (MooTools.version>='1.2.2' && this.options.initialize) this.options.initialize.call(this);
	},

	initEvents: function(){
		this.wrapper.addEvents({
			mousemove: this.mouse.bindWithEvent(this),
			mouseover: this.mouse.bindWithEvent(this),
			mouseout: this.mouse.bindWithEvent(this),
			mouseleave: this.mouseleave.bind(this),
			mousedown: function(event){
				this.fireEvent('mousedown');
				return this.stopSelection(event);
			}.bind(this),
			click: this.toggleClick.bindWithEvent(this),
			dblclick: this.toggleDblclick.bindWithEvent(this)
		});
		if(Browser.Engine.trident){
			this.wrapper.addEvent('selectstart', this.stopSelection.bind(this));
		}
		this.container.addEvent('click', this.focus.bind(this));
		document.addEvent('click', this.blurOnClick.bind(this));
		document.addEvents({
			keydown: this.keyDown.bindWithEvent(this),
			keyup: this.keyUp.bindWithEvent(this)
		});
    },

	stopSelection: function(event){
		var target=$(event.target);
		var selectable=this.options.selectable;
		for(var i=0, l=selectable.length;i<l;i++){
			if(target.match(selectable[i])) return true;
		}
		return false;
	},

	blurOnClick: function(event){
		var target=event.target;
		while(target){
			if(target==this.container) return;
			target=target.parentNode;
		}
		this.blur();
	},

	focus: function(){
		if(this.focused) return this;
		this.focused=true;
		this.container.addClass('mif-tree-focused');
		return this.fireEvent('focus');
	},

	blur: function(){
		if(!this.focused) return this;
		this.focused=false;
		this.container.removeClass('mif-tree-focused');
		return this.fireEvent('blur');
	},

	$getIndex: function(){//return array of visible nodes.
		this.$index=[];
		var node=this.forest ? this.root.getFirst() : this.root;
		var previous=node;
		while(node){
			if(!(previous.hidden && previous.contains(node))){
				if(!node.hidden) this.$index.push(node);
				previous=node;
			}
			node=node._getNextVisible();
		}
	},

	mouseleave: function(){
		this.mouse.coords={x:null,y:null};
		this.mouse.target=false;
		this.mouse.node=false;
		if(this.hover) this.hover();
	},

	mouse: function(event){
		this.mouse.coords=this.getCoords(event);
		var target=this.getTarget(event);
		this.mouse.target=target.target;
		this.mouse.node	= target.node;
	},

	getTarget: function(event){
		var target=event.target, node;
		while(!/mif-tree/.test(target.className)){
			target=target.parentNode;
		}
		var test=target.className.match(/mif-tree-(gadjet)-[^n]|mif-tree-(icon)|mif-tree-(name)|mif-tree-(checkbox)/);
		if(!test){
			var y=this.mouse.coords.y;
			if(y==-1||!this.$index) {
				node=false;
			}else{
				node=this.$index[((y)/this.height).toInt()];
			}
			return {
				node: node,
				target: 'node'
			};
		}
		for(var i=5;i>0;i--){
			if(test[i]){
				var type=test[i];
				break;
			}
		}
		return {
			node: Mif.Tree.Nodes[target.getAttribute('uid')],
			target: type
		};
	},

	getCoords: function(event){
		var position=this.wrapper.getPosition();
		var x=event.client.x-position.x;
		var y=event.client.y-position.y;
		var wrapper=this.wrapper;
		if((y-wrapper.scrollTop>wrapper.clientHeight)||(x-wrapper.scrollLeft>wrapper.clientWidth)){//scroll line
			y=-1;
		};
		return {x:x, y:y};
	},

	keyDown: function(event){
		this.key=event;
		this.key.state='down';
		if(this.focused) this.fireEvent('keydown', [event]);
	},

	keyUp: function(event){
		this.key={};
		this.key.state='up';
		if(this.focused) this.fireEvent('keyup', [event]);
	},

	toggleDblclick: function(event){
		var target=this.mouse.target;
		if(!(target=='name'||target=='icon')) return;
		this.mouse.node.toggle();
	},

	toggleClick: function(event){
		if(this.mouse.target!='gadjet') return;
		this.mouse.node.toggle();
	},

	initScroll: function(){
		this.scroll=new Fx.Scroll(this.wrapper, {link: 'cancel'});
	},

	scrollTo: function(node){
		var position=node.getVisiblePosition();
		var top=position*this.height;
		var up=top<this.wrapper.scrollTop;
		var down=top>(this.wrapper.scrollTop+this.wrapper.clientHeight-this.height);
		if(position==-1 || ( !up && !down ) ) {
			this.scroll.fireEvent('complete');
			return false;
		}
		if(this.animateScroll){
			this.scroll.start(this.wrapper.scrollLeft, top-(down ? this.wrapper.clientHeight-this.height : this.height));
		}else{
			this.scroll.set(this.wrapper.scrollLeft, top-(down ? this.wrapper.clientHeight-this.height : this.height));
			this.scroll.fireEvent('complete');
		}
	},

	updateOpenState: function(){
		this.addEvents({
			'drawChildren': function(parent){
				var children=parent.children;
				for(var i=0, l=children.length; i<l; i++){
					children[i].updateOpenState();
				}
			},
			'drawRoot': function(){
				this.root.updateOpenState();
			}
		});
	},

	expandTo: function(node){
		if (!node) return this;
		var path = [];
		while( !node.isRoot() && !(this.forest && node.getParent().isRoot()) ){
			node=node.getParent();
			if(!node) break;
			path.unshift(node);
		};
		path.each(function(el){
			el.toggle(true)
		});
		return this;
	},

	initExpandTo: function(){
		this.addEvent('loadChildren', function(parent){
			if(!parent) return;
			var children=parent.children;
			for( var i=children.length; i--; ){
				var child=children[i];
				if(child.expandTo) this.expanded.push(child);
			}
		});
		function expand(){
			this.expanded.each(function(node){
				this.expandTo(node);
			}, this);
			this.expanded=[];
		};
		this.addEvents({
			'load': expand.bind(this),
			'loadNode': expand.bind(this)
		});
	}

});
Mif.Tree.UID=0;

Array.implement({

	inject: function(added, current, where){//inject added after or before current;
		var pos=this.indexOf(current)+(where=='before' ? 0 : 1);
		for(var i=this.length-1;i>=pos;i--){
			this[i+1]=this[i];
		}
		this[pos]=added;
		return this;
	}

});
<|endoftext|>


<commit_before>var canvasId = 'qpcr-crush';
var AE = new AdamEngine(canvasId);

AE.inputMan.addMBInput(0, 'LEFTCLICK');
AE.inputMan.setup();

AE.assetMan.newAtlas('tiles', '/gfx/atlas.png', '/gfx/atlas.json');

var grid = AE.createWorldObj('grid');

grid.setup = function() {
  this.state.pos.x = 0;
  this.state.pos.y = 0;
  this.state.size.w = 650;
  this.state.size.h = 650;
  this.state.worldObjType = 'invis';
  this.spawnGrid();
};

grid.createGrid = function() {
  if (this.state.grid !== undefined) {
    this.clearGrid();
  }

  this.state.grid = []; // grid visible to players
  this.state.gridCheck = []; // grid used to check if player moves are valid
  this.state.tilesCreated = 0;
  this.state.prevClickedTile = null;
  this.state.swapping = false;

  var currY = 0;
  var tileSize = 70;
  var marginYOffset = 2;
  var tileNames = ['BHQ2.png', 'FAM2.png', 'GOLD2.png', 'ORANGE2.png', 'QUASAR2.png', 'RED2.png'];

  // create tiles within grid
  for(var i=0; i < 9; ++i) {
    var row = [];
    var currX = 0;
    var marginXOffset = 2;

    for(var j=0; j < 9; ++j) {
      ++this.state.tilesCreated;
      var tile = AE.createWorldObj('tile' + this.state.tilesCreated);

      // default state vals
      tile.state.atlas = AE.assetMan.getAtlas('tiles');
      tile.state.pos.x = currX + marginXOffset;
      tile.state.pos.y = currY + marginYOffset;
      tile.state.size.w = tileSize;
      tile.state.size.h = tileSize;
      tile.state.zIndex = 2;
      tile.state.worldObjType = 'img';

      // custom state vals
      tile.state.gridLoc = {x: j, y: i};
      tile.state.moveTo = null;

      // atlas state
      var atlas = tile.state.atlas.data;
      var randNum = Math.floor(Math.random() * (0, 6));
      var randTileName = tileNames[randNum];

      tile.state.tileType = randTileName;
      tile.state.img = tile.state.atlas.img;
      tile.state.imgData = {
        sx: atlas.frames[randTileName].frame.x,
        sy: atlas.frames[randTileName].frame.y,
        sw: atlas.frames[randTileName].sourceSize.w,
        sh: atlas.frames[randTileName].sourceSize.h
      };

      // move loc
      tile.state.moveToLoc = {x: null, y: null};
      tile.moveTo = function(gridLocX, gridLocY) {
        var destination = grid.state.grid[gridLocY][gridLocX];
        tile.state.moveToLoc = destination.state.pos;
        this.update = function() {
          console.log(tile.state.moveToLoc);
        };
      };

      row.push(tile);

      currX += tileSize;
      marginXOffset += 2;
    }

    this.state.grid.push(row);
    currY += tileSize;
    marginYOffset += 2;
  }
};

grid.spawnGrid = function() {
  // keep recreating grid until there isn't a match
  grid.createGrid();
  while(this.findMatches(this.state.grid)) {
    grid.createGrid();
  }

  this.state.gridCheck = this.copyGrid(this.state.grid);
  AE.updateRenderPipe();
};

grid.copyGrid = function(grid) {
  var newGrid = [];
  for(var y=0; y < grid.length; ++y) {
    newGrid[y] = grid[y].slice();
  }

  return newGrid;
};

grid.findMatches = function(grid) {
  var matchesFound = 0;

  // breadth check tiles
  console.log('X check');
  for(var y=0; y < 9; ++y) {
    var prevSameXTiles = [];
    for(var x=0; x < 9; ++x) {
      if(prevSameXTiles.length === 0) {
        prevSameXTiles.push(grid[y][x]);
      } else {
        // there is a match!
        if(prevSameXTiles[0].state.tileType === grid[y][x].state.tileType) {
          prevSameXTiles.push(grid[y][x]);
        } else {
          // match is broken
          if(prevSameXTiles.length > 2) {
            console.log('>3 match occurred');
            for(var i in prevSameXTiles) {
              console.log(prevSameXTiles[i].state.gridLoc.x, prevSameXTiles[i].state.gridLoc.y);
            }
            console.log('\n');

            ++matchesFound;
          }

          prevSameXTiles = [];
          prevSameXTiles.push(grid[y][x]);
        }
      }
    }

    if(prevSameXTiles.length > 2) {
      console.log('>3 match occurred');
      for(var i in prevSameXTiles) {
        console.log(prevSameXTiles[i].state.gridLoc.x, prevSameXTiles[i].state.gridLoc.y);
      }
      console.log('\n');

      ++matchesFound;
    }
  }

  // depth check tiles
  console.log('Y check');
  for(var x=0; x < 9; ++x) {
    var prevSameYTiles = [];
    for(var y=0; y < 9; ++y) {
      if(prevSameYTiles.length === 0) {
        prevSameYTiles.push(grid[y][x]);
      } else {
        // there is a match!
        if(prevSameYTiles[0].state.tileType === grid[y][x].state.tileType) {
          prevSameYTiles.push(grid[y][x]);
        } else {
          // match is broken
          if(prevSameYTiles.length > 2) {
            console.log('>3 match occurred');
            for(var i in prevSameYTiles) {
              console.log(prevSameYTiles[i].state.gridLoc.x, prevSameYTiles[i].state.gridLoc.y);
            }
            console.log('\n');

            ++matchesFound;
          }

          prevSameYTiles = [];
          prevSameYTiles.push(grid[y][x]);
        }
      }
    }

    if(prevSameYTiles.length > 2) {
      console.log('>3 match occurred');
      for(var i in prevSameYTiles) {
        console.log(prevSameYTiles[i].state.gridLoc.x, prevSameYTiles[i].state.gridLoc.y);
      }
      console.log('\n');

      ++matchesFound;
    }
  }

  return matchesFound;
};

grid.deleteTile = function(x, y) {
  var tile = this.state.grid[x][y];
  AE.deleteWorldObj(tile.name);
  this.state.grid[x][y] = null;
  AE.updateRenderPipe();
};

grid.clearGrid = function() {
  for(var y=0; y < 9; ++y) {
    for(var x=0; x < 9; ++x) {
      this.deleteTile(x, y);
    }
  }
};

grid.tileClicked = function() {
  var mousePos = AE.inputMan.getMBState('LEFTCLICK').pos;

  // check if a tile was clicked
  for(var y=0; y < this.state.grid.length; ++y) {
    for(var x=0; x < this.state.grid.length; ++x) {
      var tile = this.state.grid[y][x];

      if(
        (mousePos.x < (tile.state.pos.x + tile.state.size.w)) &&
        (mousePos.x > tile.state.pos.x) &&
        (mousePos.y < (tile.state.pos.y + tile.state.size.h)) &&
        (mousePos.y > tile.state.pos.y)
      ) {
        return tile;
      }
    }
  }
};

grid.validSwap = function(clickedTile, prevClickedTile) {
  if(
    (clickedTile.state.gridLoc.y === (prevClickedTile.state.gridLoc.y - 1) &&
    clickedTile.state.gridLoc.x === (prevClickedTile.state.gridLoc.x)) ||
    (clickedTile.state.gridLoc.x === (prevClickedTile.state.gridLoc.x + 1) &&
    clickedTile.state.gridLoc.y === prevClickedTile.state.gridLoc.y) ||
    (clickedTile.state.gridLoc.y === (prevClickedTile.state.gridLoc.y + 1) &&
    clickedTile.state.gridLoc.x === (prevClickedTile.state.gridLoc.x)) ||
    (clickedTile.state.gridLoc.x === (prevClickedTile.state.gridLoc.x - 1) &&
    clickedTile.state.gridLoc.y === prevClickedTile.state.gridLoc.y)
  ) {
    return true;
  }
  return false;
};

grid.update = function() {
  if(AE.inputMan.getMBState('LEFTCLICK').fullClick) {
    var clickedTile = this.tileClicked();
    var prevClickedTile = this.state.prevClickedTile;

    if(clickedTile) {
      console.log(this.state.grid);
      console.log('clicked a tile');
      // check if clicked tile is within range of prev
      if(prevClickedTile) {
        console.log('prevClickedTile is set');
        this.state.prevClickedTile.state.stroke = null;

        if(this.validSwap(clickedTile, prevClickedTile)) {
          console.log('swap is valid');
          // if player clicked up, right, down, or left of prevClickedTile
          var clickedTileGridLoc = clickedTile.state.gridLoc;
          var prevClickedTileGridLoc = prevClickedTile.state.gridLoc;

          var tempTile = this.state.gridCheck[clickedTileGridLoc.y][clickedTileGridLoc.x];
          this.state.gridCheck[clickedTileGridLoc.y][clickedTileGridLoc.x] = this.state.gridCheck[prevClickedTileGridLoc.y][prevClickedTileGridLoc.x];
          this.state.gridCheck[prevClickedTileGridLoc.y][prevClickedTileGridLoc.x] = tempTile;

          console.log(this.findMatches(this.state.gridCheck));
          console.log(this.state.gridCheck);
          if(this.findMatches(this.state.gridCheck)) {
            console.log('match found');
            // if swap results to a match, updated grid
            var gridCheck = this.state.gridCheck;

            // swap pos
            var newClickedTile = gridCheck[prevClickedTileGridLoc.y][prevClickedTileGridLoc.x];
            var newPrevClickedTile = gridCheck[clickedTileGridLoc.y][clickedTileGridLoc.x];
            var tempPos = {x: newClickedTile.state.pos.x, y: newClickedTile.state.pos.y};
            newClickedTile.state.pos.x = newPrevClickedTile.state.pos.x;
            newClickedTile.state.pos.y = newPrevClickedTile.state.pos.y;
            newPrevClickedTile.state.pos.x = tempPos.x;
            newPrevClickedTile.state.pos.y = tempPos.y;

            // swap gridLoc
            var tempGridLoc = {x: newClickedTile.state.gridLoc.x, y: newClickedTile.state.gridLoc.y};
            newClickedTile.state.gridLoc.x = newPrevClickedTile.state.gridLoc.x;
            newClickedTile.state.gridLoc.y = newPrevClickedTile.state.gridLoc.y;
            newPrevClickedTile.state.gridLoc.x = tempGridLoc.x;
            newPrevClickedTile.state.gridLoc.y = tempGridLoc.y;

            // begin swap animation
            this.state.grid = this.copyGrid(this.state.gridCheck); // upgrade grid with valid gridCheck
          }

          this.state.gridCheck = this.copyGrid(this.state.grid); // sync grid check with grid
          console.log(this.state.gridCheck);
        }
      }

      if(this.state.prevClickedTile) {
        this.state.prevClickedTile = null;
      } else {
        this.state.prevClickedTile = clickedTile;

        clickedTile.state.stroke = {
          pos: clickedTile.state.pos,
          size: {w: clickedTile.state.size.w, h: clickedTile.state.size.h},
          color: '#FF0000'
        };
      }
    }
  }
};

AE.start();<commit_msg>Animate swapping<commit_after>var canvasId = 'qpcr-crush';
var AE = new AdamEngine(canvasId);

AE.inputMan.addMBInput(0, 'LEFTCLICK');
AE.inputMan.setup();

AE.assetMan.newAtlas('tiles', '/gfx/atlas.png', '/gfx/atlas.json');

var grid = AE.createWorldObj('grid');

grid.setup = function() {
  this.state.pos.x = 0;
  this.state.pos.y = 0;
  this.state.size.w = 650;
  this.state.size.h = 650;
  this.state.worldObjType = 'invis';
  this.spawnGrid();
};

grid.createGrid = function() {
  if (this.state.grid !== undefined) {
    this.clearGrid();
  }

  this.state.grid = []; // grid visible to players
  this.state.gridCheck = []; // grid used to check if player moves are valid
  this.state.tilesCreated = 0;
  this.state.prevClickedTile = null;
  this.state.swapping = false;

  var currY = 0;
  var tileSize = 70;
  var marginYOffset = 2;
  var tileNames = ['BHQ2.png', 'FAM2.png', 'GOLD2.png', 'ORANGE2.png', 'QUASAR2.png', 'RED2.png'];

  // create tiles within grid
  for(var i=0; i < 9; ++i) {
    var row = [];
    var currX = 0;
    var marginXOffset = 2;

    for(var j=0; j < 9; ++j) {
      ++this.state.tilesCreated;
      var tile = AE.createWorldObj('tile' + this.state.tilesCreated);

      // default state vals
      tile.state.atlas = AE.assetMan.getAtlas('tiles');
      tile.state.pos.x = currX + marginXOffset;
      tile.state.pos.y = currY + marginYOffset;
      tile.state.size.w = tileSize;
      tile.state.size.h = tileSize;
      tile.state.zIndex = 2;
      tile.state.worldObjType = 'img';

      // custom state vals
      tile.state.gridLoc = {x: j, y: i};

      // atlas state
      var atlas = tile.state.atlas.data;
      var randNum = Math.floor(Math.random() * (0, 6));
      var randTileName = tileNames[randNum];

      tile.state.tileType = randTileName;
      tile.state.img = tile.state.atlas.img;
      tile.state.imgData = {
        sx: atlas.frames[randTileName].frame.x,
        sy: atlas.frames[randTileName].frame.y,
        sw: atlas.frames[randTileName].sourceSize.w,
        sh: atlas.frames[randTileName].sourceSize.h
      };

      // move loc
      tile.state.moveTo = null;
      tile.update = function() {
        if(grid.state.swapping && (this.state.moveTo !== null)) {
          console.log('yeeeee');
          // move tile to moveTo destination
          if(this.state.pos.x < this.state.moveTo.x) {
            this.state.pos.x = this.state.pos.x + 2;
          } else if(this.state.pos.x > this.state.moveTo.x) {
            this.state.pos.x = this.state.pos.x - 2;
          }

          if(this.state.pos.y < this.state.moveTo.y) {
            this.state.pos.y = this.state.pos.y + 2;
          } else if(this.state.pos.y > this.state.moveTo.y) {
            this.state.pos.y = this.state.pos.y - 2;
          }

          if((this.state.pos.x === this.state.moveTo.x) && (this.state.pos.y === this.state.moveTo.y)) {
            this.state.moveTo = null;
            grid.state.swapping = false;
            grid.finishSwap();
          }
        }
      }
      /*tile.moveTo = function(gridLocX, gridLocY) {
        var destination = grid.state.grid[gridLocY][gridLocX];
        tile.state.moveToLoc = destination.state.pos;
        this.update = function() {
          console.log(tile.state.moveToLoc);
        };
      };*/

      row.push(tile);

      currX += tileSize;
      marginXOffset += 2;
    }

    this.state.grid.push(row);
    currY += tileSize;
    marginYOffset += 2;
  }
};

grid.spawnGrid = function() {
  // keep recreating grid until there isn't a match
  grid.createGrid();
  while(this.findMatches(this.state.grid)) {
    grid.createGrid();
  }

  this.state.gridCheck = this.copyGrid(this.state.grid);
  AE.updateRenderPipe();
};

grid.copyGrid = function(grid) {
  var newGrid = [];
  for(var y=0; y < grid.length; ++y) {
    newGrid[y] = grid[y].slice();
  }

  return newGrid;
};

grid.findMatches = function(grid) {
  var matchesFound = 0;

  // breadth check tiles
  console.log('X check');
  for(var y=0; y < 9; ++y) {
    var prevSameXTiles = [];
    for(var x=0; x < 9; ++x) {
      if(prevSameXTiles.length === 0) {
        prevSameXTiles.push(grid[y][x]);
      } else {
        // there is a match!
        if(prevSameXTiles[0].state.tileType === grid[y][x].state.tileType) {
          prevSameXTiles.push(grid[y][x]);
        } else {
          // match is broken
          if(prevSameXTiles.length > 2) {
            console.log('>3 match occurred');
            for(var i in prevSameXTiles) {
              console.log(prevSameXTiles[i].state.gridLoc.x, prevSameXTiles[i].state.gridLoc.y);
            }
            console.log('\n');

            ++matchesFound;
          }

          prevSameXTiles = [];
          prevSameXTiles.push(grid[y][x]);
        }
      }
    }

    if(prevSameXTiles.length > 2) {
      console.log('>3 match occurred');
      for(var i in prevSameXTiles) {
        console.log(prevSameXTiles[i].state.gridLoc.x, prevSameXTiles[i].state.gridLoc.y);
      }
      console.log('\n');

      ++matchesFound;
    }
  }

  // depth check tiles
  console.log('Y check');
  for(var x=0; x < 9; ++x) {
    var prevSameYTiles = [];
    for(var y=0; y < 9; ++y) {
      if(prevSameYTiles.length === 0) {
        prevSameYTiles.push(grid[y][x]);
      } else {
        // there is a match!
        if(prevSameYTiles[0].state.tileType === grid[y][x].state.tileType) {
          prevSameYTiles.push(grid[y][x]);
        } else {
          // match is broken
          if(prevSameYTiles.length > 2) {
            console.log('>3 match occurred');
            for(var i in prevSameYTiles) {
              console.log(prevSameYTiles[i].state.gridLoc.x, prevSameYTiles[i].state.gridLoc.y);
            }
            console.log('\n');

            ++matchesFound;
          }

          prevSameYTiles = [];
          prevSameYTiles.push(grid[y][x]);
        }
      }
    }

    if(prevSameYTiles.length > 2) {
      console.log('>3 match occurred');
      for(var i in prevSameYTiles) {
        console.log(prevSameYTiles[i].state.gridLoc.x, prevSameYTiles[i].state.gridLoc.y);
      }
      console.log('\n');

      ++matchesFound;
    }
  }

  return matchesFound;
};

grid.deleteTile = function(x, y) {
  var tile = this.state.grid[x][y];
  AE.deleteWorldObj(tile.name);
  this.state.grid[x][y] = null;
  AE.updateRenderPipe();
};

grid.clearGrid = function() {
  for(var y=0; y < 9; ++y) {
    for(var x=0; x < 9; ++x) {
      this.deleteTile(x, y);
    }
  }
};

grid.tileClicked = function() {
  var mousePos = AE.inputMan.getMBState('LEFTCLICK').pos;

  // check if a tile was clicked
  for(var y=0; y < this.state.grid.length; ++y) {
    for(var x=0; x < this.state.grid.length; ++x) {
      var tile = this.state.grid[y][x];

      if(
        (mousePos.x < (tile.state.pos.x + tile.state.size.w)) &&
        (mousePos.x > tile.state.pos.x) &&
        (mousePos.y < (tile.state.pos.y + tile.state.size.h)) &&
        (mousePos.y > tile.state.pos.y)
      ) {
        return tile;
      }
    }
  }
};

grid.validSwap = function(clickedTile, prevClickedTile) {
  if(
    (clickedTile.state.gridLoc.y === (prevClickedTile.state.gridLoc.y - 1) &&
    clickedTile.state.gridLoc.x === (prevClickedTile.state.gridLoc.x)) ||
    (clickedTile.state.gridLoc.x === (prevClickedTile.state.gridLoc.x + 1) &&
    clickedTile.state.gridLoc.y === prevClickedTile.state.gridLoc.y) ||
    (clickedTile.state.gridLoc.y === (prevClickedTile.state.gridLoc.y + 1) &&
    clickedTile.state.gridLoc.x === (prevClickedTile.state.gridLoc.x)) ||
    (clickedTile.state.gridLoc.x === (prevClickedTile.state.gridLoc.x - 1) &&
    clickedTile.state.gridLoc.y === prevClickedTile.state.gridLoc.y)
  ) {
    return true;
  }
  return false;
};

grid.update = function() {
  if(AE.inputMan.getMBState('LEFTCLICK').fullClick) {
    var clickedTile = this.tileClicked();
    var prevClickedTile = this.state.prevClickedTile;

    if(clickedTile) {
      console.log(this.state.grid);
      console.log('clicked a tile');
      // check if clicked tile is within range of prev
      if(prevClickedTile) {
        console.log('prevClickedTile is set');
        this.state.prevClickedTile.state.stroke = null;

        if(this.validSwap(clickedTile, prevClickedTile)) {
          console.log('swap is valid');
          // if player clicked up, right, down, or left of prevClickedTile
          var clickedTileGridLoc = clickedTile.state.gridLoc;
          var prevClickedTileGridLoc = prevClickedTile.state.gridLoc;

          var tempTile = this.state.gridCheck[clickedTileGridLoc.y][clickedTileGridLoc.x];
          this.state.gridCheck[clickedTileGridLoc.y][clickedTileGridLoc.x] = this.state.gridCheck[prevClickedTileGridLoc.y][prevClickedTileGridLoc.x];
          this.state.gridCheck[prevClickedTileGridLoc.y][prevClickedTileGridLoc.x] = tempTile;

          console.log(this.findMatches(this.state.gridCheck));
          console.log(this.state.gridCheck);
          if(this.findMatches(this.state.gridCheck)) {
            console.log('match found');
            // if swap results to a match, updated grid
            var grid = this.state.grid;
            var gridCheck = this.state.gridCheck;

            var newClickedTile = gridCheck[prevClickedTileGridLoc.y][prevClickedTileGridLoc.x];
            var newPrevClickedTile = gridCheck[clickedTileGridLoc.y][clickedTileGridLoc.x];

            // swap pos
            this.test = function() {
              console.log('hiya');
            };

            // begin swap animation
            var tile1 = grid[clickedTileGridLoc.y][clickedTileGridLoc.x];
            var tile2 = grid[prevClickedTileGridLoc.y][prevClickedTileGridLoc.x];

            this.state.swapping = {tile1: tile1, tile2: tile2};
            tile1.state.moveTo = {x: tile2.state.pos.x, y: tile2.state.pos.y};
            tile2.state.moveTo = {x: tile1.state.pos.x, y: tile1.state.pos.y};
            this.finishSwap = function() {
              // var tempPos = {x: newClickedTile.state.pos.x, y: newClickedTile.state.pos.y};
              // newClickedTile.state.pos.x = newPrevClickedTile.state.pos.x;
              // newClickedTile.state.pos.y = newPrevClickedTile.state.pos.y;
              // newPrevClickedTile.state.pos.x = tempPos.x;
              // newPrevClickedTile.state.pos.y = tempPos.y;

              // swap gridLoc
              var tempGridLoc = {x: newClickedTile.state.gridLoc.x, y: newClickedTile.state.gridLoc.y};
              newClickedTile.state.gridLoc.x = newPrevClickedTile.state.gridLoc.x;
              newClickedTile.state.gridLoc.y = newPrevClickedTile.state.gridLoc.y;
              newPrevClickedTile.state.gridLoc.x = tempGridLoc.x;
              newPrevClickedTile.state.gridLoc.y = tempGridLoc.y;

              this.state.grid = this.copyGrid(this.state.gridCheck); // update grid with valid gridCheck
            };
          } else {
            this.state.gridCheck = this.copyGrid(this.state.grid); // sync grid check with grid
          }
        }
      }

      if(this.state.prevClickedTile) {
        this.state.prevClickedTile = null;
      } else {
        this.state.prevClickedTile = clickedTile;

        clickedTile.state.stroke = {
          pos: clickedTile.state.pos,
          size: {w: clickedTile.state.size.w, h: clickedTile.state.size.h},
          color: '#FF0000'
        };
      }
    }
  }
};

AE.start();<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import { FormattedMessage, injectIntl } from 'react-intl';
import messages from '../../../resources/messages';
import UserMenuContainer from './UserMenuContainer';

export const TOPICS_URL = 'https://topics.mediacloud.org/';
export const EXPLORER_URL = 'https://explorer.mediacloud.org/';
export const SOURCES_URL = 'https://sources.mediacloud.org/';
export const BLOG_URL = 'http://mediacloud.org/';
export const TOOLS_URL = 'https://tools.mediacloud.org/';

const AppToolbar = (props) => {
  const { backgroundColor, drawer } = props;
  const { formatMessage } = props.intl;
  const styles = { backgroundColor };
  return (

      <Grid>
        <Row>
          <Col lg={10}>

                  <FormattedMessage {...messages.toolsAppName} />

                  <FormattedMessage {...messages.explorerToolName} />

                  <FormattedMessage {...messages.topicsToolName} />

                  <FormattedMessage {...messages.sourcesToolName} />

                  <FormattedMessage {...messages.blogToolName} />

          </Col>
          <Col lg={2}>

              <UserMenuContainer />
              {drawer}

          </Col>
        </Row>
      </Grid>

  );
};

AppToolbar.propTypes = {
  // from composition chain
  intl: PropTypes.object.isRequired,
  // from parent
  backgroundColor: PropTypes.string.isRequired,
  drawer: PropTypes.node,
};

export default
  injectIntl(
    AppToolbar
  );
<commit_msg>Revert "Revert "Fix toolbar back to dashboard for builds""<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import { FormattedMessage, injectIntl } from 'react-intl';
import messages from '../../../resources/messages';
import UserMenuContainer from './UserMenuContainer';

export const TOPICS_URL = 'https://topics.mediacloud.org/';
export const EXPLORER_URL = 'https://explorer.mediacloud.org/';
export const DASHBOARD_URL = 'https://dashboard.mediacloud.org/';
export const SOURCES_URL = 'https://sources.mediacloud.org/';
export const BLOG_URL = 'http://mediacloud.org/';
export const TOOLS_URL = 'https://tools.mediacloud.org/';

const AppToolbar = (props) => {
  const { backgroundColor, drawer } = props;
  const { formatMessage } = props.intl;
  const styles = { backgroundColor };
  return (

      <Grid>
        <Row>
          <Col lg={10}>

                  <FormattedMessage {...messages.toolsAppName} />

              {
              /*

                  <FormattedMessage {...messages.explorerToolName} />

              */
              }

                  <FormattedMessage {...messages.dashboardToolName} />

                  <FormattedMessage {...messages.topicsToolName} />

                  <FormattedMessage {...messages.sourcesToolName} />

                  <FormattedMessage {...messages.blogToolName} />

          </Col>
          <Col lg={2}>

              <UserMenuContainer />
              {drawer}

          </Col>
        </Row>
      </Grid>

  );
};

AppToolbar.propTypes = {
  // from composition chain
  intl: PropTypes.object.isRequired,
  // from parent
  backgroundColor: PropTypes.string.isRequired,
  drawer: PropTypes.node,
};

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


<commit_before>/*
---

name: Entity

description: Provides the base class for every objects that are displayed
             through an element.

license: MIT-style license.

authors:
	- Jean-Philippe Dery (jeanphilippe.dery@gmail.com)

requires:
	- Core/Class
	- Core/Class.Extras
	- Class-Extras/Class.Binds

provides:
	- Entity

...
*/

if (!window.Moobile) window.Moobile = {};

Moobile.Entity = new Class({

	Implements: [
		Events,
		Options,
		Class.Binds
	],

	name: null,

	started: false,

	element: null,

	children: [],

	owner: null,

	window: null,

	options: {
		className: null,
		tagName: 'div',
	},

	initialize: function(element, options, name) {

		this.name = name;

		this.setOptions(options);

		this.element = document.id(element);
		if (this.element == null) {
			this.element = new Element(this.options.tagName);
			if (typeof element == 'string') {
				this.element.set('html', element);
				var element = this.element.getChildren();
				if (children.length == 1) {
					this.element = children[0];
				}
			}
		}

		this.build();

		var className = this.options.className;
		if (className) {
			this.element.addClass(className);
		}

		return this;
	},

	startup: function() {

		if (this.started)
			return this;

		this.setup();
		this.attachEvents();
		this.started = true;

		return this;
	},

	destroy: function() {

		if (!this.started)
			return this;

		this.detachEvents();
		this.destroyChildren();
		this.teardown();

		this.removeFromParent();

		this.element.destroy();
		this.element = null;
		this.window = null;

		this.started = false;

		return this;
	},

	build: function() {
		return this;
	},

	setup: function() {
		return this;
	},

	teardown: function() {
		return this;
	},

	attachEvents: function() {
		this.element.addEvent('click', this.bound('onClick'));
		this.element.addEvent('mouseup', this.bound('onMouseUp'))
		this.element.addEvent('mousedown', this.bound('onMouseDown'));
		return this;
	},

	detachEvents: function() {
		this.element.removeEvent('click', this.bound('onClick'));
		this.element.removeEvent('mouseup', this.bound('onMouseUp'));
		this.element.removeEvent('mousedown', this.bound('onMouseDown'));
		return this;
	},

	addChild: function(child, where, relative) {

		if (this.children.contains(child))
			return this;

		this.willAddChild(child);
		child.ownerWillChange(this);
		child.setOwner(this);
		child.setWindow(this.window);

		var element = child.getElement();

		if (!this.element.contains(element)) {

			var context = document.id(relative);
			if (context == null) {
				context = this.element;
			} else if (!this.element.contains(context)) {
				throw new Error('You are trying to add a child relative to an element that does not belong to this entity');
			}

			element.inject(context, where);
		}

		this.children.push(child);
		child.ownerDidChange(this);
		child.startup();
		this.didAddChild(child);

		return this;
	},

	getChild: function(name) {
		return this.children.find(function(children) {
			return children.getName() == name;
		});
	},

	getChildAt: function(index) {
		return this.children[index] || null;
	},

	replaceChild: function(replace, child) {
		return this.addChild(child, 'before', replace).removeChild(replace);
	},

	removeChild: function(child) {

		if (!this.children.contains(child))
			return this;

		this.willRemoveChild(child);
		child.ownerWillChange(null);
		child.setOwner(null);
		child.setWindow(null);
		child.ownerDidChange(null);

		child.getElement()
		     .dispose();

		this.children.erase(child);
		this.didRemoveChild(child);

		return this;
	},

	removeFromParent: function() {
		var parent = this.owner || this.window;
		if (parent) parent.removeChild(this);
		return this;
	},

	destroyChildren: function() {
		this.children.each(this.bound('destroyChild'));
		this.children.empty();
		return this;
	},

	destroyChild: function(child) {
		child.destroy();
		child = null;
		return this;
	},

	show: function() {
		this.willShow();
		this.element.show();
		this.didShow();
		return this;
	},

	hide: function() {
		this.willHide();
		this.element.hide();
		this.didHide();
		return this;
	},

	setWindow: function(window) {
		this.window = window;
		return this;
	},

	getWindow: function(window) {
		return this.window;
	},

	getName: function() {
		return this.name;
	},

	getElement: function(selector) {
		return selector
			? this.element.getElement(selector)
			: this.element;
	},

	getElements: function(selector) {
		return this.element.getElements(selector);
	},

	setOwner: function(owner) {
		this.owner = owner;
		return this;
	},

	getOwner: function() {
		return this.owner;
	},

	getChildren: function() {
		return this.children;
	},

	getSize: function() {
		return this.element.getSize();
	},

	ownerWillChange: function(owner) {
		return this;
	},

	ownerDidChange: function(owner) {
		return this;
	},

	willAddChild: function(child) {
		return this;
	},

	didAddChild: function(child) {
		return this;
	},

	willRemoveChild: function(child) {
		return this;
	},

	didRemoveChild: function(child) {
		return this;
	},

	willShow: function() {
		return this;
	},

	didShow: function() {
		return this;
	},

	willHide: function() {
		return this;
	},

	didHide: function() {
		return this;
	},

	onClick: function(e) {
		e.target = this;
		this.fireEvent('click', e);
		return this;
	},

	onMouseUp: function(e) {
		e.target = this;
		this.fireEvent('mouseup', e);
		return this;
	},

	onMouseDown: function(e) {
		e.target = this;
		this.fireEvent('mousedown', e);
		return this;
	},

	toElement: function() {
		return this.element;
	}

});<commit_msg>Simplified the Entity class<commit_after>/*
---

name: Entity

description: Provides the base class for every objects that are displayed
             through an element.

license: MIT-style license.

authors:
	- Jean-Philippe Dery (jeanphilippe.dery@gmail.com)

requires:
	- Core/Class
	- Core/Class.Extras
	- Class-Extras/Class.Binds

provides:
	- Entity

...
*/

if (!window.Moobile) window.Moobile = {};

Moobile.Entity = new Class({

	Implements: [
		Events,
		Options,
		Class.Binds
	],

	name: null,

	element: null,

	children: [],

	owner: null,

	window: null,

	options: {
		className: null,
		tagName: 'div',
	},

	initialize: function(element, options, name) {

		this.name = name;

		this.setOptions(options);
		this.setElement(element);

		var className = this.options.className;
		if (className) {
			this.element.addClass(className);
		}

		this.setup();
		this.attachEvents();

		return this;
	},

	setup: function() {
		return this;
	},

	teardown: function() {
		return this;
	},

	destroy: function() {

		this.detachEvents();

		this.children.each(function(child){child.destroy()});
		this.children = null;

		this.teardown();

		this.removeFromParent();

		this.element.destroy();
		this.element = null;
		this.window = null;

		return this;
	},

	attachEvents: function() {
		this.element.addEvent('click', this.bound('onClick'));
		this.element.addEvent('mouseup', this.bound('onMouseUp'))
		this.element.addEvent('mousedown', this.bound('onMouseDown'));
		return this;
	},

	detachEvents: function() {
		this.element.removeEvent('click', this.bound('onClick'));
		this.element.removeEvent('mouseup', this.bound('onMouseUp'));
		this.element.removeEvent('mousedown', this.bound('onMouseDown'));
		return this;
	},

	addChild: function(child, where, relative) {

		if (this.children.contains(child))
			return this;

		this.willAddChild(child);

		var element = child.getElement();

		if (!this.element.contains(element)) {

			var context = document.id(relative);
			if (context == null) {
				context = this.element;
			} else if (!this.element.contains(context)) {
				throw new Error('You are trying to add a child relative to an element that does not belong to this entity');
			}

			element.inject(context, where);
		}

		this.children.push(child);

		child.ownerWillChange(this);
		child.setOwner(this);
		child.setWindow(this.window);
		child.ownerDidChange(this);

		this.didAddChild(child);

		return this;
	},

	getChild: function(name) {
		return this.children.find(function(children) {
			return children.getName() == name;
		});
	},

	getChildAt: function(index) {
		return this.children[index] || null;
	},

	replaceChild: function(replace, child) {
		return this.addChild(child, 'before', replace).removeChild(replace);
	},

	removeChild: function(child) {

		if (!this.children.contains(child))
			return this;

		this.willRemoveChild(child);
		child.ownerWillChange(null);
		child.setOwner(null);
		child.setWindow(null);
		child.ownerDidChange(null);

		child.getElement().dispose();

		this.children.erase(child);
		this.didRemoveChild(child);

		return this;
	},

	removeFromParent: function() {
		var parent = this.owner || this.window;
		if (parent) parent.removeChild(this);
		return this;
	},

	show: function() {
		this.willShow();
		this.element.show();
		this.didShow();
		return this;
	},

	hide: function() {
		this.willHide();
		this.element.hide();
		this.didHide();
		return this;
	},

	setWindow: function(window) {
		this.window = window;
		return this;
	},

	getWindow: function(window) {
		return this.window;
	},

	getName: function() {
		return this.name;
	},

	setElement: function(element) {

		if (this.element)
			return;

		var root = document.id(element);
		if (root == null) {
			root = new Element(this.options.tagName);
			if (typeof element == 'string' && element.trim().match(/^<\s*([^ >]+)[^>]*>.*?<\/\s*\1\s*>$/mig)) {
				root = root.set('html', element).getElement(':first-child');
			}
		}

		this.element = root;

		return this;
	},

	getElement: function(selector) {
		return selector
			? this.element.getElement(selector)
			: this.element;
	},

	getElements: function(selector) {
		return this.element.getElements(selector);
	},

	setOwner: function(owner) {
		this.owner = owner;
		return this;
	},

	getOwner: function() {
		return this.owner;
	},

	getChildren: function() {
		return this.children;
	},

	getSize: function() {
		return this.element.getSize();
	},

	ownerWillChange: function(owner) {
		return this;
	},

	ownerDidChange: function(owner) {
		return this;
	},

	willAddChild: function(child) {
		return this;
	},

	didAddChild: function(child) {
		return this;
	},

	willRemoveChild: function(child) {
		return this;
	},

	didRemoveChild: function(child) {
		return this;
	},

	willShow: function() {
		return this;
	},

	didShow: function() {
		return this;
	},

	willHide: function() {
		return this;
	},

	didHide: function() {
		return this;
	},

	onClick: function(e) {
		e.target = this;
		this.fireEvent('click', e);
		return this;
	},

	onMouseUp: function(e) {
		e.target = this;
		this.fireEvent('mouseup', e);
		return this;
	},

	onMouseDown: function(e) {
		e.target = this;
		this.fireEvent('mousedown', e);
		return this;
	},

	toElement: function() {
		return this.element;
	}

});<|endoftext|>