<commit_before>Ext2.namespace('Kwf.Menu');

Kwf.Menu.Index = Ext2.extend(Ext2.Toolbar,
{
    controllerUrl: '/kwf/user/menu',
    changeUserUrl: '/kwf/user/changeUser',
    changeUserTpl: ['<tpl for=".">',
                        '
',
                            '
{lastname:htmlEncode} {firstname:htmlEncode}
',
                            '{email:htmlEncode} ({role:htmlEncode})',
                        '
',
                      '</tpl>'],
    changeUserHeight: 350,
    changeUserWidth: 280,

    initComponent : function()
    {
        this.addEvents(
            'menuevent'
        );
        Kwf.Menu.Index.superclass.initComponent.call(this);

    },
    afterRender: function() {
        Kwf.Menu.Index.superclass.afterRender.call(this);
        this.reload();
    },
    reload: function()
    {
        Ext2.Ajax.request({
            url: this.controllerUrl+'/json-data',
            params: this.params,
            success: this.loadMenu,
            scope: this
        });
    },
    _processMenus: function(menus)
    {
        var menuItems = [];
        for (var i=0; i<menus.length; i++) {
            var m = menus[i];
            var subMenu = m.menuConfig;
            if (m.type == 'dropdown') {
                var childMenuItems = this._processMenus(m.children);
                var menu = new Ext2.menu.Menu({
                    items: childMenuItems
                });
                subMenu.menu = menu;
            } else if (m.type == 'url') {
                subMenu.href = m.url;
                subMenu.handler = function(o, e) {
                    e.stopEvent();
                    var cb = function() {
                        if (top != window) {
                            top.location.href = o.href;
                        } else {
                            location.href = o.href;
                        }
                    };
                    if (!Kwf.currentViewport || !Kwf.currentViewport.mabySubmit) {
                        cb();
                    } else {
                        if (Kwf.currentViewport.mabySubmit({
                            callback: cb,
                            scope: this
                        })) {
                            cb();
                        }
                    }
                };
                subMenu.scope = this;
            } else if (m.type == 'event') {
                subMenu.handler = function(o) {
                    if(o.eventConfig && !o.eventConfig.title) o.eventConfig.title = o.text;
                    this.fireEvent('menuevent', o.eventConfig);
                };
                subMenu.scope = this;
                subMenu.eventConfig = m.eventConfig;
            } else if (m.type == 'commandDialog') {
                subMenu.handler = function(o) {
                    var c = eval(o.commandClass);
                    var dlg = new c(o.commandConfig);
                    dlg.show();
                };
                subMenu.scope = this;
                subMenu.commandClass = m.commandClass;
                subMenu.commandConfig = m.commandConfig;
                if (m.command != undefined) {
                    eval(m.command + '();');
                }
            } else if (m.type == 'command') {
                subMenu.handler = function(o) {
                    Kwf.currentViewport.remove(Kwf.currentViewport.items.item('mainPanel'));
                    var c = eval(o.commandClass);
                    var panel = new c(o.commandConfig);
                    panel.region = 'center';
                    panel.id = 'mainPanel';
                    Kwf.currentViewport.add(panel);
                    Kwf.currentViewport.layout.rendered = false;
                    Kwf.currentViewport.doLayout();
                };
                subMenu.scope = this;
                subMenu.commandClass = m.commandClass;
                subMenu.commandConfig = m.commandConfig;
            } else if (m.type == 'separator') {
                subMenu = '-';
            } else {
                throw "unknown menu-type: "+m.type;
            }

            menuItems.push(subMenu);
        }
        return menuItems;
    },
    loadMenu: function(response, options, result)
    {
        this.items.each(function(i) {
            i.destroy();
        });
        var menuItems = this._processMenus(result.menus);
        menuItems.each(function(menuItem) {
            if (menuItem.icon && menuItem.text) {
                menuItem.cls = 'x2-btn-text-icon';
            } else if (menuItem.icon) {
                menuItem.cls = 'x2-btn-icon';
            }
            this.add(menuItem);
        }, this);

        this.add(new Ext2.Toolbar.Fill());

        this.showUserMenu = new Ext2.Button({
            id: 'userMenu',
            tooltip: trlKwf('Show User Menu'),
            cls: 'x2-btn-icon',
            icon: '/assets/silkicons/bullet_arrow_down.png',
            handler: function() {
                if (!this.userToolbar.isVisible()) {
                    this.showUserMenu.btnEl.setStyle('background-image', 'url(/assets/silkicons/bullet_arrow_up.png)');
                    this.userToolbar.show();
                } else {
                    this.showUserMenu.btnEl.setStyle('background-image', 'url(/assets/silkicons/bullet_arrow_down.png)');
                    this.userToolbar.hide();
                }
            },
            scope: this
        });
        this.add(this.showUserMenu);

        if (result.changeUser) {
            var changeUser = new Kwf.Form.ComboBox({
                store: {
                    url: this.changeUserUrl+'/json-data'
                },
                mode: 'remote',
                editable: true,
                forceSelection: true,
                pageSize: 10,
                triggerAction: 'all',
                width: 120,
                maxHeight: this.changeUserHeight,
                listWidth: this.changeUserWidth,
                tpl: new Ext2.XTemplate(
                        this.changeUserTpl
                      )
            });
            changeUser.on('render', function(combo) {
                combo.setRawValue(result.fullname);
            }, this, {delay: 10});
            changeUser.on('select', function(combo, record, index) {
                Ext2.Ajax.request({
                    url: this.changeUserUrl+'/json-change-user',
                    params: { userId: record.id },
                    success: function() {
                        location.href = '/kwf/welcome';
                    },
                    scope: this
                });
            }, this);
            this.add(changeUser);
            this.add(' ');
            this.add(' ');
            this.add('-');
        }

        this.userToolbar = new Ext2.Toolbar({
            renderTo: this.el,
            style: 'position:absolute;right:0'
        });

        if (result.fullname && result.userSelfControllerUrl) {
            this.userToolbar.add({
                id: 'currentUser',
                text: result.fullname,
                cls: 'x2-btn-text-icon',
                icon: '/assets/silkicons/user.png',
                disabled: !result.userId,
                handler: function() {
                    var dlg = new Kwf.Auto.Form.Window({
                        formConfig: {
                            controllerUrl: result.userSelfControllerUrl
                        }
                    });
                    dlg.on('datachange', function() {
                        this.reload();
                    }, this);
                    dlg.showEdit(result.userId);
                },
                scope: this
            });
        }
        if (result.showLogout) {
            this.userToolbar.add({
                cls: 'x2-btn-icon',
                tooltip: trlKwf('Logout'),
                icon: '/assets/silkicons/door_out.png',
                handler: function() {
                    Ext2.Ajax.request({
                        url : '/kwf/user/login/json-logout-user',
                        success : function(form, action) {
                            //nicht reload, weil user nach erneutem login vielleicht
                            //die aktuelle seite gar nicht mehr sehen darf
                            location.href = '/kwf/welcome';
                        },
                        scope: this
                    });
                },
                scope: this
            });
        }
        this.userToolbar.add({
            cls: 'x2-btn-icon',
            icon: '/assets/kwf/images/information.png',
            tooltip: trlKwf('Information'),
            handler: function() {
                var about = new Kwf.About();
                about.show();
            },
            scope: this
        });

        this.userToolbar.hide();

        if (result.frontendUrls.length == 1) {
            //single frontend urls
            this.add({
                tooltip: trlKwf('Open frontend in a new window'),
                cls: 'x2-btn-icon',
                icon: '/assets/silkicons/world.png',
                handler: function() {
                    window.open(result.frontendUrls[0].href);
                },
                scope: this
            });
        } else if (result.frontendUrls.length > 1) {
            //multiple frontend urls
            var frontendItems = [];
            result.frontendUrls.each(function(url) {
                frontendItems.push({
                    text: url.text,
                    cls: 'x2-btn-text-icon',
                    icon: '/assets/silkicons/world.png',
                    tooltip: trlKwf('Open frontend in a new window'),
                    handler: function(options) {
                        window.open(options.url.href);
                    },
                    url: url,
                    scope: this
                });
            }, this);
            this.add({
                tooltip: trlKwf('Open frontend in a new window'),
                cls: 'x2-btn-icon',
                icon: '/assets/silkicons/world.png',
                menu: new Ext2.menu.Menu({
                    items: frontendItems
                })
            });
        }
    }
});
Ext2.reg('kwf.menu', Kwf.Menu.Index);
<commit_msg>escape username in backend to prevent xss vulnerability<commit_after>Ext2.namespace('Kwf.Menu');

Kwf.Menu.Index = Ext2.extend(Ext2.Toolbar,
{
    controllerUrl: '/kwf/user/menu',
    changeUserUrl: '/kwf/user/changeUser',
    changeUserTpl: ['<tpl for=".">',
                        '
',
                            '
{lastname:htmlEncode} {firstname:htmlEncode}
',
                            '{email:htmlEncode} ({role:htmlEncode})',
                        '
',
                      '</tpl>'],
    changeUserHeight: 350,
    changeUserWidth: 280,

    initComponent : function()
    {
        this.addEvents(
            'menuevent'
        );
        Kwf.Menu.Index.superclass.initComponent.call(this);

    },
    afterRender: function() {
        Kwf.Menu.Index.superclass.afterRender.call(this);
        this.reload();
    },
    reload: function()
    {
        Ext2.Ajax.request({
            url: this.controllerUrl+'/json-data',
            params: this.params,
            success: this.loadMenu,
            scope: this
        });
    },
    _processMenus: function(menus)
    {
        var menuItems = [];
        for (var i=0; i<menus.length; i++) {
            var m = menus[i];
            var subMenu = m.menuConfig;
            if (m.type == 'dropdown') {
                var childMenuItems = this._processMenus(m.children);
                var menu = new Ext2.menu.Menu({
                    items: childMenuItems
                });
                subMenu.menu = menu;
            } else if (m.type == 'url') {
                subMenu.href = m.url;
                subMenu.handler = function(o, e) {
                    e.stopEvent();
                    var cb = function() {
                        if (top != window) {
                            top.location.href = o.href;
                        } else {
                            location.href = o.href;
                        }
                    };
                    if (!Kwf.currentViewport || !Kwf.currentViewport.mabySubmit) {
                        cb();
                    } else {
                        if (Kwf.currentViewport.mabySubmit({
                            callback: cb,
                            scope: this
                        })) {
                            cb();
                        }
                    }
                };
                subMenu.scope = this;
            } else if (m.type == 'event') {
                subMenu.handler = function(o) {
                    if(o.eventConfig && !o.eventConfig.title) o.eventConfig.title = o.text;
                    this.fireEvent('menuevent', o.eventConfig);
                };
                subMenu.scope = this;
                subMenu.eventConfig = m.eventConfig;
            } else if (m.type == 'commandDialog') {
                subMenu.handler = function(o) {
                    var c = eval(o.commandClass);
                    var dlg = new c(o.commandConfig);
                    dlg.show();
                };
                subMenu.scope = this;
                subMenu.commandClass = m.commandClass;
                subMenu.commandConfig = m.commandConfig;
                if (m.command != undefined) {
                    eval(m.command + '();');
                }
            } else if (m.type == 'command') {
                subMenu.handler = function(o) {
                    Kwf.currentViewport.remove(Kwf.currentViewport.items.item('mainPanel'));
                    var c = eval(o.commandClass);
                    var panel = new c(o.commandConfig);
                    panel.region = 'center';
                    panel.id = 'mainPanel';
                    Kwf.currentViewport.add(panel);
                    Kwf.currentViewport.layout.rendered = false;
                    Kwf.currentViewport.doLayout();
                };
                subMenu.scope = this;
                subMenu.commandClass = m.commandClass;
                subMenu.commandConfig = m.commandConfig;
            } else if (m.type == 'separator') {
                subMenu = '-';
            } else {
                throw "unknown menu-type: "+m.type;
            }

            menuItems.push(subMenu);
        }
        return menuItems;
    },
    loadMenu: function(response, options, result)
    {
        this.items.each(function(i) {
            i.destroy();
        });
        var menuItems = this._processMenus(result.menus);
        menuItems.each(function(menuItem) {
            if (menuItem.icon && menuItem.text) {
                menuItem.cls = 'x2-btn-text-icon';
            } else if (menuItem.icon) {
                menuItem.cls = 'x2-btn-icon';
            }
            this.add(menuItem);
        }, this);

        this.add(new Ext2.Toolbar.Fill());

        this.showUserMenu = new Ext2.Button({
            id: 'userMenu',
            tooltip: trlKwf('Show User Menu'),
            cls: 'x2-btn-icon',
            icon: '/assets/silkicons/bullet_arrow_down.png',
            handler: function() {
                if (!this.userToolbar.isVisible()) {
                    this.showUserMenu.btnEl.setStyle('background-image', 'url(/assets/silkicons/bullet_arrow_up.png)');
                    this.userToolbar.show();
                } else {
                    this.showUserMenu.btnEl.setStyle('background-image', 'url(/assets/silkicons/bullet_arrow_down.png)');
                    this.userToolbar.hide();
                }
            },
            scope: this
        });
        this.add(this.showUserMenu);

        if (result.changeUser) {
            var changeUser = new Kwf.Form.ComboBox({
                store: {
                    url: this.changeUserUrl+'/json-data'
                },
                mode: 'remote',
                editable: true,
                forceSelection: true,
                pageSize: 10,
                triggerAction: 'all',
                width: 120,
                maxHeight: this.changeUserHeight,
                listWidth: this.changeUserWidth,
                tpl: new Ext2.XTemplate(
                        this.changeUserTpl
                      )
            });
            changeUser.on('render', function(combo) {
                combo.setRawValue(result.fullname);
            }, this, {delay: 10});
            changeUser.on('select', function(combo, record, index) {
                Ext2.Ajax.request({
                    url: this.changeUserUrl+'/json-change-user',
                    params: { userId: record.id },
                    success: function() {
                        location.href = '/kwf/welcome';
                    },
                    scope: this
                });
            }, this);
            this.add(changeUser);
            this.add(' ');
            this.add(' ');
            this.add('-');
        }

        this.userToolbar = new Ext2.Toolbar({
            renderTo: this.el,
            style: 'position:absolute;right:0'
        });

        if (result.fullname && result.userSelfControllerUrl) {
            this.userToolbar.add({
                id: 'currentUser',
                text: Ext2.util.Format.htmlEncode(result.fullname),
                cls: 'x2-btn-text-icon',
                icon: '/assets/silkicons/user.png',
                disabled: !result.userId,
                handler: function() {
                    var dlg = new Kwf.Auto.Form.Window({
                        formConfig: {
                            controllerUrl: result.userSelfControllerUrl
                        }
                    });
                    dlg.on('datachange', function() {
                        this.reload();
                    }, this);
                    dlg.showEdit(result.userId);
                },
                scope: this
            });
        }
        if (result.showLogout) {
            this.userToolbar.add({
                cls: 'x2-btn-icon',
                tooltip: trlKwf('Logout'),
                icon: '/assets/silkicons/door_out.png',
                handler: function() {
                    Ext2.Ajax.request({
                        url : '/kwf/user/login/json-logout-user',
                        success : function(form, action) {
                            //nicht reload, weil user nach erneutem login vielleicht
                            //die aktuelle seite gar nicht mehr sehen darf
                            location.href = '/kwf/welcome';
                        },
                        scope: this
                    });
                },
                scope: this
            });
        }
        this.userToolbar.add({
            cls: 'x2-btn-icon',
            icon: '/assets/kwf/images/information.png',
            tooltip: trlKwf('Information'),
            handler: function() {
                var about = new Kwf.About();
                about.show();
            },
            scope: this
        });

        this.userToolbar.hide();

        if (result.frontendUrls.length == 1) {
            //single frontend urls
            this.add({
                tooltip: trlKwf('Open frontend in a new window'),
                cls: 'x2-btn-icon',
                icon: '/assets/silkicons/world.png',
                handler: function() {
                    window.open(result.frontendUrls[0].href);
                },
                scope: this
            });
        } else if (result.frontendUrls.length > 1) {
            //multiple frontend urls
            var frontendItems = [];
            result.frontendUrls.each(function(url) {
                frontendItems.push({
                    text: url.text,
                    cls: 'x2-btn-text-icon',
                    icon: '/assets/silkicons/world.png',
                    tooltip: trlKwf('Open frontend in a new window'),
                    handler: function(options) {
                        window.open(options.url.href);
                    },
                    url: url,
                    scope: this
                });
            }, this);
            this.add({
                tooltip: trlKwf('Open frontend in a new window'),
                cls: 'x2-btn-icon',
                icon: '/assets/silkicons/world.png',
                menu: new Ext2.menu.Menu({
                    items: frontendItems
                })
            });
        }
    }
});
Ext2.reg('kwf.menu', Kwf.Menu.Index);
<|endoftext|>


<commit_before>'use strict'

const _ = require('lodash')

const REDUCER_FUNCTION = (module.exports.type = 'ReducerFunction')

/**
 * @class
 * @property {string} type - @see reducerType
 * @property {string} name - name of the reducer
 * @property {Array} parameters - collection of  @see {@link Parameter} items
 * @property {boolean} isFunction - true if reducer is already function
 * @property {Function} body - actual function body
 */
function ReducerFunction () {
  this.type = REDUCER_FUNCTION
  this.body = undefined
}

module.exports.ReducerFunction = ReducerFunction

/**
 * @param {*} source
 * @returns {boolean}
 */
function isFunction (source) {
  return _.isFunction(source)
}

module.exports.isFunction = isFunction

/**
 * @param {Function} fn
 * @throws if fn has an arity greater than 2
 * @returns {boolean}
 */
function validateFunction (fn) {
  if (fn.length > 2) {
    const e = new Error(`Reducer functions must have an arity of 2 at most`)
    e.name = 'InvalidArity'
    throw e
  }

  return true
}

module.exports.validateFunction = validateFunction

/**
 * parse reducer
 * @param  {string} reducerRaw raw reducer path
 * @return {reducer}
 */
function create (source) {
  const reducer = new ReducerFunction()
  validateFunction(source)
  reducer.body = source
  return Object.freeze(reducer)
}

module.exports.create = create
<commit_msg>refactor(reducer-function): Validate source immediately in reducer-function#create<commit_after>'use strict'

const _ = require('lodash')

const REDUCER_FUNCTION = (module.exports.type = 'ReducerFunction')

/**
 * @class
 * @property {string} type - @see reducerType
 * @property {string} name - name of the reducer
 * @property {Array} parameters - collection of  @see {@link Parameter} items
 * @property {boolean} isFunction - true if reducer is already function
 * @property {Function} body - actual function body
 */
function ReducerFunction () {
  this.type = REDUCER_FUNCTION
  this.body = undefined
}

module.exports.ReducerFunction = ReducerFunction

/**
 * @param {*} source
 * @returns {boolean}
 */
function isFunction (source) {
  return _.isFunction(source)
}

module.exports.isFunction = isFunction

/**
 * @param {Function} fn
 * @throws if fn has an arity greater than 2
 * @returns {boolean}
 */
function validateFunction (fn) {
  if (fn.length > 2) {
    const e = new Error(`Reducer functions must have an arity of 2 at most`)
    e.name = 'InvalidArity'
    throw e
  }

  return true
}

module.exports.validateFunction = validateFunction

/**
 * parse reducer
 * @param  {string} reducerRaw raw reducer path
 * @return {reducer}
 */
function create (source) {
  validateFunction(source)
  const reducer = new ReducerFunction()
  reducer.body = source
  return Object.freeze(reducer)
}

module.exports.create = create
<|endoftext|>


<commit_before>BlazeComponent.extendComponent({
  canSeeAddCard() {
    const list = Template.currentData();
    return !list.getWipLimit('enabled') || list.getWipLimit('soft') || !this.reachedWipLimit();
  },

  editTitle(evt) {
    evt.preventDefault();
    const newTitle = this.childComponents('inlinedForm')[0].getValue().trim();
    const list = this.currentData();
    if (newTitle) {
      list.rename(newTitle.trim());
    }
  },

  isWatching() {
    const list = this.currentData();
    return list.findWatcher(Meteor.userId());
  },

  limitToShowCardsCount() {
    return Meteor.user().getLimitToShowCardsCount();
  },

  reachedWipLimit() {
    const list = Template.currentData();
    return list.getWipLimit('enabled') && list.getWipLimit('value') <= list.cards().count();
  },

  showCardsCountForList(count) {
    return count > this.limitToShowCardsCount();
  },

  events() {
    return [{
      'click .js-open-list-menu': Popup.open('listAction'),
      'click .js-add-card' () {
        const listDom = document.getElementById(`js-list-${this.currentData()._id}`);
        const listComponent = BlazeComponent.getComponentForElement(listDom);
        listComponent.openForm({
          position: 'top',
        });
      },
      'click .js-unselect-list'() {
        Session.set('currentList', null);
      },
      submit: this.editTitle,
    }];
  },
}).register('listHeader');

Template.listActionPopup.helpers({
  isWipLimitEnabled() {
    return Template.currentData().getWipLimit('enabled');
  },

  isWatching() {
    return this.findWatcher(Meteor.userId());
  },
});

Template.listActionPopup.events({
  'click .js-list-subscribe' () {},
  'click .js-select-cards' () {
    const cardIds = this.allCards().map((card) => card._id);
    MultiSelection.add(cardIds);
    Popup.close();
  },
  'click .js-toggle-watch-list' () {
    const currentList = this;
    const level = currentList.findWatcher(Meteor.userId()) ? null : 'watching';
    Meteor.call('watch', 'list', currentList._id, level, (err, ret) => {
      if (!err && ret) Popup.close();
    });
  },
  'click .js-close-list' (evt) {
    evt.preventDefault();
    this.archive();
    Popup.close();
  },
  'click .js-set-wip-limit': Popup.open('setWipLimit'),
  'click .js-more': Popup.open('listMore'),
});

BlazeComponent.extendComponent({
  applyWipLimit() {
    const list = Template.currentData();
    const limit = parseInt(Template.instance().$('.wip-limit-value').val(), 10);

    if(limit < list.cards().count() && !list.getWipLimit('soft')){
      Template.instance().$('.wip-limit-error').click();
    } else {
      Meteor.call('applyWipLimit', list._id, limit);
      Popup.back();
    }
  },

  enableSoftLimit() {
    const list = Template.currentData();

    if(list.getWipLimit('soft') && list.getWipLimit('value') < list.cards().count()){
      list.setWipLimit(list.cards().count());
    }
    Meteor.call('enableSoftLimit', Template.currentData()._id);
  },

  enableWipLimit() {
    const list = Template.currentData();
    // Prevent user from using previously stored wipLimit.value if it is less than the current number of cards in the list
    if(!list.getWipLimit('enabled') && list.getWipLimit('value') < list.cards().count()){
      list.setWipLimit(list.cards().count());
    }
    Meteor.call('enableWipLimit', list._id);
  },

  isWipLimitSoft() {
    return Template.currentData().getWipLimit('soft');
  },

  isWipLimitEnabled() {
    return Template.currentData().getWipLimit('enabled');
  },

  wipLimitValue(){
    return Template.currentData().getWipLimit('value');
  },

  events() {
    return [{
      'click .js-enable-wip-limit': this.enableWipLimit,
      'click .wip-limit-apply': this.applyWipLimit,
      'click .wip-limit-error': Popup.open('wipLimitError'),
      'click .materialCheckBox': this.enableSoftLimit,
    }];
  },
}).register('setWipLimitPopup');

Template.listMorePopup.events({
  'click .js-delete': Popup.afterConfirm('listDelete', function () {
    Popup.close();
    this.allCards().map((card) => Cards.remove(card._id));
    Lists.remove(this._id);
    Utils.goBoardId(this.boardId);
  }),
});
<commit_msg>Fix #1577<commit_after>BlazeComponent.extendComponent({
  canSeeAddCard() {
    const list = Template.currentData();
    return !list.getWipLimit('enabled') || list.getWipLimit('soft') || !this.reachedWipLimit();
  },

  editTitle(evt) {
    evt.preventDefault();
    const newTitle = this.childComponents('inlinedForm')[0].getValue().trim();
    const list = this.currentData();
    if (newTitle) {
      list.rename(newTitle.trim());
    }
  },

  isWatching() {
    const list = this.currentData();
    return list.findWatcher(Meteor.userId());
  },

  limitToShowCardsCount() {
    return Meteor.user().getLimitToShowCardsCount();
  },

  reachedWipLimit() {
    const list = Template.currentData();
    return list.getWipLimit('enabled') && list.getWipLimit('value') <= list.cards().count();
  },

  showCardsCountForList(count) {
    return count > this.limitToShowCardsCount();
  },

  events() {
    return [{
      'click .js-open-list-menu': Popup.open('listAction'),
      'click .js-add-card' (evt) {
        const listDom = $(evt.target).parents(`#js-list-${this.currentData()._id}`)[0];
        const listComponent = BlazeComponent.getComponentForElement(listDom);
        listComponent.openForm({
          position: 'top',
        });
      },
      'click .js-unselect-list'() {
        Session.set('currentList', null);
      },
      submit: this.editTitle,
    }];
  },
}).register('listHeader');

Template.listActionPopup.helpers({
  isWipLimitEnabled() {
    return Template.currentData().getWipLimit('enabled');
  },

  isWatching() {
    return this.findWatcher(Meteor.userId());
  },
});

Template.listActionPopup.events({
  'click .js-list-subscribe' () {},
  'click .js-select-cards' () {
    const cardIds = this.allCards().map((card) => card._id);
    MultiSelection.add(cardIds);
    Popup.close();
  },
  'click .js-toggle-watch-list' () {
    const currentList = this;
    const level = currentList.findWatcher(Meteor.userId()) ? null : 'watching';
    Meteor.call('watch', 'list', currentList._id, level, (err, ret) => {
      if (!err && ret) Popup.close();
    });
  },
  'click .js-close-list' (evt) {
    evt.preventDefault();
    this.archive();
    Popup.close();
  },
  'click .js-set-wip-limit': Popup.open('setWipLimit'),
  'click .js-more': Popup.open('listMore'),
});

BlazeComponent.extendComponent({
  applyWipLimit() {
    const list = Template.currentData();
    const limit = parseInt(Template.instance().$('.wip-limit-value').val(), 10);

    if(limit < list.cards().count() && !list.getWipLimit('soft')){
      Template.instance().$('.wip-limit-error').click();
    } else {
      Meteor.call('applyWipLimit', list._id, limit);
      Popup.back();
    }
  },

  enableSoftLimit() {
    const list = Template.currentData();

    if(list.getWipLimit('soft') && list.getWipLimit('value') < list.cards().count()){
      list.setWipLimit(list.cards().count());
    }
    Meteor.call('enableSoftLimit', Template.currentData()._id);
  },

  enableWipLimit() {
    const list = Template.currentData();
    // Prevent user from using previously stored wipLimit.value if it is less than the current number of cards in the list
    if(!list.getWipLimit('enabled') && list.getWipLimit('value') < list.cards().count()){
      list.setWipLimit(list.cards().count());
    }
    Meteor.call('enableWipLimit', list._id);
  },

  isWipLimitSoft() {
    return Template.currentData().getWipLimit('soft');
  },

  isWipLimitEnabled() {
    return Template.currentData().getWipLimit('enabled');
  },

  wipLimitValue(){
    return Template.currentData().getWipLimit('value');
  },

  events() {
    return [{
      'click .js-enable-wip-limit': this.enableWipLimit,
      'click .wip-limit-apply': this.applyWipLimit,
      'click .wip-limit-error': Popup.open('wipLimitError'),
      'click .materialCheckBox': this.enableSoftLimit,
    }];
  },
}).register('setWipLimitPopup');

Template.listMorePopup.events({
  'click .js-delete': Popup.afterConfirm('listDelete', function () {
    Popup.close();
    this.allCards().map((card) => Cards.remove(card._id));
    Lists.remove(this._id);
    Utils.goBoardId(this.boardId);
  }),
});
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import ProjectsSelect from '../../projects/ProjectsSelect';

const Input = props => (

    <input
      type="text"
      onKeyPress={e => {
        if (e.charCode === 13) {
          this.handleEnterButton(props.id, props.editName, props.categoryID);
        }
      }}
      value={props.editName}
      onChange={props.handleNameInput}
    />
    <ProjectsSelect
      disableDefault
      parentID={props.categoryID}
      projects={props.projects}
      changeSelect={props.changeSelect}
    />

);

Input.propTypes = {
  id: PropTypes.string.isRequired,
  editName: PropTypes.string.isRequired,
  categoryID: PropTypes.string.isRequired,
  projects: PropTypes.array.isRequired,
  handleNameInput: PropTypes.func.isRequired,
  changeSelect: PropTypes.func.isRequired,
};

export default Input;
<commit_msg>added flow<commit_after>// @flow
import React from 'react';
import PropTypes from 'prop-types';
import ProjectsSelect from '../../projects/ProjectsSelect';

type Props = {
  id: string,
  editName: string,
  categoryID: string,
  projects: string,
  handleNameInput: () => void,
  changeSelect: () => void,
};

const Input = (props: Props) => (

    <input
      type="text"
      onKeyPress={e => {
        if (e.charCode === 13) {
          this.handleEnterButton(props.id, props.editName, props.categoryID);
        }
      }}
      value={props.editName}
      onChange={props.handleNameInput}
    />
    <ProjectsSelect
      disableDefault
      parentID={props.categoryID}
      projects={props.projects}
      changeSelect={props.changeSelect}
    />

);

export default Input;
<|endoftext|>


<commit_before>module.exports.rules = {
    'jsx-uses-m-pragma': {
        create(context) {
            const pragma = 'm';
            const usePragma = () => context.markVariableAsUsed(pragma);
            return {
                JSXOpeningElement: usePragma,
                JSXOpeningFragment: usePragma,
            };
        },
    },
    'jsx-uses-vars': {
        create(context) {
            return {
                JSXOpeningElement(node) {
                    const variable = node.name.name;
                    if (variable) {
                        context.markVariableAsUsed(variable);
                    }
                },
            };
        },
    },
};
<commit_msg>Fixed "unused var" JSX warning (#2938)<commit_after>module.exports.rules = {
    'jsx-uses-m-pragma': {
        create(context) {
            const pragma = 'm';
            const usePragma = () => context.markVariableAsUsed(pragma);
            return {
                JSXOpeningElement: usePragma,
                JSXOpeningFragment: usePragma,
            };
        },
    },
    'jsx-uses-vars': {
        create(context) {
            return {
                JSXOpeningElement(node) {
                    let variable;
                    const jsxTag = node.name;
                    if (jsxTag.type === 'JSXIdentifier') {
                        variable = jsxTag.name;
                    } else if (jsxTag.type === 'JSXMemberExpression') {
                        variable = jsxTag.object.name;
                    } else {
                        console.warn('Unsupported JSX identifier', jsxTag);
                    }
                    context.markVariableAsUsed(variable);
                },
            };
        },
    },
};
<|endoftext|>


<commit_before>'use strict'
/* globals cli */

global.cli = require('heroku-cli-util')
cli.raiseErrors = true
cli.color.enabled = false

let nock = require('nock')
nock.disableNetConnect()
<commit_msg>Set stdout / stderr with to 80 to fix tests<commit_after>'use strict'
/* globals cli */

global.cli = require('heroku-cli-util')
cli.raiseErrors = true
cli.color.enabled = false

process.stdout.columns = 80
process.stderr.columns = 80

let nock = require('nock')
nock.disableNetConnect()
<|endoftext|>


<commit_before>import { merge } from 'rxjs/observable/merge';
import { empty } from 'rxjs/observable/empty';
import {
  tap,
  filter,
  map,
  ignoreElements,
  switchMap,
  catchError
} from 'rxjs/operators';
import { ofType } from 'redux-observable';
import store from 'store';
import uuid from 'uuid/v4';

import { types, onlineStatusChange, isOnlineSelector } from './';
import postUpdate$ from '../../templates/Challenges/utils/postUpdate$';

const key = 'fcc-failed-updates';

function delay(time = 0, fn) {
  return setTimeout(fn, time);
}

function failedUpdateEpic(action$, { getState }) {
  const storeUpdates = action$.pipe(
    ofType(types.updateFailed),
    tap(({ payload }) => {
      const failures = store.get(key) || [];
      payload.id = uuid();
      store.set(key, [...failures, payload]);
    }),
    map(() => onlineStatusChange(false))
  );

  const flushUpdates = action$.pipe(
    ofType(types.fetchUserComplete, types.updateComplete),
    filter(() => store.get(key)),
    filter(() => isOnlineSelector(getState())),
    tap(() => {
      const failures = store.get(key) || [];
      let delayTime = 0;
      const batch = failures.map(update => {
        delayTime += 300;
        // we stagger the updates here so we don't hammer the server
        return delay(delayTime, () =>
          postUpdate$(update)
            .pipe(
              switchMap(response => {
                if (response && response.message) {
                  // the request completed successfully
                  const failures = store.get(key) || [];
                  const newFailures = failures.filter(x => x.id !== update.id);
                  store.set(key, newFailures);
                }
                return empty();
              }),
              catchError(() => empty())
            )
            .toPromise()
        );
      });
      Promise.all(batch)
        .then(() => console.info('progress updates processed where possible'))
        .catch(err =>
          console.warn('unable to process progress updates', err.message)
        );
    }),
    ignoreElements()
  );

  return merge(storeUpdates, flushUpdates);
}

export default failedUpdateEpic;
<commit_msg>fix(response handling): Handle success responses better<commit_after>import { merge } from 'rxjs/observable/merge';
import { empty } from 'rxjs/observable/empty';
import {
  tap,
  filter,
  map,
  ignoreElements,
  switchMap,
  catchError
} from 'rxjs/operators';
import { ofType } from 'redux-observable';
import store from 'store';
import uuid from 'uuid/v4';

import { types, onlineStatusChange, isOnlineSelector } from './';
import postUpdate$ from '../../templates/Challenges/utils/postUpdate$';
import { isGoodXHRStatus } from '../../templates/Challenges/utils';

const key = 'fcc-failed-updates';

function delay(time = 0, fn) {
  return setTimeout(fn, time);
}

function failedUpdateEpic(action$, { getState }) {
  const storeUpdates = action$.pipe(
    ofType(types.updateFailed),
    tap(({ payload = {} }) => {
      if ('endpoint' in payload && 'payload' in payload) {
        const failures = store.get(key) || [];
        payload.id = uuid();
        store.set(key, [...failures, payload]);
      }
    }),
    map(() => onlineStatusChange(false))
  );

  const flushUpdates = action$.pipe(
    ofType(types.fetchUserComplete, types.updateComplete),
    filter(() => store.get(key)),
    filter(() => isOnlineSelector(getState())),
    tap(() => {
      const failures = store.get(key) || [];
      let delayTime = 0;
      const batch = failures.map(update => {
        delayTime += 300;
        // we stagger the updates here so we don't hammer the server
        return delay(delayTime, () =>
          postUpdate$(update)
            .pipe(
              switchMap(response => {
                if (
                  response &&
                  (response.message || isGoodXHRStatus(response.status))
                ) {
                  console.info(`${update.id} succeeded`);
                  // the request completed successfully
                  const failures = store.get(key) || [];
                  const newFailures = failures.filter(x => x.id !== update.id);
                  store.set(key, newFailures);
                }
                return empty();
              }),
              catchError(() => empty())
            )
            .toPromise()
        );
      });
      Promise.all(batch)
        .then(() => console.info('progress updates processed where possible'))
        .catch(err =>
          console.warn('unable to process progress updates', err.message)
        );
    }),
    ignoreElements()
  );

  return merge(storeUpdates, flushUpdates);
}

export default failedUpdateEpic;
<|endoftext|>


<commit_before>/*
 * loquat test / error.ParseError.merge()
 * copyright (c) 2016 Susisu
 */

"use strict";

const { expect } = require("chai");

const { SourcePos } = require("pos.js");
const { ErrorMessageType, ErrorMessage, ParseError, LazyParseError } = require("error.js");

describe(".merge(errA, errB)", () => {
    it("should return an `AbstractParseError' object that is equal to `errA' if `errB' is unknown but `errA' is not", () => {
        let posA = new SourcePos("foobar", 496, 28);
        let msgsA = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "foo"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "bar"),
            new ErrorMessage(ErrorMessageType.EXPECT, "baz"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "nyancat")
        ];
        let posB = new SourcePos("foobar", 6, 28);
        let msgsB = [];
        // strict
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
        // lazy
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
        // mixed
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
    });

    it("should return an `AbstractParseError' object that is equal to `errB' if `errA' is unknown but `errB' is not", () => {
        let posA = new SourcePos("foobar", 496, 28);
        let msgsA = [];
        let posB = new SourcePos("foobar", 6, 28);
        let msgsB = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "foo"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "bar"),
            new ErrorMessage(ErrorMessageType.EXPECT, "baz"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "nyancat")
        ];
        // strict
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
        // lazy
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
        // mixed
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
    });

    it("should return an `AbstractParseError' object that is equal to `errA' if `errA.pos' is behind `errB.pos'", () => {
        let posA = new SourcePos("foobar", 496, 28);
        let msgsA = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "foo"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "bar"),
            new ErrorMessage(ErrorMessageType.EXPECT, "baz"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "nyancat")
        ];
        let posB = new SourcePos("foobar", 6, 28);
        let msgsB = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "x"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "y"),
            new ErrorMessage(ErrorMessageType.EXPECT, "z"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "w")
        ];
        // strict
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
        // lazy
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
        // mixed
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
    });
    it("should return an `AbstractParseError' object that is equal to `errB' if `errB.pos' is behind `errA.pos'", () => {
        let posA = new SourcePos("foobar", 6, 28);
        let msgsA = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "foo"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "bar"),
            new ErrorMessage(ErrorMessageType.EXPECT, "baz"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "nyancat")
        ];
        let posB = new SourcePos("foobar", 496, 28);
        let msgsB = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "x"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "y"),
            new ErrorMessage(ErrorMessageType.EXPECT, "z"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "w")
        ];
        // strict
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
        // lazy
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
        // mixed
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
    });

    it("should return an `AbstractParseError' object with error messages of `errA' and `errB' concatenated"
        + " if `errA.pos' is equal to `errB.pos'", () => {
        let pos = new SourcePos("foobar", 496, 28);
        let msgsA = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "foo"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "bar"),
            new ErrorMessage(ErrorMessageType.EXPECT, "baz"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "nyancat")
        ];
        let msgsB = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "x"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "y"),
            new ErrorMessage(ErrorMessageType.EXPECT, "z"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "w")
        ];
        let msgs = msgsA.concat(msgsB);
        // strict
        {
            let errA = new ParseError(pos, msgsA);
            let errB = new ParseError(pos, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, pos)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgs)).to.be.true;
        }
        // lazy
        {
            let errA = new LazyParseError(() => new ParseError(pos, msgsA));
            let errB = new LazyParseError(() => new ParseError(pos, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, pos)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgs)).to.be.true;
        }
        // mixed
        {
            let errA = new LazyParseError(() => new ParseError(pos, msgsA));
            let errB = new ParseError(pos, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, pos)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgs)).to.be.true;
        }
        {
            let errA = new ParseError(pos, msgsA);
            let errB = new LazyParseError(() => new ParseError(pos, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, pos)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgs)).to.be.true;
        }
    });
});
<commit_msg>[core] Remove linter warnings<commit_after>/*
 * loquat test / error.ParseError.merge()
 * copyright (c) 2016 Susisu
 */

"use strict";

const { expect } = require("chai");

const { SourcePos } = require("pos.js");
const { ErrorMessageType, ErrorMessage, ParseError, LazyParseError } = require("error.js");

describe(".merge(errA, errB)", () => {
    it("should return an `AbstractParseError' object that is equal to `errA'"
        + " if `errB' is unknown but `errA' is not", () => {
        let posA = new SourcePos("foobar", 496, 28);
        let msgsA = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "foo"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "bar"),
            new ErrorMessage(ErrorMessageType.EXPECT, "baz"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "nyancat")
        ];
        let posB = new SourcePos("foobar", 6, 28);
        let msgsB = [];
        // strict
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
        // lazy
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
        // mixed
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
    });

    it("should return an `AbstractParseError' object that is equal to `errB'"
        + " if `errA' is unknown but `errB' is not", () => {
        let posA = new SourcePos("foobar", 496, 28);
        let msgsA = [];
        let posB = new SourcePos("foobar", 6, 28);
        let msgsB = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "foo"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "bar"),
            new ErrorMessage(ErrorMessageType.EXPECT, "baz"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "nyancat")
        ];
        // strict
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
        // lazy
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
        // mixed
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
    });

    it("should return an `AbstractParseError' object that is equal to `errA'"
        + " if `errA.pos' is behind `errB.pos'", () => {
        let posA = new SourcePos("foobar", 496, 28);
        let msgsA = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "foo"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "bar"),
            new ErrorMessage(ErrorMessageType.EXPECT, "baz"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "nyancat")
        ];
        let posB = new SourcePos("foobar", 6, 28);
        let msgsB = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "x"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "y"),
            new ErrorMessage(ErrorMessageType.EXPECT, "z"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "w")
        ];
        // strict
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
        // lazy
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
        // mixed
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posA)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsA)).to.be.true;
        }
    });
    it("should return an `AbstractParseError' object that is equal to `errB'"
        + " if `errB.pos' is behind `errA.pos'", () => {
        let posA = new SourcePos("foobar", 6, 28);
        let msgsA = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "foo"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "bar"),
            new ErrorMessage(ErrorMessageType.EXPECT, "baz"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "nyancat")
        ];
        let posB = new SourcePos("foobar", 496, 28);
        let msgsB = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "x"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "y"),
            new ErrorMessage(ErrorMessageType.EXPECT, "z"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "w")
        ];
        // strict
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
        // lazy
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
        // mixed
        {
            let errA = new LazyParseError(() => new ParseError(posA, msgsA));
            let errB = new ParseError(posB, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
        {
            let errA = new ParseError(posA, msgsA);
            let errB = new LazyParseError(() => new ParseError(posB, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, posB)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgsB)).to.be.true;
        }
    });

    it("should return an `AbstractParseError' object with error messages of `errA' and `errB' concatenated"
        + " if `errA.pos' is equal to `errB.pos'", () => {
        let pos = new SourcePos("foobar", 496, 28);
        let msgsA = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "foo"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "bar"),
            new ErrorMessage(ErrorMessageType.EXPECT, "baz"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "nyancat")
        ];
        let msgsB = [
            new ErrorMessage(ErrorMessageType.SYSTEM_UNEXPECT, "x"),
            new ErrorMessage(ErrorMessageType.UNEXPECT, "y"),
            new ErrorMessage(ErrorMessageType.EXPECT, "z"),
            new ErrorMessage(ErrorMessageType.MESSAGE, "w")
        ];
        let msgs = msgsA.concat(msgsB);
        // strict
        {
            let errA = new ParseError(pos, msgsA);
            let errB = new ParseError(pos, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, pos)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgs)).to.be.true;
        }
        // lazy
        {
            let errA = new LazyParseError(() => new ParseError(pos, msgsA));
            let errB = new LazyParseError(() => new ParseError(pos, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, pos)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgs)).to.be.true;
        }
        // mixed
        {
            let errA = new LazyParseError(() => new ParseError(pos, msgsA));
            let errB = new ParseError(pos, msgsB);
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, pos)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgs)).to.be.true;
        }
        {
            let errA = new ParseError(pos, msgsA);
            let errB = new LazyParseError(() => new ParseError(pos, msgsB));
            let err = ParseError.merge(errA, errB);
            expect(SourcePos.equal(err.pos, pos)).to.be.true;
            expect(ErrorMessage.messagesEqual(err.msgs, msgs)).to.be.true;
        }
    });
});
<|endoftext|>


<commit_before>/* global window */
/* eslint react/no-find-dom-node: 0 */
// @flow
import * as React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import * as R from 'ramda';
import { withTheme } from 'styled-components';
import List from 'react-virtualized/dist/commonjs/List/index';
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer/index';
import ClickOutside from 'react-overlay-pack/lib/ClickOutside/index';
import Transition from 'react-overlay-pack/lib/Transition/index';
import IconFold from 'mcs-lite-icon/lib/IconFold';
import rafThrottle from 'raf-throttle';
import Input from '../Input';
import Rotate from '../Rotate';
import MenuItem from '../Menu/MenuItem';
import { StyledButton } from '../Select/styled-components';
import {
  StyledMenu,
  StyledInputGroup,
  NoRowWrapper,
  FakeInputValue,
  TextOverflow,
} from './styled-components';
import { type Value, type ItemProps, type ItemValueMapper } from './type.flow';
import {
  filterBy,
  getInputValue,
  getPlaceholder,
  getMenuItemHeight,
  getMenuHeight,
} from './utils';

type Props = {
  value: Value,
  onChange: (value: Value) => Promise<void> | void,
  items: Array<ItemProps>,
  kind?: string,
  placeholder?: string,
  focus?: boolean,
  noRowsRenderer?: ({ onClose: () => void }) => React.Node,
  disableFilter?: boolean,
  itemValueMapper?: ItemValueMapper,
  // Note: innerRef for the problem of outside click in dialog
  menuRef?: (ref: React.ElementRef<typeof StyledMenu>) => void,
};

const defaultItemValueMapper = (item: ItemProps) => item.children;

class PureInputSelect extends React.Component<
  Props & { theme: Object },
  {
    isOpen: boolean,
    filter: string,
    menuWidth: number,
  },
> {
  static defaultProps = {
    kind: 'primary',
    noRowsRenderer: () => <NoRowWrapper>No results found</NoRowWrapper>,
    disableFilter: false,
    itemValueMapper: defaultItemValueMapper,
  };
  state = { isOpen: false, filter: '', menuWidth: 0 };
  componentDidMount() {
    this.resize();
    window.addEventListener('resize', this.resize);
  }
  componentWillUnmount() {
    window.removeEventListener('resize', this.resize);
    if (this.resize && this.resize.cancel) this.resize.cancel();
  }
  onOpen = () => this.setState(() => ({ isOpen: true }));
  onClose = () => this.setState(() => ({ isOpen: false }));
  onClickOutside = (e: any) => {
    const node = findDOMNode(this.input);
    if (node && node.contains(e.target)) return; // Note: Omit input target.
    this.onClose();
  };
  onToggle = (e: any) => {
    e.preventDefault();
    this.setState(({ isOpen }) => ({ isOpen: !isOpen }));
  };
  onFilterChange = (e: any) => {
    const { value } = e.target;
    this.setState(() => ({ filter: value }));
  };
  onInputGroupRef = (instance: React.ElementRef<typeof StyledInputGroup>) => {
    // Note: for dropdown width
    this.inputGroup = instance;
  };
  onInputRef = (instance: React.ElementRef<typeof Input>) => {
    // Note: for click outside
    this.input = instance;
  };
  resize = rafThrottle(() => {
    const { inputGroup } = this;
    const menuWidth =
      inputGroup &&
      inputGroup.getBoundingClientRect &&
      inputGroup.getBoundingClientRect().width;

    this.setState(() => ({ menuWidth: parseInt(menuWidth, 10) }));
  });
  rowRenderer = ({
    key,
    index,
    style,
  }: {
    key: string,
    index: number,
    style: Object,
  }): React.Element<typeof MenuItem> => {
    const {
      items,
      value,
      onChange,
      itemValueMapper = defaultItemValueMapper,
    } = this.props;
    const { filter } = this.state;
    const { onClose } = this;
    const filteredItems = filterBy({ items, filter, itemValueMapper });
    const { value: itemValue, children }: ItemProps = filteredItems[index];
    const onItemClick = () => {
      onChange(itemValue);
      this.setState(() => ({ filter: '' }));
      onClose();
    };

    return (
      <MenuItem
        key={key}
        style={style}
        active={itemValue === value}
        onClick={onItemClick}
        title={children}
      >
        <TextOverflow>{children}</TextOverflow>
      </MenuItem>
    );
  };
  input: ?React.ElementRef<typeof Input>;
  inputGroup: ?React.ElementRef<typeof StyledInputGroup>;
  render() {
    const {
      items,
      kind,
      placeholder,
      theme,
      value,
      noRowsRenderer,
      focus,
      menuRef,
      disableFilter,
      itemValueMapper = defaultItemValueMapper,
      ...otherProps
    } = this.props;
    const { menuWidth, isOpen, filter } = this.state;
    const {
      onInputGroupRef,
      onOpen,
      onClose,
      onToggle,
      onFilterChange,
      rowRenderer,
      onInputRef,
      onClickOutside,
    } = this;

    const filteredItems = filterBy({ items, filter, itemValueMapper });
    const activeIndex = R.findIndex(R.propEq('value', value))(items);
    const activeItem = items[activeIndex];
    const menuItemHeight = getMenuItemHeight(theme);
    const menuHeight = getMenuHeight({ filteredItems, menuItemHeight });

    return (

        {/* Input filter */}

        </StyledInputGroup>

        {/* Dropdown overlay */}
        {isOpen && (
          <ClickOutside onClick={onClickOutside}>
            <Transition
              style={{ transform: 'translateY(0px)' }} // From
              animation={{ translateY: 5, ease: 'easeOutQuart', duration: 350 }} // To
              component={false}
            >

            </Transition>
          </ClickOutside>
        )}

    );
  }
}

const InputSelect: React.ComponentType<Props> = withTheme(PureInputSelect);
InputSelect.displayName = 'InputSelect';
InputSelect.propTypes = {
  value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
  onChange: PropTypes.func.isRequired, // (value: Value) => Promise<void> | void,
  items: PropTypes.arrayOf(
    PropTypes.shape({
      value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
        .isRequired,
      children: PropTypes.node.isRequired,
    }),
  ).isRequired,
  kind: PropTypes.string,
  placeholder: PropTypes.string,
  noRowsRenderer: PropTypes.func,
  focus: PropTypes.bool,
  disableFilter: PropTypes.bool,
  itemValueMapper: PropTypes.func,
  menuRef: PropTypes.func,
};

export default InputSelect;
<commit_msg>fix(ui): fix title with itemValueMapper function (#665)<commit_after>/* global window */
/* eslint react/no-find-dom-node: 0 */
// @flow
import * as React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import * as R from 'ramda';
import { withTheme } from 'styled-components';
import List from 'react-virtualized/dist/commonjs/List/index';
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer/index';
import ClickOutside from 'react-overlay-pack/lib/ClickOutside/index';
import Transition from 'react-overlay-pack/lib/Transition/index';
import IconFold from 'mcs-lite-icon/lib/IconFold';
import rafThrottle from 'raf-throttle';
import Input from '../Input';
import Rotate from '../Rotate';
import MenuItem from '../Menu/MenuItem';
import { StyledButton } from '../Select/styled-components';
import {
  StyledMenu,
  StyledInputGroup,
  NoRowWrapper,
  FakeInputValue,
  TextOverflow,
} from './styled-components';
import { type Value, type ItemProps, type ItemValueMapper } from './type.flow';
import {
  filterBy,
  getInputValue,
  getPlaceholder,
  getMenuItemHeight,
  getMenuHeight,
} from './utils';

type Props = {
  value: Value,
  onChange: (value: Value) => Promise<void> | void,
  items: Array<ItemProps>,
  kind?: string,
  placeholder?: string,
  focus?: boolean,
  noRowsRenderer?: ({ onClose: () => void }) => React.Node,
  disableFilter?: boolean,
  itemValueMapper?: ItemValueMapper,
  // Note: innerRef for the problem of outside click in dialog
  menuRef?: (ref: React.ElementRef<typeof StyledMenu>) => void,
};

const defaultItemValueMapper = (item: ItemProps) => item.children;

class PureInputSelect extends React.Component<
  Props & { theme: Object },
  {
    isOpen: boolean,
    filter: string,
    menuWidth: number,
  },
> {
  static defaultProps = {
    kind: 'primary',
    noRowsRenderer: () => <NoRowWrapper>No results found</NoRowWrapper>,
    disableFilter: false,
    itemValueMapper: defaultItemValueMapper,
  };
  state = { isOpen: false, filter: '', menuWidth: 0 };
  componentDidMount() {
    this.resize();
    window.addEventListener('resize', this.resize);
  }
  componentWillUnmount() {
    window.removeEventListener('resize', this.resize);
    if (this.resize && this.resize.cancel) this.resize.cancel();
  }
  onOpen = () => this.setState(() => ({ isOpen: true }));
  onClose = () => this.setState(() => ({ isOpen: false }));
  onClickOutside = (e: any) => {
    const node = findDOMNode(this.input);
    if (node && node.contains(e.target)) return; // Note: Omit input target.
    this.onClose();
  };
  onToggle = (e: any) => {
    e.preventDefault();
    this.setState(({ isOpen }) => ({ isOpen: !isOpen }));
  };
  onFilterChange = (e: any) => {
    const { value } = e.target;
    this.setState(() => ({ filter: value }));
  };
  onInputGroupRef = (instance: React.ElementRef<typeof StyledInputGroup>) => {
    // Note: for dropdown width
    this.inputGroup = instance;
  };
  onInputRef = (instance: React.ElementRef<typeof Input>) => {
    // Note: for click outside
    this.input = instance;
  };
  resize = rafThrottle(() => {
    const { inputGroup } = this;
    const menuWidth =
      inputGroup &&
      inputGroup.getBoundingClientRect &&
      inputGroup.getBoundingClientRect().width;

    this.setState(() => ({ menuWidth: parseInt(menuWidth, 10) }));
  });
  rowRenderer = ({
    key,
    index,
    style,
  }: {
    key: string,
    index: number,
    style: Object,
  }): React.Element<typeof MenuItem> => {
    const {
      items,
      value,
      onChange,
      itemValueMapper = defaultItemValueMapper,
    } = this.props;
    const { filter } = this.state;
    const { onClose } = this;
    const filteredItems = filterBy({ items, filter, itemValueMapper });
    const item = filteredItems[index];
    const onItemClick = () => {
      onChange(item.value);
      this.setState(() => ({ filter: '' }));
      onClose();
    };

    return (
      <MenuItem
        key={key}
        style={style}
        active={item.value === value}
        onClick={onItemClick}
        title={itemValueMapper(item)}
      >
        <TextOverflow>{item.children}</TextOverflow>
      </MenuItem>
    );
  };
  input: ?React.ElementRef<typeof Input>;
  inputGroup: ?React.ElementRef<typeof StyledInputGroup>;
  render() {
    const {
      items,
      kind,
      placeholder,
      theme,
      value,
      noRowsRenderer,
      focus,
      menuRef,
      disableFilter,
      itemValueMapper = defaultItemValueMapper,
      ...otherProps
    } = this.props;
    const { menuWidth, isOpen, filter } = this.state;
    const {
      onInputGroupRef,
      onOpen,
      onClose,
      onToggle,
      onFilterChange,
      rowRenderer,
      onInputRef,
      onClickOutside,
    } = this;

    const filteredItems = filterBy({ items, filter, itemValueMapper });
    const activeIndex = R.findIndex(R.propEq('value', value))(items);
    const activeItem = items[activeIndex];
    const menuItemHeight = getMenuItemHeight(theme);
    const menuHeight = getMenuHeight({ filteredItems, menuItemHeight });

    return (

        {/* Input filter */}

        </StyledInputGroup>

        {/* Dropdown overlay */}
        {isOpen && (
          <ClickOutside onClick={onClickOutside}>
            <Transition
              style={{ transform: 'translateY(0px)' }} // From
              animation={{ translateY: 5, ease: 'easeOutQuart', duration: 350 }} // To
              component={false}
            >

            </Transition>
          </ClickOutside>
        )}

    );
  }
}

const InputSelect: React.ComponentType<Props> = withTheme(PureInputSelect);
InputSelect.displayName = 'InputSelect';
InputSelect.propTypes = {
  value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
  onChange: PropTypes.func.isRequired, // (value: Value) => Promise<void> | void,
  items: PropTypes.arrayOf(
    PropTypes.shape({
      value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
        .isRequired,
      children: PropTypes.node.isRequired,
    }),
  ).isRequired,
  kind: PropTypes.string,
  placeholder: PropTypes.string,
  noRowsRenderer: PropTypes.func,
  focus: PropTypes.bool,
  disableFilter: PropTypes.bool,
  itemValueMapper: PropTypes.func,
  menuRef: PropTypes.func,
};

export default InputSelect;
<|endoftext|>


<commit_before>import { mapState, mapActions, mapGetters } from 'vuex';
import ApiClient     from '@/utils/ApiClient';
import CommonHelper  from '@/utils/CommonHelper';
import ScreenComment from '@/models/ScreenComment';

export default {
    data() {
        return {
            isLoadingComments: false,
            showCommentsPanel: false,
            mentionsList:      [],
        }
    },
    computed: {
        ...mapState({
            activePrototypeId:    state => state.prototypes.activePrototypeId,
            activeScreenId:       state => state.screens.activeScreenId,
            scaleFactor:          state => state.screens.scaleFactor,
            previewToken:         state => state.preview.previewToken,
            showResolvedComments: state => state.comments.showResolvedComments,
            activeCommentId:      state => state.comments.activeCommentId,
            unreadComments:       state => state.notifications.unreadComments,
        }),
        ...mapGetters({
            getCommentsForScreen:       'comments/getCommentsForScreen',
            activeComment:              'comments/activeComment',
            getUnreadCommentsForScreen: 'notifications/getUnreadCommentsForScreen',
            isCommentUnread:            'notifications/isCommentUnread',
        }),

        activeScreenComments() {
            return this.getCommentsForScreen(this.activeScreenId);
        },
        totalActiveScreenComments() {
            return this.activeScreenComments.length;
        },
        activeScreenResolvedComments() {
            return this.getCommentsForScreen(this.activeScreenId, 'resolved');
        },
        totalActiveScreenResolvedComments() {
            return this.activeScreenResolvedComments.length;
        },
        activeScreenUnreadComments() {
            return this.getUnreadCommentsForScreen(this.activeScreenId);
        },
        totalActiveScreenUnreadComments() {
            return this.activeScreenUnreadComments.length
        },
        totalActiveScreenUnresolvedComments() {
            return this.totalActiveScreenComments - this.totalActiveScreenResolvedComments;
        },
    },
    watch: {
        activeScreenUnreadComments(newVal, oldVal) {
            // adds unread primary comments to the comment pins list
            for (let i in newVal) {
                if (!newVal[i].replyTo) {
                    this.addComment(newVal[i]);
                }
            }
        },
        activeCommentId(newVal, oldVal) {
            this.updateQueryCommentIdParam();

            if (this.activeComment && this.activeComment.isResolved) {
                this.setShowResolvedComments(true);
            }
        },
    },
    methods: {
        ...mapActions({
            setComments:             'comments/setComments',
            addComment:              'comments/addComment',
            appendComments:          'comments/appendComments',
            setShowResolvedComments: 'comments/setShowResolvedComments',
            setActiveCommentId:      'comments/setActiveCommentId',
        }),

        convertCollaboratorsListToMentionsList(collaborators, excludeEmails = []) {
            collaborators = collaborators || [];

            var result = [];

            for (let i = collaborators.length - 1; i >= 0; i--) {
                if (excludeEmails.indexOf(collaborators[i].email) >= 0) {
                    continue;
                }

                let name = ((collaborators[i].firstName || '') + (collaborators[i].lastName || '')).trim();

                result.push({
                    'value': collaborators[i].email,
                    'label': name ? (name + ' (' + collaborators[i].email +')') : collaborators[i].email,
                });
            }

            return result;
        },
        loadComments(prototypeId, page = 1, forPreview = false) {
            prototypeId = prototypeId || this.activePrototypeId;

            if (!prototypeId || this.isLoadingComments) {
                return;
            }

            this.isLoadingComments = true;

            var request = null;

            ApiClient.enableAutoCancellation(false);

            if (forPreview) {
                request = ApiClient.Previews.getScreenCommentsList(this.previewToken, page, 100, {
                    'envelope':            true,
                    'search[prototypeId]': prototypeId,
                    'search[replyTo]':     0, // only primary
                })
            } else {
                request = ApiClient.ScreenComments.getList(page, 100, {
                    'envelope':            true,
                    'search[prototypeId]': prototypeId,
                    'search[replyTo]':     0, // only primary
                });
            }

            request.finally(() => {
                this.isLoadingComments = false;
            }).then((response) => {
                var commentsData = CommonHelper.getNestedVal(response, 'data.response', []);
                var currentPage  = CommonHelper.getNestedVal(response, 'data.headers.x-pagination-current-page', 1);
                var totalPages   = CommonHelper.getNestedVal(response, 'data.headers.x-pagination-page-count', 1);

                if (page == 1) {
                    this.setComments(commentsData);
                } else {
                    this.appendComments(commentsData);
                }

                // load next portion of hotspots (if there are more)
                if (totalPages > currentPage) {
                    this.loadComments(prototypeId, page + 1, forPreview);
                }

                if (this.$route.query.commentId) {
                    setTimeout(() => {
                        this.setActiveCommentId(this.$route.query.commentId);
                    }, 300); // animation delay
                }
            }).catch((err) => {
                this.$errResponseHandler(err);
            });
        },
        initCommentCreation(e, screenId) {
            if (!this.isInCommentsMode) {
                return;
            }

            this.deactivateComments();

            var comment = new ScreenComment({
                screenId: (screenId || this.activeScreenId),
                left:     this.scaleFactor > 0 ? (e.offsetX / this.scaleFactor) : e.offsetX,
                top:      this.scaleFactor > 0 ? (e.offsetY / this.scaleFactor) : e.offsetY,
            });

            this.addComment(comment);

            this.setActiveCommentId(comment.id);
        },
        deactivateComments() {
            this.setActiveCommentId(null);
        },
        updateQueryCommentIdParam() {
            var query = Object.assign({}, this.$route.query);

            if (this.activeComment && !this.activeComment.isNew) {
                query.commentId = this.activeComment.id;
            } else {
                delete(query.commentId);
            }

            this.$router.replace({
                name: this.$route.name,
                params: Object.assign({}, this.$route.params),
                query: query,
            });
        },
        onCommentRepositioning(comment, elem) {
            if (this.$refs.commentPopover) {
                this.$refs.commentPopover.reposition(elem);
            }
        },
    },
}
<commit_msg>fixed comment typo<commit_after>import { mapState, mapActions, mapGetters } from 'vuex';
import ApiClient     from '@/utils/ApiClient';
import CommonHelper  from '@/utils/CommonHelper';
import ScreenComment from '@/models/ScreenComment';

export default {
    data() {
        return {
            isLoadingComments: false,
            showCommentsPanel: false,
            mentionsList:      [],
        }
    },
    computed: {
        ...mapState({
            activePrototypeId:    state => state.prototypes.activePrototypeId,
            activeScreenId:       state => state.screens.activeScreenId,
            scaleFactor:          state => state.screens.scaleFactor,
            previewToken:         state => state.preview.previewToken,
            showResolvedComments: state => state.comments.showResolvedComments,
            activeCommentId:      state => state.comments.activeCommentId,
            unreadComments:       state => state.notifications.unreadComments,
        }),
        ...mapGetters({
            getCommentsForScreen:       'comments/getCommentsForScreen',
            activeComment:              'comments/activeComment',
            getUnreadCommentsForScreen: 'notifications/getUnreadCommentsForScreen',
            isCommentUnread:            'notifications/isCommentUnread',
        }),

        activeScreenComments() {
            return this.getCommentsForScreen(this.activeScreenId);
        },
        totalActiveScreenComments() {
            return this.activeScreenComments.length;
        },
        activeScreenResolvedComments() {
            return this.getCommentsForScreen(this.activeScreenId, 'resolved');
        },
        totalActiveScreenResolvedComments() {
            return this.activeScreenResolvedComments.length;
        },
        activeScreenUnreadComments() {
            return this.getUnreadCommentsForScreen(this.activeScreenId);
        },
        totalActiveScreenUnreadComments() {
            return this.activeScreenUnreadComments.length
        },
        totalActiveScreenUnresolvedComments() {
            return this.totalActiveScreenComments - this.totalActiveScreenResolvedComments;
        },
    },
    watch: {
        activeScreenUnreadComments(newVal, oldVal) {
            // adds unread primary comments to the comment pins list
            for (let i in newVal) {
                if (!newVal[i].replyTo) {
                    this.addComment(newVal[i]);
                }
            }
        },
        activeCommentId(newVal, oldVal) {
            this.updateQueryCommentIdParam();

            if (this.activeComment && this.activeComment.isResolved) {
                this.setShowResolvedComments(true);
            }
        },
    },
    methods: {
        ...mapActions({
            setComments:             'comments/setComments',
            addComment:              'comments/addComment',
            appendComments:          'comments/appendComments',
            setShowResolvedComments: 'comments/setShowResolvedComments',
            setActiveCommentId:      'comments/setActiveCommentId',
        }),

        convertCollaboratorsListToMentionsList(collaborators, excludeEmails = []) {
            collaborators = collaborators || [];

            var result = [];

            for (let i = collaborators.length - 1; i >= 0; i--) {
                if (excludeEmails.indexOf(collaborators[i].email) >= 0) {
                    continue;
                }

                let name = ((collaborators[i].firstName || '') + (collaborators[i].lastName || '')).trim();

                result.push({
                    'value': collaborators[i].email,
                    'label': name ? (name + ' (' + collaborators[i].email +')') : collaborators[i].email,
                });
            }

            return result;
        },
        loadComments(prototypeId, page = 1, forPreview = false) {
            prototypeId = prototypeId || this.activePrototypeId;

            if (!prototypeId || this.isLoadingComments) {
                return;
            }

            this.isLoadingComments = true;

            var request = null;

            ApiClient.enableAutoCancellation(false);

            if (forPreview) {
                request = ApiClient.Previews.getScreenCommentsList(this.previewToken, page, 100, {
                    'envelope':            true,
                    'search[prototypeId]': prototypeId,
                    'search[replyTo]':     0, // only primary
                })
            } else {
                request = ApiClient.ScreenComments.getList(page, 100, {
                    'envelope':            true,
                    'search[prototypeId]': prototypeId,
                    'search[replyTo]':     0, // only primary
                });
            }

            request.finally(() => {
                this.isLoadingComments = false;
            }).then((response) => {
                var commentsData = CommonHelper.getNestedVal(response, 'data.response', []);
                var currentPage  = CommonHelper.getNestedVal(response, 'data.headers.x-pagination-current-page', 1);
                var totalPages   = CommonHelper.getNestedVal(response, 'data.headers.x-pagination-page-count', 1);

                if (page == 1) {
                    this.setComments(commentsData);
                } else {
                    this.appendComments(commentsData);
                }

                // load next portion of comments (if there are more)
                if (totalPages > currentPage) {
                    this.loadComments(prototypeId, page + 1, forPreview);
                }

                if (this.$route.query.commentId) {
                    setTimeout(() => {
                        this.setActiveCommentId(this.$route.query.commentId);
                    }, 300); // animation delay
                }
            }).catch((err) => {
                this.$errResponseHandler(err);
            });
        },
        initCommentCreation(e, screenId) {
            if (!this.isInCommentsMode) {
                return;
            }

            this.deactivateComments();

            var comment = new ScreenComment({
                screenId: (screenId || this.activeScreenId),
                left:     this.scaleFactor > 0 ? (e.offsetX / this.scaleFactor) : e.offsetX,
                top:      this.scaleFactor > 0 ? (e.offsetY / this.scaleFactor) : e.offsetY,
            });

            this.addComment(comment);

            this.setActiveCommentId(comment.id);
        },
        deactivateComments() {
            this.setActiveCommentId(null);
        },
        updateQueryCommentIdParam() {
            var query = Object.assign({}, this.$route.query);

            if (this.activeComment && !this.activeComment.isNew) {
                query.commentId = this.activeComment.id;
            } else {
                delete(query.commentId);
            }

            this.$router.replace({
                name: this.$route.name,
                params: Object.assign({}, this.$route.params),
                query: query,
            });
        },
        onCommentRepositioning(comment, elem) {
            if (this.$refs.commentPopover) {
                this.$refs.commentPopover.reposition(elem);
            }
        },
    },
}
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import { isFunction } from 'lodash';
import { Provider } from 'react-redux';
import * as appModule from '../redux/modules/veritoneApp';
import appConfig from '../../config.json';
import configureStore from '../redux/configureStore';
import { modules, helpers } from 'veritone-redux-common';
const { auth: authModule, config: configModule, user: userModule } = modules;
const { promiseMiddleware } = helpers;
const {
  WAIT_FOR_ACTION,
  ERROR_ACTION,
  CALLBACK_ERROR_ARGUMENT
} = promiseMiddleware;

class _VeritoneApp {
  _store = configureStore();
  _containerEl = null;

  constructor(config) {
    this._store.dispatch(configModule.setConfig({ ...appConfig, ...config }));
  }

  _register(widget) {
    this._store.dispatch(appModule.widgetAdded(widget));
    this._renderReactApp();
  }

  _unregister(widget) {
    this._store.dispatch(appModule.widgetRemoved(widget));
    this._renderReactApp();
  }

  login({ sessionToken, OAuthToken } = {}) {
    // Allows us to transform dispatch into a promise by adding symbols
    // See promiseMiddleware.js
    const addSymbols = action => {
      return {
        ...action,
        [WAIT_FOR_ACTION]: userModule.FETCH_USER_SUCCESS,
        [ERROR_ACTION]: userModule.FETCH_USER_FAILURE,
        [CALLBACK_ERROR_ARGUMENT]: action => action.payload
      };
    };

    if (sessionToken) {
      return this._store.dispatch(
        addSymbols(authModule.setSessionToken(sessionToken))
      );
    } else if (OAuthToken) {
      return this._store.dispatch(
        addSymbols(authModule.setOAuthToken(OAuthToken))
      );
    } else {
      return this._store.dispatch(addSymbols(authModule.checkAuthNoToken()));
    }
  }

  destroy() {
    if (this._containerEl) {
      ReactDOM.unmountComponentAtNode(this._containerEl);
      try {
        document.body.removeChild(this._containerEl);
      } catch (e) {
        // ignore
      }
    }
  }

  setWidgetRef = (widget, ref) => {
    if (!ref) {
      // protect against errors when destroying the app
      return;
    }

    if (isFunction(ref.getWrappedInstance) && !ref.wrappedInstance) {
      console.warn(
        `Warning: the following widget looks like it's wrapped with a
         @connect decorator, but the withRef option is not set to true.
         { withRef: true } should be set as the fourth argument to @connect`
      );
      console.warn(widget);

      return;
    }

    // try to get at the base component for @connected widgets.
    // fixme: generic solution (hoisting specified instance methods?)
    // https://github.com/elado/hoist-non-react-methods
    const r = isFunction(ref.getWrappedInstance)
      ? ref.getWrappedInstance()
      : ref;

    widget.setRefProperties(r);
  };

  _renderReactApp() {
    this._containerEl = document.getElementById('veritone-react-app');
    if (!this._containerEl) {
      this._containerEl = document.createElement('div');
      this._containerEl.setAttribute('id', 'veritone-react-app');
      document.body.appendChild(this._containerEl);
    }

    ReactDOM.render(
      <Provider store={this._store}>

          {appModule.widgets(this._store.getState()).map(w => {
            if (!w._elId) {
              console.warn(
                'The widget',
                w,
                'needs to specify an elId that references an existing dom node.'
              );
              return null;
            }

            if (document.getElementById(w._elId)) {
              return ReactDOM.createPortal(
                <w.Component
                  {...w.props}
                  // bind is OK because this isn't a component -- only renders
                  // when mount() is called.
                  // eslint-disable-next-line
                  ref={this.setWidgetRef.bind(this, w)}
                />,
                document.getElementById(w._elId)
              );
            }
          })}

      </Provider>,
      this._containerEl
    );
  }
}

let _appSingleton;
export default function VeritoneApp(config, { _isWidget } = {}) {
  // client calls this on init to configure the app:
  // import VeritoneApp from 'veritone-widgets';
  // VeritoneApp({ ...myConfig })
  if (!_appSingleton) {
    if (_isWidget) {
      console.warn(
        `A widget was registered to an app which hasn't yet been initialized. Import and call VeritoneApp before constructing any widgets.`
      );
      return;
    }

    _appSingleton = new _VeritoneApp(config);
  }

  return _appSingleton;
}
<commit_msg>put singleton on window if available. should fix some weird behavior when using script tag builds<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import { isFunction } from 'lodash';
import { Provider } from 'react-redux';
import * as appModule from '../redux/modules/veritoneApp';
import appConfig from '../../config.json';
import configureStore from '../redux/configureStore';
import { modules, helpers } from 'veritone-redux-common';
const { auth: authModule, config: configModule, user: userModule } = modules;
const { promiseMiddleware } = helpers;
const {
  WAIT_FOR_ACTION,
  ERROR_ACTION,
  CALLBACK_ERROR_ARGUMENT
} = promiseMiddleware;

class _VeritoneApp {
  _store = configureStore();
  _containerEl = null;

  constructor(config) {
    this._store.dispatch(configModule.setConfig({ ...appConfig, ...config }));
  }

  _register(widget) {
    this._store.dispatch(appModule.widgetAdded(widget));
    this._renderReactApp();
  }

  _unregister(widget) {
    this._store.dispatch(appModule.widgetRemoved(widget));
    this._renderReactApp();
  }

  login({ sessionToken, OAuthToken } = {}) {
    // Allows us to transform dispatch into a promise by adding symbols
    // See promiseMiddleware.js
    const addSymbols = action => {
      return {
        ...action,
        [WAIT_FOR_ACTION]: userModule.FETCH_USER_SUCCESS,
        [ERROR_ACTION]: userModule.FETCH_USER_FAILURE,
        [CALLBACK_ERROR_ARGUMENT]: action => action.payload
      };
    };

    if (sessionToken) {
      return this._store.dispatch(
        addSymbols(authModule.setSessionToken(sessionToken))
      );
    } else if (OAuthToken) {
      return this._store.dispatch(
        addSymbols(authModule.setOAuthToken(OAuthToken))
      );
    } else {
      return this._store.dispatch(addSymbols(authModule.checkAuthNoToken()));
    }
  }

  destroy() {
    if (this._containerEl) {
      ReactDOM.unmountComponentAtNode(this._containerEl);
      try {
        document.body.removeChild(this._containerEl);
      } catch (e) {
        // ignore
      }
    }
  }

  setWidgetRef = (widget, ref) => {
    if (!ref) {
      // protect against errors when destroying the app
      return;
    }

    if (isFunction(ref.getWrappedInstance) && !ref.wrappedInstance) {
      console.warn(
        `Warning: the following widget looks like it's wrapped with a
         @connect decorator, but the withRef option is not set to true.
         { withRef: true } should be set as the fourth argument to @connect`
      );
      console.warn(widget);

      return;
    }

    // try to get at the base component for @connected widgets.
    // fixme: generic solution (hoisting specified instance methods?)
    // https://github.com/elado/hoist-non-react-methods
    const r = isFunction(ref.getWrappedInstance)
      ? ref.getWrappedInstance()
      : ref;

    widget.setRefProperties(r);
  };

  _renderReactApp() {
    this._containerEl = document.getElementById('veritone-react-app');
    if (!this._containerEl) {
      this._containerEl = document.createElement('div');
      this._containerEl.setAttribute('id', 'veritone-react-app');
      document.body.appendChild(this._containerEl);
    }

    ReactDOM.render(
      <Provider store={this._store}>

          {appModule.widgets(this._store.getState()).map(w => {
            if (!w._elId) {
              console.warn(
                'The widget',
                w,
                'needs to specify an elId that references an existing dom node.'
              );
              return null;
            }

            if (document.getElementById(w._elId)) {
              return ReactDOM.createPortal(
                <w.Component
                  {...w.props}
                  // bind is OK because this isn't a component -- only renders
                  // when mount() is called.
                  // eslint-disable-next-line
                  ref={this.setWidgetRef.bind(this, w)}
                />,
                document.getElementById(w._elId)
              );
            }
          })}

      </Provider>,
      this._containerEl
    );
  }
}

let window = window || {};
export default function VeritoneApp(config, { _isWidget } = {}) {
  // client calls this on init to configure the app:
  // import VeritoneApp from 'veritone-widgets';
  // VeritoneApp({ ...myConfig })
  if (!window.__veritoneAppSingleton) {
    if (_isWidget) {
      console.warn(
        `A widget was registered to an app which hasn't yet been initialized. Import and call VeritoneApp before constructing any widgets.`
      );
      return;
    }

    window.__veritoneAppSingleton = new _VeritoneApp(config);
  }

  return window.__veritoneAppSingleton;
}
<|endoftext|>


<commit_before>var exportKey = function exportKey(format, key, nonce){
  var args = arguments;
  console.log('exportKey args', nonce)
  return new Promise(function rejecter(resolve,reject){
    if (!key._export)
      reject(new Error("operation not supported"));
    else
      resolve(key._export.apply({},[format, key, nonce]))
  });
};

module.exports = exportKey;
<commit_msg>remove log<commit_after>var exportKey = function exportKey(format, key, nonce){
  var args = arguments;
  return new Promise(function rejecter(resolve,reject){
    if (!key._export)
      reject(new Error("operation not supported"));
    else
      resolve(key._export.apply({},[format, key, nonce]))
  });
};

module.exports = exportKey;
<|endoftext|>