<commit_before>/*
 *
 * (c) Copyright Ascensio System SIA 2010-2019
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
*/
/**
 * User: Julia.Svinareva
 * Date: 11.02.2022
 */

define([
    'text!common/main/lib/template/SearchPanel.template',
    'common/main/lib/util/utils',
    'common/main/lib/component/BaseView',
    'common/main/lib/component/Layout'
], function (template) {
    'use strict';

    Common.Views.SearchPanel = Common.UI.BaseView.extend(_.extend({
        el: '#left-panel-search',
        template: _.template(template),

        initialize: function(options) {
            _.extend(this, options);
            Common.UI.BaseView.prototype.initialize.call(this, arguments);

            this.mode = false;

            window.SSE && (this.extendedOptions = Common.localStorage.getBool('sse-search-options-extended', true));
        },

        render: function(el) {
            var me = this;
            if (!this.rendered) {
                el = el || this.el;
                $(el).html(this.template({
                    scope: this
                }));
                this.$el = $(el);

                this.inputText = new Common.UI.InputField({
                    el: $('#search-adv-text'),
                    placeHolder: this.textFind,
                    allowBlank: true,
                    validateOnBlur: false,
                    style: 'width: 100%;',
                    dataHint: '1',
                    dataHintDirection: 'left',
                    dataHintOffset: 'small'
                });
                this.inputText._input.on('input', _.bind(function () {
                    this.fireEvent('search:input', [this.inputText._input.val()]);
                }, this)).on('keydown', _.bind(function (e) {
                    this.fireEvent('search:keydown', [this.inputText._input.val(), e]);
                }, this));

                this.inputReplace = new Common.UI.InputField({
                    el: $('#search-adv-replace-text'),
                    placeHolder: this.textReplaceWith,
                    allowBlank: true,
                    validateOnBlur: false,
                    style: 'width: 100%;',
                    dataHint: '1',
                    dataHintDirection: 'left',
                    dataHintOffset: 'small'
                });

                this.btnBack = new Common.UI.Button({
                    parentEl: $('#search-adv-back'),
                    cls: 'btn-toolbar',
                    iconCls: 'toolbar__icon btn-arrow-up',
                    hint: this.tipPreviousResult,
                    dataHint: '1',
                    dataHintDirection: 'bottom'
                });
                this.btnBack.on('click', _.bind(this.onBtnNextClick, this, 'back'));

                this.btnNext = new Common.UI.Button({
                    parentEl: $('#search-adv-next'),
                    cls: 'btn-toolbar',
                    iconCls: 'toolbar__icon btn-arrow-down',
                    hint: this.tipNextResult,
                    dataHint: '1',
                    dataHintDirection: 'bottom'
                });
                this.btnNext.on('click', _.bind(this.onBtnNextClick, this, 'next'));

                this.btnReplace = new Common.UI.Button({
                    el: $('#search-adv-replace')
                });
                this.btnReplace.on('click', _.bind(this.onReplaceClick, this, 'replace'));

                this.btnReplaceAll = new Common.UI.Button({
                    el: $('#search-adv-replace-all')
                });
                this.btnReplaceAll.on('click', _.bind(this.onReplaceClick, this, 'replaceall'));

                this.$reaultsNumber = $('#search-adv-results-number');
                this.updateResultsNumber('no-results');

                this.chCaseSensitive = new Common.UI.CheckBox({
                    el: $('#search-adv-case-sensitive'),
                    labelText: this.textCaseSensitive,
                    value: false,
                    dataHint: '1',
                    dataHintDirection: 'left',
                    dataHintOffset: 'small'
                }).on('change', function(field) {
                    me.fireEvent('search:options', ['case-sensitive', field.getValue() === 'checked']);
                });

                /*this.chUseRegExp = new Common.UI.CheckBox({
                    el: $('#search-adv-use-regexp'),
                    labelText: this.textMatchUsingRegExp,
                    dataHint: '1',
                    dataHintDirection: 'left',
                    dataHintOffset: 'small'
                }).on('change', function(field) {
                    me.fireEvent('search:options', ['regexp', field.getValue() === 'checked']);
                });*/

                this.chMatchWord = new Common.UI.CheckBox({
                    el: $('#search-adv-match-word'),
                    labelText: window.SSE ? this.textItemEntireCell : this.textWholeWords,
                    value: false,
                    dataHint: '1',
                    dataHintDirection: 'left',
                    dataHintOffset: 'small'
                }).on('change', function(field) {
                    me.fireEvent('search:options', ['match-word', field.getValue() === 'checked']);
                });

                this.buttonClose = new Common.UI.Button({
                    parentEl: $('#search-btn-close', this.$el),
                    cls: 'btn-toolbar',
                    iconCls: 'toolbar__icon btn-close',
                    hint: this.textCloseSearch,
                    dataHint: '1',
                    dataHintDirection: 'bottom',
                    dataHintOffset: 'medium'
                });
                this.buttonClose.on('click', _.bind(this.onClickClosePanel, this));

                this.$resultsContainer = $('#search-results');
                this.$resultsContainer.hide();

                Common.NotificationCenter.on('search:updateresults', _.bind(this.disableNavButtons, this));
                if (window.SSE) {
                    this.cmbWithin = new Common.UI.ComboBox({
                        el: $('#search-adv-cmb-within'),
                        menuStyle: 'min-width: 100%;',
                        style: "width: 219px;",
                        editable: false,
                        cls: 'input-group-nr',
                        data: [
                            { value: 0, displayValue: this.textSheet },
                            { value: 1, displayValue: this.textWorkbook },
                            { value: 2, displayValue: this.textSpecificRange}
                        ],
                        dataHint: '1',
                        dataHintDirection: 'bottom',
                        dataHintOffset: 'big'
                    }).on('selected', function(combo, record) {
                        me.fireEvent('search:options', ['within', record.value]);
                    });

                    this.inputSelectRange = new Common.UI.InputFieldBtn({
                        el: $('#search-adv-select-range'),
                        placeHolder: this.textSelectDataRange,
                        allowBlank: true,
                        validateOnChange: true,
                        validateOnBlur: true,
                        style: "width: 219px; margin-top: 8px",
                        disabled: true,
                        dataHint: '1',
                        dataHintDirection: 'left',
                        dataHintOffset: 'small'
                    }).on('keyup:after', function(input, e) {
                        me.fireEvent('search:options', ['range', input.getValue(), e.keyCode !== Common.UI.Keys.RETURN]);
                    });

                    this.cmbSearch = new Common.UI.ComboBox({
                        el: $('#search-adv-cmb-search'),
                        menuStyle: 'min-width: 100%;',
                        style: "width: 219px;",
                        editable: false,
                        cls: 'input-group-nr',
                        data: [
                            { value: 0, displayValue: this.textByRows },
                            { value: 1, displayValue: this.textByColumns }
                        ],
                        dataHint: '1',
                        dataHintDirection: 'bottom',
                        dataHintOffset: 'big'
                    }).on('selected', function(combo, record) {
                        me.fireEvent('search:options', ['search', !record.value]);
                    });

                    this.cmbLookIn = new Common.UI.ComboBox({
                        el: $('#search-adv-cmb-look-in'),
                        menuStyle: 'min-width: 100%;',
                        style: "width: 219px;",
                        editable: false,
                        cls: 'input-group-nr',
                        data: [
                            { value: 0, displayValue: this.textFormulas },
                            { value: 1, displayValue: this.textValues }
                        ],
                        dataHint: '1',
                        dataHintDirection: 'bottom',
                        dataHintOffset: 'big'
                    }).on('selected', function(combo, record) {
                        me.fireEvent('search:options', ['lookIn', !record.value]);
                    });

                    this.$searchOptionsBlock = $('.search-options-block');
                    this.$searchOptionsBlock.show();
                    $('#open-search-options').on('click', _.bind(this.expandSearchOptions, this));

                    if (!this.extendedOptions) {
                        this.$searchOptionsBlock.addClass('no-expand');
                    }

                    this.cmbWithin.setValue(0);
                    this.cmbSearch.setValue(0);
                    this.cmbLookIn.setValue(0);

                    var tableTemplate = '
' +
                        '
' +
                        '
' + this.textSheet + '
' +
                        '
' + this.textName + '
' +
                        '
' + this.textCell + '
' +
                        '
' + this.textValue + '
' +
                        '
' + this.textFormula + '
' +
                        '
' +
                        '

' +
                        '
',
                        $resultTable = $(tableTemplate).appendTo(this.$resultsContainer);
                    this.$resultsContainer.scroller = new Common.UI.Scroller({
                        el: $resultTable.find('.search-items'),
                        includePadding: true,
                        useKeyboard: true,
                        minScrollbarLength: 40,
                        alwaysVisibleY: true
                    });
                } else {
                    this.$resultsContainer.scroller = new Common.UI.Scroller({
                        el: this.$resultsContainer,
                        includePadding: true,
                        useKeyboard: true,
                        minScrollbarLength: 40,
                        alwaysVisibleY: true
                    });
                }
                Common.NotificationCenter.on('window:resize', function() {
                    me.$resultsContainer.outerHeight($('#search-box').outerHeight() - $('#search-header').outerHeight() - $('#search-adv-settings').outerHeight());
                    me.$resultsContainer.scroller.update({alwaysVisibleY: true});
                });
            }

            this.rendered = true;
            this.trigger('render:after', this);
            return this;
        },

        show: function () {
            Common.UI.BaseView.prototype.show.call(this,arguments);
            this.fireEvent('show', this );

            this.$resultsContainer.outerHeight($('#search-box').outerHeight() - $('#search-header').outerHeight() - $('#search-adv-settings').outerHeight());
            this.$resultsContainer.scroller.update({alwaysVisibleY: true});
        },

        hide: function () {
            Common.UI.BaseView.prototype.hide.call(this,arguments);
            this.fireEvent('hide', this );
        },

        focus: function(type) {
            var me  = this,
                el = type === 'replace' ? me.inputReplace.$el : (type === 'range' ? me.inputSelectRange.$el : me.inputText.$el);
            setTimeout(function(){
                el.find('input').focus();
                el.find('input').select();
            }, 10);
        },

        setSearchMode: function (mode) {
            if (this.mode !== mode) {
                this.$el.find('.edit-setting')[mode !== 'no-replace' ? 'show' : 'hide']();
                this.$el.find('#search-adv-title').text(mode !== 'no-replace' ? this.textFindAndReplace : this.textFind);
                this.mode = mode;
            }
        },

        ChangeSettings: function(props) {
        },

        updateResultsNumber: function (current, count) {
            var text;
            if (count > 300) {
                text = this.textTooManyResults;
            } else {
                text = current === 'no-results' ? this.textNoSearchResults :
                    (current === 'stop' ? this.textSearchHasStopped :
                    (!count ? this.textNoMatches : Common.Utils.String.format(this.textSearchResults, current + 1, count)));
            }
            this.$reaultsNumber.text(text);
            !window.SSE && this.disableReplaceButtons(!count);
        },

        onClickClosePanel: function() {
            Common.NotificationCenter.trigger('leftmenu:change', 'hide');
            this.fireEvent('hide', this );
        },

        onBtnNextClick: function (action) {
            this.fireEvent('search:'+action, [this.inputText.getValue(), true]);
        },

        onReplaceClick: function (action) {
            this.fireEvent('search:'+action, [this.inputText.getValue(), this.inputReplace.getValue()]);
        },

        getSettings: function() {
            return {
                textsearch: this.inputText.getValue(),
                matchcase: this.chCaseSensitive.checked,
                matchword: this.chMatchWord.checked
            };
        },

        expandSearchOptions: function () {
            this.extendedOptions = !this.extendedOptions;
            this.$searchOptionsBlock[this.extendedOptions ? 'removeClass' : 'addClass']('no-expand');
            Common.localStorage.setBool('sse-search-options-extended', this.extendedOptions);

            this.$resultsContainer.outerHeight($('#search-box').outerHeight() - $('#search-header').outerHeight() - $('#search-adv-settings').outerHeight());
            this.$resultsContainer.scroller.update({alwaysVisibleY: true});
        },

        setFindText: function (val) {
            this.inputText.setValue(val);
        },

        clearResultsNumber: function () {
            this.updateResultsNumber('no-results');
        },

        disableNavButtons: function (resultNumber, allResults) {
            var disable = (this.inputText._input.val() === '' && !window.SSE) || !allResults;
            this.btnBack.setDisabled(disable);
            this.btnNext.setDisabled(disable);
        },

        disableReplaceButtons: function (disable) {
            this.btnReplace.setDisabled(disable);
            this.btnReplaceAll.setDisabled(disable);
        },

        textFind: 'Find',
        textFindAndReplace: 'Find and replace',
        textCloseSearch: 'Close search',
        textReplace: 'Replace',
        textReplaceAll: 'Replace All',
        textSearchResults: 'Search results: {0}/{1}',
        textReplaceWith: 'Replace with',
        textCaseSensitive: 'Case sensitive',
        textMatchUsingRegExp: 'Match using regular expressions',
        textWholeWords: 'Whole words only',
        textWithin: 'Within',
        textSelectDataRange: 'Select Data range',
        textSearch: 'Search',
        textLookIn: 'Look in',
        textSheet: 'Sheet',
        textWorkbook: 'Workbook',
        textSpecificRange: 'Specific range',
        textByRows: 'By rows',
        textByColumns: 'By columns',
        textFormulas: 'Formulas',
        textValues: 'Values',
        textSearchOptions: 'Search options',
        textNoMatches: 'No matches',
        textNoSearchResults: 'No search results',
        textItemEntireCell: 'Entire cell contents',
        textTooManyResults: 'There are too many results to show here',
        tipPreviousResult: 'Previous result',
        tipNextResult: 'Next result',
        textName: 'Name',
        textCell: 'Cell',
        textValue: 'Value',
        textFormula: 'Formula',
        textSearchHasStopped: 'Search has stopped'

    }, Common.Views.SearchPanel || {}));
});<commit_msg>[DE PE SSE] Fix bug 58187<commit_after>/*
 *
 * (c) Copyright Ascensio System SIA 2010-2019
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
*/
/**
 * User: Julia.Svinareva
 * Date: 11.02.2022
 */

define([
    'text!common/main/lib/template/SearchPanel.template',
    'common/main/lib/util/utils',
    'common/main/lib/component/BaseView',
    'common/main/lib/component/Layout'
], function (template) {
    'use strict';

    Common.Views.SearchPanel = Common.UI.BaseView.extend(_.extend({
        el: '#left-panel-search',
        template: _.template(template),

        initialize: function(options) {
            _.extend(this, options);
            Common.UI.BaseView.prototype.initialize.call(this, arguments);

            this.mode = false;

            window.SSE && (this.extendedOptions = Common.localStorage.getBool('sse-search-options-extended', true));
        },

        render: function(el) {
            var me = this;
            if (!this.rendered) {
                el = el || this.el;
                $(el).html(this.template({
                    scope: this
                }));
                this.$el = $(el);

                this.inputText = new Common.UI.InputField({
                    el: $('#search-adv-text'),
                    placeHolder: this.textFind,
                    allowBlank: true,
                    validateOnBlur: false,
                    style: 'width: 100%;',
                    dataHint: '1',
                    dataHintDirection: 'left',
                    dataHintOffset: 'small'
                });
                this.inputText._input.on('input', _.bind(function () {
                    this.fireEvent('search:input', [this.inputText._input.val()]);
                }, this)).on('keydown', _.bind(function (e) {
                    this.fireEvent('search:keydown', [this.inputText._input.val(), e]);
                }, this));

                this.inputReplace = new Common.UI.InputField({
                    el: $('#search-adv-replace-text'),
                    placeHolder: this.textReplaceWith,
                    allowBlank: true,
                    validateOnBlur: false,
                    style: 'width: 100%;',
                    dataHint: '1',
                    dataHintDirection: 'left',
                    dataHintOffset: 'small'
                });
                this.inputReplace._input.on('keydown', _.bind(function (e) {
                    if (e.keyCode === Common.UI.Keys.RETURN && !this.btnReplace.isDisabled()) {
                        this.onReplaceClick('replace');
                    }
                }, this));

                this.btnBack = new Common.UI.Button({
                    parentEl: $('#search-adv-back'),
                    cls: 'btn-toolbar',
                    iconCls: 'toolbar__icon btn-arrow-up',
                    hint: this.tipPreviousResult,
                    dataHint: '1',
                    dataHintDirection: 'bottom'
                });
                this.btnBack.on('click', _.bind(this.onBtnNextClick, this, 'back'));

                this.btnNext = new Common.UI.Button({
                    parentEl: $('#search-adv-next'),
                    cls: 'btn-toolbar',
                    iconCls: 'toolbar__icon btn-arrow-down',
                    hint: this.tipNextResult,
                    dataHint: '1',
                    dataHintDirection: 'bottom'
                });
                this.btnNext.on('click', _.bind(this.onBtnNextClick, this, 'next'));

                this.btnReplace = new Common.UI.Button({
                    el: $('#search-adv-replace')
                });
                this.btnReplace.on('click', _.bind(this.onReplaceClick, this, 'replace'));

                this.btnReplaceAll = new Common.UI.Button({
                    el: $('#search-adv-replace-all')
                });
                this.btnReplaceAll.on('click', _.bind(this.onReplaceClick, this, 'replaceall'));

                this.$reaultsNumber = $('#search-adv-results-number');
                this.updateResultsNumber('no-results');

                this.chCaseSensitive = new Common.UI.CheckBox({
                    el: $('#search-adv-case-sensitive'),
                    labelText: this.textCaseSensitive,
                    value: false,
                    dataHint: '1',
                    dataHintDirection: 'left',
                    dataHintOffset: 'small'
                }).on('change', function(field) {
                    me.fireEvent('search:options', ['case-sensitive', field.getValue() === 'checked']);
                });

                /*this.chUseRegExp = new Common.UI.CheckBox({
                    el: $('#search-adv-use-regexp'),
                    labelText: this.textMatchUsingRegExp,
                    dataHint: '1',
                    dataHintDirection: 'left',
                    dataHintOffset: 'small'
                }).on('change', function(field) {
                    me.fireEvent('search:options', ['regexp', field.getValue() === 'checked']);
                });*/

                this.chMatchWord = new Common.UI.CheckBox({
                    el: $('#search-adv-match-word'),
                    labelText: window.SSE ? this.textItemEntireCell : this.textWholeWords,
                    value: false,
                    dataHint: '1',
                    dataHintDirection: 'left',
                    dataHintOffset: 'small'
                }).on('change', function(field) {
                    me.fireEvent('search:options', ['match-word', field.getValue() === 'checked']);
                });

                this.buttonClose = new Common.UI.Button({
                    parentEl: $('#search-btn-close', this.$el),
                    cls: 'btn-toolbar',
                    iconCls: 'toolbar__icon btn-close',
                    hint: this.textCloseSearch,
                    dataHint: '1',
                    dataHintDirection: 'bottom',
                    dataHintOffset: 'medium'
                });
                this.buttonClose.on('click', _.bind(this.onClickClosePanel, this));

                this.$resultsContainer = $('#search-results');
                this.$resultsContainer.hide();

                Common.NotificationCenter.on('search:updateresults', _.bind(this.disableNavButtons, this));
                if (window.SSE) {
                    this.cmbWithin = new Common.UI.ComboBox({
                        el: $('#search-adv-cmb-within'),
                        menuStyle: 'min-width: 100%;',
                        style: "width: 219px;",
                        editable: false,
                        cls: 'input-group-nr',
                        data: [
                            { value: 0, displayValue: this.textSheet },
                            { value: 1, displayValue: this.textWorkbook },
                            { value: 2, displayValue: this.textSpecificRange}
                        ],
                        dataHint: '1',
                        dataHintDirection: 'bottom',
                        dataHintOffset: 'big'
                    }).on('selected', function(combo, record) {
                        me.fireEvent('search:options', ['within', record.value]);
                    });

                    this.inputSelectRange = new Common.UI.InputFieldBtn({
                        el: $('#search-adv-select-range'),
                        placeHolder: this.textSelectDataRange,
                        allowBlank: true,
                        validateOnChange: true,
                        validateOnBlur: true,
                        style: "width: 219px; margin-top: 8px",
                        disabled: true,
                        dataHint: '1',
                        dataHintDirection: 'left',
                        dataHintOffset: 'small'
                    }).on('keyup:after', function(input, e) {
                        me.fireEvent('search:options', ['range', input.getValue(), e.keyCode !== Common.UI.Keys.RETURN]);
                    });

                    this.cmbSearch = new Common.UI.ComboBox({
                        el: $('#search-adv-cmb-search'),
                        menuStyle: 'min-width: 100%;',
                        style: "width: 219px;",
                        editable: false,
                        cls: 'input-group-nr',
                        data: [
                            { value: 0, displayValue: this.textByRows },
                            { value: 1, displayValue: this.textByColumns }
                        ],
                        dataHint: '1',
                        dataHintDirection: 'bottom',
                        dataHintOffset: 'big'
                    }).on('selected', function(combo, record) {
                        me.fireEvent('search:options', ['search', !record.value]);
                    });

                    this.cmbLookIn = new Common.UI.ComboBox({
                        el: $('#search-adv-cmb-look-in'),
                        menuStyle: 'min-width: 100%;',
                        style: "width: 219px;",
                        editable: false,
                        cls: 'input-group-nr',
                        data: [
                            { value: 0, displayValue: this.textFormulas },
                            { value: 1, displayValue: this.textValues }
                        ],
                        dataHint: '1',
                        dataHintDirection: 'bottom',
                        dataHintOffset: 'big'
                    }).on('selected', function(combo, record) {
                        me.fireEvent('search:options', ['lookIn', !record.value]);
                    });

                    this.$searchOptionsBlock = $('.search-options-block');
                    this.$searchOptionsBlock.show();
                    $('#open-search-options').on('click', _.bind(this.expandSearchOptions, this));

                    if (!this.extendedOptions) {
                        this.$searchOptionsBlock.addClass('no-expand');
                    }

                    this.cmbWithin.setValue(0);
                    this.cmbSearch.setValue(0);
                    this.cmbLookIn.setValue(0);

                    var tableTemplate = '
' +
                        '
' +
                        '
' + this.textSheet + '
' +
                        '
' + this.textName + '
' +
                        '
' + this.textCell + '
' +
                        '
' + this.textValue + '
' +
                        '
' + this.textFormula + '
' +
                        '
' +
                        '

' +
                        '
',
                        $resultTable = $(tableTemplate).appendTo(this.$resultsContainer);
                    this.$resultsContainer.scroller = new Common.UI.Scroller({
                        el: $resultTable.find('.search-items'),
                        includePadding: true,
                        useKeyboard: true,
                        minScrollbarLength: 40,
                        alwaysVisibleY: true
                    });
                } else {
                    this.$resultsContainer.scroller = new Common.UI.Scroller({
                        el: this.$resultsContainer,
                        includePadding: true,
                        useKeyboard: true,
                        minScrollbarLength: 40,
                        alwaysVisibleY: true
                    });
                }
                Common.NotificationCenter.on('window:resize', function() {
                    me.$resultsContainer.outerHeight($('#search-box').outerHeight() - $('#search-header').outerHeight() - $('#search-adv-settings').outerHeight());
                    me.$resultsContainer.scroller.update({alwaysVisibleY: true});
                });
            }

            this.rendered = true;
            this.trigger('render:after', this);
            return this;
        },

        show: function () {
            Common.UI.BaseView.prototype.show.call(this,arguments);
            this.fireEvent('show', this );

            this.$resultsContainer.outerHeight($('#search-box').outerHeight() - $('#search-header').outerHeight() - $('#search-adv-settings').outerHeight());
            this.$resultsContainer.scroller.update({alwaysVisibleY: true});
        },

        hide: function () {
            Common.UI.BaseView.prototype.hide.call(this,arguments);
            this.fireEvent('hide', this );
        },

        focus: function(type) {
            var me  = this,
                el = type === 'replace' ? me.inputReplace.$el : (type === 'range' ? me.inputSelectRange.$el : me.inputText.$el);
            setTimeout(function(){
                el.find('input').focus();
                el.find('input').select();
            }, 10);
        },

        setSearchMode: function (mode) {
            if (this.mode !== mode) {
                this.$el.find('.edit-setting')[mode !== 'no-replace' ? 'show' : 'hide']();
                this.$el.find('#search-adv-title').text(mode !== 'no-replace' ? this.textFindAndReplace : this.textFind);
                this.mode = mode;
            }
        },

        ChangeSettings: function(props) {
        },

        updateResultsNumber: function (current, count) {
            var text;
            if (count > 300) {
                text = this.textTooManyResults;
            } else {
                text = current === 'no-results' ? this.textNoSearchResults :
                    (current === 'stop' ? this.textSearchHasStopped :
                    (!count ? this.textNoMatches : Common.Utils.String.format(this.textSearchResults, current + 1, count)));
            }
            this.$reaultsNumber.text(text);
            !window.SSE && this.disableReplaceButtons(!count);
        },

        onClickClosePanel: function() {
            Common.NotificationCenter.trigger('leftmenu:change', 'hide');
            this.fireEvent('hide', this );
        },

        onBtnNextClick: function (action) {
            this.fireEvent('search:'+action, [this.inputText.getValue(), true]);
        },

        onReplaceClick: function (action) {
            this.fireEvent('search:'+action, [this.inputText.getValue(), this.inputReplace.getValue()]);
        },

        getSettings: function() {
            return {
                textsearch: this.inputText.getValue(),
                matchcase: this.chCaseSensitive.checked,
                matchword: this.chMatchWord.checked
            };
        },

        expandSearchOptions: function () {
            this.extendedOptions = !this.extendedOptions;
            this.$searchOptionsBlock[this.extendedOptions ? 'removeClass' : 'addClass']('no-expand');
            Common.localStorage.setBool('sse-search-options-extended', this.extendedOptions);

            this.$resultsContainer.outerHeight($('#search-box').outerHeight() - $('#search-header').outerHeight() - $('#search-adv-settings').outerHeight());
            this.$resultsContainer.scroller.update({alwaysVisibleY: true});
        },

        setFindText: function (val) {
            this.inputText.setValue(val);
        },

        clearResultsNumber: function () {
            this.updateResultsNumber('no-results');
        },

        disableNavButtons: function (resultNumber, allResults) {
            var disable = (this.inputText._input.val() === '' && !window.SSE) || !allResults;
            this.btnBack.setDisabled(disable);
            this.btnNext.setDisabled(disable);
        },

        disableReplaceButtons: function (disable) {
            this.btnReplace.setDisabled(disable);
            this.btnReplaceAll.setDisabled(disable);
        },

        textFind: 'Find',
        textFindAndReplace: 'Find and replace',
        textCloseSearch: 'Close search',
        textReplace: 'Replace',
        textReplaceAll: 'Replace All',
        textSearchResults: 'Search results: {0}/{1}',
        textReplaceWith: 'Replace with',
        textCaseSensitive: 'Case sensitive',
        textMatchUsingRegExp: 'Match using regular expressions',
        textWholeWords: 'Whole words only',
        textWithin: 'Within',
        textSelectDataRange: 'Select Data range',
        textSearch: 'Search',
        textLookIn: 'Look in',
        textSheet: 'Sheet',
        textWorkbook: 'Workbook',
        textSpecificRange: 'Specific range',
        textByRows: 'By rows',
        textByColumns: 'By columns',
        textFormulas: 'Formulas',
        textValues: 'Values',
        textSearchOptions: 'Search options',
        textNoMatches: 'No matches',
        textNoSearchResults: 'No search results',
        textItemEntireCell: 'Entire cell contents',
        textTooManyResults: 'There are too many results to show here',
        tipPreviousResult: 'Previous result',
        tipNextResult: 'Next result',
        textName: 'Name',
        textCell: 'Cell',
        textValue: 'Value',
        textFormula: 'Formula',
        textSearchHasStopped: 'Search has stopped'

    }, Common.Views.SearchPanel || {}));
});<|endoftext|>


<commit_before>// Copyright 2015 Yahoo! Inc.
// Copyrights licensed under the Mit License. See the accompanying LICENSE file for terms.

/**
 * @class BufferedStream
 * @module PNG
 * @submodule PNGCore
 * @param {Buffer} [data] Data buffer
 * @param {int|boolean} [offset=0] Offset within the data (With offset = false, they data will be taken as-is)
 * @param {int} [length=data.length-offset] Length of the data
 * @constructor
 */
var BufferedStream = function (data, offset, length) {

	this.readOffset = 0;
	this.writeOffset = 0;

	this.readCounter = 0;
	this.writeCounter = 0;

	if (data) {
		if (offset === false) {
			this._data = data;
			this.writeOffset = data.length;
		} else {
			this._data = new Buffer((length || (data.length - (offset || 0))) * 2);
			this.writeBuffer(data, offset, length);
		}
	} else {
		if (length === 0) {
			this._data = new Buffer();
		} else {
			this._data = new Buffer(length || 1024 * 500);
		}
	}
};

Object.defineProperty(BufferedStream.prototype, 'length', {
	enumerable: false,
	configurable: false,
	get: function () {
		return this.writeOffset - this.readOffset;
	},
	set: function (length) {
		if (this.readOffset + length > this.writeOffset) {
			throw new Error('Length beyond the write pointer is not allowed.');
		}
		this.writeOffset = this.readOffset + length;
	}
});

/**
 * Checks if a read goes beyond the write pointer, reaching out of bounds
 *
 * @method _readCheck
 * @param {int} size Size of the data that is pending to be read from the stream
 * @private
 */
BufferedStream.prototype._readCheck = function (size) {
	if (this.readOffset + size > this.writeOffset) {
		throw new Error('Reading out of bounds.');
	}
};

/**
 * Skips a number of bytes
 *
 * @param {int} count Number of bytes to skip
 */
BufferedStream.prototype.skip = function (count) {
	this.readOffset += count;
	this.readCounter += count;
};

/**
 * @method readUInt8
 * @param {boolean} [noAssert=false]
 * @return {int}
 */
/**
 * @method peekUInt8
 * @param {boolean} [noAssert=false]
 * @return {int}
 */
[
	['UInt16LE', 2],
	['UInt16BE', 2],
	['UInt32LE', 4],
	['UInt32BE', 4],
	['Int8', 1],
	['Int16LE', 2],
	['Int16BE', 2],
	['Int32LE', 4],
	['Int32BE', 4],
	['FloatLE', 4],
	['FloatBE', 4],
	['DoubleLE', 8],
	['DoubleBE', 8]
].forEach(function (fnInfo) {

		['read', 'peek'].forEach(function (prefix) {
			var relayedMethodName = 'read' + fnInfo[0],
				methodName = prefix + fnInfo[0],
				peeking = (prefix === 'peek'),
				size = fnInfo[1];

			BufferedStream.prototype[methodName] = function (noAssert) {
				var value;

				this._readCheck(size);

				value = this._data[relayedMethodName](this.readOffset, noAssert);
				if (!peeking) {
					this.readOffset += size;
					this.readCounter += size;
				}

				return value;
			}
		});
	});

BufferedStream.prototype.peekUInt8 = function () {
	return this._data[this.readOffset];
};

BufferedStream.prototype.readUInt8 = function () {
	var result = this._data[this.readOffset];
	this.readOffset++;
	this.readCounter++;
	return result;
};

BufferedStream.prototype.writeUInt8 = function (value) {
	this._writeCheck(1);
	this._data[this.writeOffset] = value & 0xff;
	this.writeOffset++;
	this.writeCounter++;
};

//BufferedStream.prototype.peekUInt16BE = function () {
//	return (this._data[this.readOffset] << 8) | this._data[this.readOffset + 1]
//};
//
//BufferedStream.prototype.readUInt16BE = function () {
//	var result = this.peekUInt16BE();
//	this.readOffset += 2;
//	this.readCounter += 2;
//	return result;
//};

BufferedStream.prototype.writeUInt16BE = function (value) {
	this._writeCheck(2);
	this._data[this.writeOffset] = (value >>> 8) & 0xff;
	this._data[this.writeOffset + 1] = value & 0xff;
	this.writeOffset += 2;
	this.writeCounter += 2;
};

//BufferedStream.prototype.peekUInt32BE = function () {
//	return (this._data[this.readOffset] * 0x1000000) +
//		((this._data[this.readOffset + 1] << 16) |
//		(this._data[this.readOffset + 2] << 8) |
//		this._data[this.readOffset + 3]);
//};
//
//BufferedStream.prototype.readUInt32BE = function () {
//	var result = this.peekUInt32BE();
//	this.readOffset += 4;
//	this.readCounter += 4;
//	return result;
//};

BufferedStream.prototype.writeUInt32BE = function (value) {
	this._writeCheck(4);
	this._data[this.writeOffset] = (value >>> 24) & 0xff;
	this._data[this.writeOffset + 1] = (value >>> 16) & 0xff;
	this._data[this.writeOffset + 2] = (value >>> 8) & 0xff;
	this._data[this.writeOffset + 3] = value & 0xff;
	this.writeOffset += 4;
	this.writeCounter += 4;
};

/**
 * Reads a string from the stream without moving the read pointer
 *
 * @method peekString
 * @param {int} size Number of bytes to read from stream
 * @param {string} [encoding='utf8'] Encoding of string
 * @return {string}
 */
BufferedStream.prototype.peekString = function (size, encoding) {
	this._readCheck(size);
	return this._data.toString(encoding || 'utf8', this.readOffset, this.readOffset + size);
};

/**
 * Reads a string from the stream
 *
 * @method readString
 * @param {int} size Number of bytes to read from stream
 * @param {string} [encoding='utf8'] Encoding of string
 * @return {string}
 */
BufferedStream.prototype.readString = function (size, encoding) {
	var result = this.peekString(size, encoding);
	this.readOffset += size;
	this.readCounter += size;
	return result;
};

/**
 * Reads data from the stream into a buffer without moving the read pointer
 *
 * @method peekBuffer
 * @param {int} size Number of bytes that should be read from the stream
 * @returns {Buffer}
 */
BufferedStream.prototype.peekBuffer = function (size) {
	var buffer;

	this._readCheck(size);

	buffer = new Buffer(size);
	this._data.copy(buffer, 0, this.readOffset, this.readOffset + size);

	return buffer;
};

/**
 * Reads data from the stream into a buffer
 *
 * @method readBuffer
 * @param {int} size Number of bytes that should be read from the stream
 * @return {Buffer}
 */
BufferedStream.prototype.readBuffer = function (size) {
	var buffer = this.peekBuffer(size);
	this.readOffset += size;
	this.readCounter += size;
	return buffer;
};

/**
 * Reads data from the stream into a buffer
 *
 * @method peekBufferedStream
 * @param {BufferedStream} stream Stream to write to
 * @param {int} size Number of bytes that should be read
 */
BufferedStream.prototype.peekBufferedStream = function (stream, size) {
	stream.writeBufferedStream(this, size);
};

/**
 * Reads data from the stream into a buffer
 *
 * @method readBufferedStream
 * @param {BufferedStream} stream Stream to write to
 * @param {int} size Number of bytes that should be read
 */
BufferedStream.prototype.readBufferedStream = function (stream, size) {
	this.peekBufferedStream(stream, size);
	this.readOffset += size;
	this.readCounter += size;
};

/**
 * Checks if a write needs a re-size
 *
 * @method _writeCheck
 * @param {int} size Size that is pending to be written to the stream
 * @private
 */
BufferedStream.prototype._writeCheck = function (size) {
	if (this._data.length < this.writeOffset + size) {
		this._resize();
	}
};

/**
 * Re-sizes the internal buffer, copying all existing data into it
 *
 * @method _resize
 * @private
 */
BufferedStream.prototype._resize = function () {
	var buffer = new Buffer(this._data.length * 2);
	this._data.copy(buffer, 0, 0, this.writeOffset);
	this._data = buffer;
};

/**
 * @method writeUInt8
 * @param {boolean} [noAssert=false]
 */
[
	['writeUInt16LE', 2],
	['writeUInt32LE', 4],
	['writeInt8', 1],
	['writeInt16LE', 2],
	['writeInt16BE', 2],
	['writeInt32LE', 4],
	['writeInt32BE', 4],
	['writeFloatLE', 4],
	['writeFloatBE', 4],
	['writeDoubleLE', 8],
	['writeDoubleBE', 8]
].forEach(function (fnInfo) {
		BufferedStream.prototype[fnInfo[0]] = function (value, noAssert) {
			this._writeCheck(fnInfo[1]);
			this._data[fnInfo[0]](value, this.writeOffset, noAssert);
			this.writeOffset += fnInfo[1];
			this.writeCounter += fnInfo[1];
		}
	});

/**
 * Writes a string to the stream
 *
 * @method writeASCIIString
 * @param {string} text Text to write to the stream
 */
BufferedStream.prototype.writeASCIIString = function (text) {

	this._writeCheck(text.length);

	for(var i = 0, len = text.length; i < len; i++) {
		this._data.writeUInt8(text.charCodeAt(i) & 0xff, this.writeOffset + i);
	}
	this.writeOffset += text.length;
	this.writeCounter += text.length;
};

/**
 * Writes a string to the stream
 *
 * @method writeString
 * @param {string} text Text to write to the stream
 * @param {string} [encoding='utf8'] Encoding of string
 */
BufferedStream.prototype.writeString = function (text, encoding) {
	this.writeBuffer(new Buffer(text, encoding || 'utf8'));
};

/**
 * Writes a buffer to the stream
 *
 * @method writeBuffer
 * @param {Buffer} buffer Data buffer
 * @param {int} [offset=0] Offset within buffer
 * @param {int} [length=buffer.length-offset] length Length of buffer
 */
BufferedStream.prototype.writeBuffer = function (buffer, offset, length) {
	var localOffset = offset || 0,
		  localLength = length || (buffer.length - localOffset);

	this._writeCheck(localLength);

	buffer.copy(this._data, this.writeOffset, localOffset, localOffset + localLength);
	this.writeOffset += localLength;
	this.writeCounter += localLength;
};

/**
 * Writes a buffered stream into the stream
 *
 * @param {BufferedStream} stream
 * @param {int} [length=stream.length] Length of buffered data
 */
BufferedStream.prototype.writeBufferedStream = function (stream, length) {
	var len = (length || stream.length);

	this._writeCheck(len);

	stream._data.copy(this._data, this.writeOffset, stream.readOffset, stream.readOffset + len);
	this.writeOffset += len;
	this.writeCounter += len;
};

/**
 * Clones the stream with the current internal state
 *
 * @method clone
 * @return {BufferedStream} Cloned stream
 */
BufferedStream.prototype.clone = function () {
	return this.slice();
};

/**
 * Slices the stream
 *
 * @method slice
 * @param {int} [start=0] Start of stream
 * @param {int} [end=this.length] End of stream
 * @return {BufferedStream} Sliced stream
 */
BufferedStream.prototype.slice = function (start, end) {

	var stream,
		localStart = start || 0,
		localEnd = end || this._data.length;

	if (localStart > localEnd) {
		throw new Error('End cannot be smaller than start.');
	}

	stream = new BufferedStream(this._data, false);

	stream.writeOffset = this.readOffset + localEnd;
	stream.readOffset = this.readOffset + localStart;

	stream.readCounter = this.readCounter;
	stream.writeCounter = this.writeCounter;

	return stream;
};

/**
 * Converts contents to a buffer without moving the read pointer
 *
 * @method toBuffer
 * @param {boolean} [noCopy=false]
 * @return {Buffer}
 */
BufferedStream.prototype.toBuffer = function (noCopy) {
	if (noCopy) {
		return this._data;
	} else {
		return this.peekBuffer(this.length);
	}
};

module.exports = BufferedStream;
<commit_msg>Add method to figure out how much space if left in the buffer before resizing<commit_after>// Copyright 2015 Yahoo! Inc.
// Copyrights licensed under the Mit License. See the accompanying LICENSE file for terms.

/**
 * @class BufferedStream
 * @module PNG
 * @submodule PNGCore
 * @param {Buffer} [data] Data buffer
 * @param {int|boolean} [offset=0] Offset within the data (With offset = false, they data will be taken as-is)
 * @param {int} [length=data.length-offset] Length of the data
 * @constructor
 */
var BufferedStream = function (data, offset, length) {

	this.readOffset = 0;
	this.writeOffset = 0;

	this.readCounter = 0;
	this.writeCounter = 0;

	if (data) {
		if (offset === false) {
			this._data = data;
			this.writeOffset = data.length;
		} else {
			this._data = new Buffer((length || (data.length - (offset || 0))) * 2);
			this.writeBuffer(data, offset, length);
		}
	} else {
		if (length === 0) {
			this._data = new Buffer();
		} else {
			this._data = new Buffer(length || 1024 * 500);
		}
	}
};

Object.defineProperty(BufferedStream.prototype, 'length', {
	enumerable: false,
	configurable: false,
	get: function () {
		return this.writeOffset - this.readOffset;
	},
	set: function (length) {
		if (this.readOffset + length > this.writeOffset) {
			throw new Error('Length beyond the write pointer is not allowed.');
		}
		this.writeOffset = this.readOffset + length;
	}
});

/**
 * Returns the number of bytes left before resizing
 *
 * @method getSpaceLeft
 * @return {int}
 */
BufferedStream.prototype.getSpaceLeft = function () {
	return this._data.length - this.writeOffset;
};

/**
 * Checks if a read goes beyond the write pointer, reaching out of bounds
 *
 * @method _readCheck
 * @param {int} size Size of the data that is pending to be read from the stream
 * @private
 */
BufferedStream.prototype._readCheck = function (size) {
	if (this.readOffset + size > this.writeOffset) {
		throw new Error('Reading out of bounds.');
	}
};

/**
 * Skips a number of bytes
 *
 * @param {int} count Number of bytes to skip
 */
BufferedStream.prototype.skip = function (count) {
	this.readOffset += count;
	this.readCounter += count;
};

/**
 * @method readUInt8
 * @param {boolean} [noAssert=false]
 * @return {int}
 */
/**
 * @method peekUInt8
 * @param {boolean} [noAssert=false]
 * @return {int}
 */
[
	['UInt16LE', 2],
	['UInt16BE', 2],
	['UInt32LE', 4],
	['UInt32BE', 4],
	['Int8', 1],
	['Int16LE', 2],
	['Int16BE', 2],
	['Int32LE', 4],
	['Int32BE', 4],
	['FloatLE', 4],
	['FloatBE', 4],
	['DoubleLE', 8],
	['DoubleBE', 8]
].forEach(function (fnInfo) {

		['read', 'peek'].forEach(function (prefix) {
			var relayedMethodName = 'read' + fnInfo[0],
				methodName = prefix + fnInfo[0],
				peeking = (prefix === 'peek'),
				size = fnInfo[1];

			BufferedStream.prototype[methodName] = function (noAssert) {
				var value;

				this._readCheck(size);

				value = this._data[relayedMethodName](this.readOffset, noAssert);
				if (!peeking) {
					this.readOffset += size;
					this.readCounter += size;
				}

				return value;
			}
		});
	});

BufferedStream.prototype.peekUInt8 = function () {
	return this._data[this.readOffset];
};

BufferedStream.prototype.readUInt8 = function () {
	var result = this._data[this.readOffset];
	this.readOffset++;
	this.readCounter++;
	return result;
};

BufferedStream.prototype.writeUInt8 = function (value) {
	this._writeCheck(1);
	this._data[this.writeOffset] = value & 0xff;
	this.writeOffset++;
	this.writeCounter++;
};

//BufferedStream.prototype.peekUInt16BE = function () {
//	return (this._data[this.readOffset] << 8) | this._data[this.readOffset + 1]
//};
//
//BufferedStream.prototype.readUInt16BE = function () {
//	var result = this.peekUInt16BE();
//	this.readOffset += 2;
//	this.readCounter += 2;
//	return result;
//};

BufferedStream.prototype.writeUInt16BE = function (value) {
	this._writeCheck(2);
	this._data[this.writeOffset] = (value >>> 8) & 0xff;
	this._data[this.writeOffset + 1] = value & 0xff;
	this.writeOffset += 2;
	this.writeCounter += 2;
};

//BufferedStream.prototype.peekUInt32BE = function () {
//	return (this._data[this.readOffset] * 0x1000000) +
//		((this._data[this.readOffset + 1] << 16) |
//		(this._data[this.readOffset + 2] << 8) |
//		this._data[this.readOffset + 3]);
//};
//
//BufferedStream.prototype.readUInt32BE = function () {
//	var result = this.peekUInt32BE();
//	this.readOffset += 4;
//	this.readCounter += 4;
//	return result;
//};

BufferedStream.prototype.writeUInt32BE = function (value) {
	this._writeCheck(4);
	this._data[this.writeOffset] = (value >>> 24) & 0xff;
	this._data[this.writeOffset + 1] = (value >>> 16) & 0xff;
	this._data[this.writeOffset + 2] = (value >>> 8) & 0xff;
	this._data[this.writeOffset + 3] = value & 0xff;
	this.writeOffset += 4;
	this.writeCounter += 4;
};

/**
 * Reads a string from the stream without moving the read pointer
 *
 * @method peekString
 * @param {int} size Number of bytes to read from stream
 * @param {string} [encoding='utf8'] Encoding of string
 * @return {string}
 */
BufferedStream.prototype.peekString = function (size, encoding) {
	this._readCheck(size);
	return this._data.toString(encoding || 'utf8', this.readOffset, this.readOffset + size);
};

/**
 * Reads a string from the stream
 *
 * @method readString
 * @param {int} size Number of bytes to read from stream
 * @param {string} [encoding='utf8'] Encoding of string
 * @return {string}
 */
BufferedStream.prototype.readString = function (size, encoding) {
	var result = this.peekString(size, encoding);
	this.readOffset += size;
	this.readCounter += size;
	return result;
};

/**
 * Reads data from the stream into a buffer without moving the read pointer
 *
 * @method peekBuffer
 * @param {int} size Number of bytes that should be read from the stream
 * @returns {Buffer}
 */
BufferedStream.prototype.peekBuffer = function (size) {
	var buffer;

	this._readCheck(size);

	buffer = new Buffer(size);
	this._data.copy(buffer, 0, this.readOffset, this.readOffset + size);

	return buffer;
};

/**
 * Reads data from the stream into a buffer
 *
 * @method readBuffer
 * @param {int} size Number of bytes that should be read from the stream
 * @return {Buffer}
 */
BufferedStream.prototype.readBuffer = function (size) {
	var buffer = this.peekBuffer(size);
	this.readOffset += size;
	this.readCounter += size;
	return buffer;
};

/**
 * Reads data from the stream into a buffer
 *
 * @method peekBufferedStream
 * @param {BufferedStream} stream Stream to write to
 * @param {int} size Number of bytes that should be read
 */
BufferedStream.prototype.peekBufferedStream = function (stream, size) {
	stream.writeBufferedStream(this, size);
};

/**
 * Reads data from the stream into a buffer
 *
 * @method readBufferedStream
 * @param {BufferedStream} stream Stream to write to
 * @param {int} size Number of bytes that should be read
 */
BufferedStream.prototype.readBufferedStream = function (stream, size) {
	this.peekBufferedStream(stream, size);
	this.readOffset += size;
	this.readCounter += size;
};

/**
 * Checks if a write needs a re-size
 *
 * @method _writeCheck
 * @param {int} size Size that is pending to be written to the stream
 * @private
 */
BufferedStream.prototype._writeCheck = function (size) {
	if (this._data.length < this.writeOffset + size) {
		this._resize();
	}
};

/**
 * Re-sizes the internal buffer, copying all existing data into it
 *
 * @method _resize
 * @private
 */
BufferedStream.prototype._resize = function () {
	var buffer = new Buffer(this._data.length * 2);
	this._data.copy(buffer, 0, 0, this.writeOffset);
	this._data = buffer;
};

/**
 * @method writeUInt8
 * @param {boolean} [noAssert=false]
 */
[
	['writeUInt16LE', 2],
	['writeUInt32LE', 4],
	['writeInt8', 1],
	['writeInt16LE', 2],
	['writeInt16BE', 2],
	['writeInt32LE', 4],
	['writeInt32BE', 4],
	['writeFloatLE', 4],
	['writeFloatBE', 4],
	['writeDoubleLE', 8],
	['writeDoubleBE', 8]
].forEach(function (fnInfo) {
		BufferedStream.prototype[fnInfo[0]] = function (value, noAssert) {
			this._writeCheck(fnInfo[1]);
			this._data[fnInfo[0]](value, this.writeOffset, noAssert);
			this.writeOffset += fnInfo[1];
			this.writeCounter += fnInfo[1];
		}
	});

/**
 * Writes a string to the stream
 *
 * @method writeASCIIString
 * @param {string} text Text to write to the stream
 */
BufferedStream.prototype.writeASCIIString = function (text) {

	this._writeCheck(text.length);

	for(var i = 0, len = text.length; i < len; i++) {
		this._data.writeUInt8(text.charCodeAt(i) & 0xff, this.writeOffset + i);
	}
	this.writeOffset += text.length;
	this.writeCounter += text.length;
};

/**
 * Writes a string to the stream
 *
 * @method writeString
 * @param {string} text Text to write to the stream
 * @param {string} [encoding='utf8'] Encoding of string
 */
BufferedStream.prototype.writeString = function (text, encoding) {
	this.writeBuffer(new Buffer(text, encoding || 'utf8'));
};

/**
 * Writes a buffer to the stream
 *
 * @method writeBuffer
 * @param {Buffer} buffer Data buffer
 * @param {int} [offset=0] Offset within buffer
 * @param {int} [length=buffer.length-offset] length Length of buffer
 */
BufferedStream.prototype.writeBuffer = function (buffer, offset, length) {
	var localOffset = offset || 0,
		  localLength = length || (buffer.length - localOffset);

	this._writeCheck(localLength);

	buffer.copy(this._data, this.writeOffset, localOffset, localOffset + localLength);
	this.writeOffset += localLength;
	this.writeCounter += localLength;
};

/**
 * Writes a buffered stream into the stream
 *
 * @param {BufferedStream} stream
 * @param {int} [length=stream.length] Length of buffered data
 */
BufferedStream.prototype.writeBufferedStream = function (stream, length) {
	var len = (length || stream.length);

	this._writeCheck(len);

	stream._data.copy(this._data, this.writeOffset, stream.readOffset, stream.readOffset + len);
	this.writeOffset += len;
	this.writeCounter += len;
};

/**
 * Clones the stream with the current internal state
 *
 * @method clone
 * @return {BufferedStream} Cloned stream
 */
BufferedStream.prototype.clone = function () {
	return this.slice();
};

/**
 * Slices the stream
 *
 * @method slice
 * @param {int} [start=0] Start of stream
 * @param {int} [end=this.length] End of stream
 * @return {BufferedStream} Sliced stream
 */
BufferedStream.prototype.slice = function (start, end) {

	var stream,
		localStart = start || 0,
		localEnd = end || this._data.length;

	if (localStart > localEnd) {
		throw new Error('End cannot be smaller than start.');
	}

	stream = new BufferedStream(this._data, false);

	stream.writeOffset = this.readOffset + localEnd;
	stream.readOffset = this.readOffset + localStart;

	stream.readCounter = this.readCounter;
	stream.writeCounter = this.writeCounter;

	return stream;
};

/**
 * Converts contents to a buffer without moving the read pointer
 *
 * @method toBuffer
 * @param {boolean} [noCopy=false]
 * @return {Buffer}
 */
BufferedStream.prototype.toBuffer = function (noCopy) {
	if (noCopy) {
		return this._data;
	} else {
		return this.peekBuffer(this.length);
	}
};

module.exports = BufferedStream;
<|endoftext|>


<commit_before>var flow = require("flow.js").flow,
    fs = require("fs"),
    path = require("path"),
    crypto = require('crypto'),
    moment = require('moment');

var mod = module.exports = function (app, config) {

    var styleCache = {},
        styleUrlMatcher = /^\/style\/([^/]+).css(?:\?([^=]+))?/,
        reloadCss = (config.environment["reloadCss"] === true);

    app.use(function (req, res, next) {

        if (req.method === "GET" || req.method === "HEAD") {
            var match = styleUrlMatcher.exec(req.url);

            if (match) {
                var styleName = match[1],
                    hash = match[2],
                    cache = styleCache,
                    isDevelop = hash === "develop";

                if (isDevelop) {
                    // do not cache
                    cache = {};
                }

                mod.getStyle(styleName, hash, cache, config.baseUri, function (err, style) {

                    if (isDevelop) {
                        // do not cache
                        res.set({
                            'Cache-Control': 'private, no-cache, no-store, must-revalidate',
                            'Expires': '-1',
                            'Pragma': 'no-cache'
                        });

                    } else if (style.hash === hash) {
                        // same as requested
                        // TODO: set cache headers
                    }

                    res.set({
                        "Last-Modified": style.date,
                        "Content-Type": "text/css"
                    });

                    if (err) {
                        res.send(500);
                    } else {
                        res.send(200, style.css);
                    }

                });

            } else {
                next();
            }

        } else {
            next();
        }

    });

    app.get("/lib/cssrefresh.js", function (req, res) {
        res.sendfile(path.join(__dirname + "/../asset/cssrefresh.js"));
    });

    return function (meta, document, context, callback) {

        var style = meta.style;

        if (!style) {
            callback();
            return;
        }

        // we can handle multiple styles
        if (!(style instanceof Array)) {
            style = [style];
        }

        var styles = [style[0]];
        // remove duplicates
        for (var i = 1; i < style.length; i++) {
            if (styles.indexOf(style[i]) === -1) {
                styles.push(style[i]);
            }
        }

        var usedStyles = {};

        // TODO: use par each, but than we need to make sure the order is still correct
        flow()
            .parEach(styles, function (styleName, cb) {
                mod.getStyle(styleName, null, styleCache, config.baseUri, function(err, style) {

                    usedStyles[styleName] = style;
                    cb(err);

                });
            })
            .seq(function () {

                var head = document.getElementsByTagName("head")[0];

                styles.reverse().forEach(function (styleName) {
                    var styleEntry = usedStyles[styleName],
                        node = document.createElement("link");

                    node.setAttribute("href", "/style/" + styleName + ".css?" + styleEntry.hash);
                    node.setAttribute("rel", "stylesheet");

                    head.insertBefore(node, head.firstChild);
                });

            })
            .seq(function () {
                if (reloadCss) {
                    // embed the reloading script
                    var body = document.body || document.getElementsByTagName("body")[0],
                        node = document.createElement("script");

                    node.setAttribute("src", "/lib/cssrefresh.js");
                    node.setAttribute("async", "async");
                    node.setAttribute("type", "text/javascript");
                    node.textContent = " ";

                    body && body.appendChild(node);
                }
            })
            .exec(function (err) {
                callback(err);
            });

    };
};

mod.getStyle = function (styleName, hash, styleCache, baseUri, callback) {

    var styleEntry = styleCache[styleName + "_" + (hash || "")];
    if (styleEntry) {
        callback && callback(null, styleEntry);
        return;
    }

    flow()
        .seq("info", function (cb) {
            mod.getStylePath(styleName, baseUri, cb);
        })
        .seq("css", function (cb) {
            var style = this.vars.info;
            mod.getCss[style.type](style.path, cb);
        })
        .seq("hash", function () {

            // create a hash of the css
            var hash = crypto.createHash("sha1");
            hash.setEncoding("hex");
            hash.write(this.vars.css);
            hash.end();

            return hash.read();
        })
        .seq("date", function() {
            return moment().format("ddd, DD MMM YYYY HH:mm:ss [GMT]");
        })
        .seq(function() {
            styleCache[styleName + "_" + this.vars.hash] = this.vars;
        })
        .exec(callback);
};

mod.getStylePath = function (style, baseUri, callback) {

    var ret = null;

    flow()
        .seqEach(["scss", "css"], function (extension, cb) {

            var file = path.join(baseUri, extension, style + "." + extension);

            fs.exists(file, function (exists) {
                if (exists) {
                    ret = {
                        type: extension,
                        path: file
                    };
                    cb.end();
                } else {
                    cb();
                }
            });
        })
        .exec(function (err) {
            if (!err && ret) {
                callback(null, ret);
            } else {
                callback(err || new Error("No style found for '" + style + "'"));
            }
        });
};

mod.getCss = {
    scss: function (path, cb) {
        var sass = require("node-sass");
        sass.render({
            file: path,
            success: function (css) {
                cb(null, css);
            },
            error: cb
        })

    },
    css: function (path, cb) {
        fs.readFile(path, "utf8", cb);
    }
};

mod.metaKey = "style";<commit_msg>do not minify js cssrefresh in StyleProcessor<commit_after>var flow = require("flow.js").flow,
    fs = require("fs"),
    path = require("path"),
    crypto = require('crypto'),
    moment = require('moment');

var mod = module.exports = function (app, config) {

    var styleCache = {},
        styleUrlMatcher = /^\/style\/([^/]+).css(?:\?([^=]+))?/,
        reloadCss = (config.environment["reloadCss"] === true);

    app.use(function (req, res, next) {

        if (req.method === "GET" || req.method === "HEAD") {
            var match = styleUrlMatcher.exec(req.url);

            if (match) {
                var styleName = match[1],
                    hash = match[2],
                    cache = styleCache,
                    isDevelop = hash === "develop";

                if (isDevelop) {
                    // do not cache
                    cache = {};
                }

                mod.getStyle(styleName, hash, cache, config.baseUri, function (err, style) {

                    if (isDevelop) {
                        // do not cache
                        res.set({
                            'Cache-Control': 'private, no-cache, no-store, must-revalidate',
                            'Expires': '-1',
                            'Pragma': 'no-cache'
                        });

                    } else if (style.hash === hash) {
                        // same as requested
                        // TODO: set cache headers
                    }

                    res.set({
                        "Last-Modified": style.date,
                        "Content-Type": "text/css"
                    });

                    if (err) {
                        res.send(500);
                    } else {
                        res.send(200, style.css);
                    }

                });

            } else {
                next();
            }

        } else {
            next();
        }

    });

    app.get("/lib/cssrefresh.js", function (req, res) {
        res.noFilter = true;
        res.sendfile(path.join(__dirname + "/../asset/cssrefresh.js"));
    });

    return function (meta, document, context, callback) {

        var style = meta.style;

        if (!style) {
            callback();
            return;
        }

        // we can handle multiple styles
        if (!(style instanceof Array)) {
            style = [style];
        }

        var styles = [style[0]];
        // remove duplicates
        for (var i = 1; i < style.length; i++) {
            if (styles.indexOf(style[i]) === -1) {
                styles.push(style[i]);
            }
        }

        var usedStyles = {};

        // TODO: use par each, but than we need to make sure the order is still correct
        flow()
            .parEach(styles, function (styleName, cb) {
                mod.getStyle(styleName, null, styleCache, config.baseUri, function(err, style) {

                    usedStyles[styleName] = style;
                    cb(err);

                });
            })
            .seq(function () {

                var head = document.getElementsByTagName("head")[0];

                styles.reverse().forEach(function (styleName) {
                    var styleEntry = usedStyles[styleName],
                        node = document.createElement("link");

                    node.setAttribute("href", "/style/" + styleName + ".css?" + styleEntry.hash);
                    node.setAttribute("rel", "stylesheet");

                    head.insertBefore(node, head.firstChild);
                });

            })
            .seq(function () {
                if (reloadCss) {
                    // embed the reloading script
                    var body = document.body || document.getElementsByTagName("body")[0],
                        node = document.createElement("script");

                    node.setAttribute("src", "/lib/cssrefresh.js");
                    node.setAttribute("async", "async");
                    node.setAttribute("type", "text/javascript");
                    node.textContent = " ";

                    body && body.appendChild(node);
                }
            })
            .exec(function (err) {
                callback(err);
            });

    };
};

mod.getStyle = function (styleName, hash, styleCache, baseUri, callback) {

    var styleEntry = styleCache[styleName + "_" + (hash || "")];
    if (styleEntry) {
        callback && callback(null, styleEntry);
        return;
    }

    flow()
        .seq("info", function (cb) {
            mod.getStylePath(styleName, baseUri, cb);
        })
        .seq("css", function (cb) {
            var style = this.vars.info;
            mod.getCss[style.type](style.path, cb);
        })
        .seq("hash", function () {

            // create a hash of the css
            var hash = crypto.createHash("sha1");
            hash.setEncoding("hex");
            hash.write(this.vars.css);
            hash.end();

            return hash.read();
        })
        .seq("date", function() {
            return moment().format("ddd, DD MMM YYYY HH:mm:ss [GMT]");
        })
        .seq(function() {
            styleCache[styleName + "_" + this.vars.hash] = this.vars;
        })
        .exec(callback);
};

mod.getStylePath = function (style, baseUri, callback) {

    var ret = null;

    flow()
        .seqEach(["scss", "css"], function (extension, cb) {

            var file = path.join(baseUri, extension, style + "." + extension);

            fs.exists(file, function (exists) {
                if (exists) {
                    ret = {
                        type: extension,
                        path: file
                    };
                    cb.end();
                } else {
                    cb();
                }
            });
        })
        .exec(function (err) {
            if (!err && ret) {
                callback(null, ret);
            } else {
                callback(err || new Error("No style found for '" + style + "'"));
            }
        });
};

mod.getCss = {
    scss: function (path, cb) {
        var sass = require("node-sass");
        sass.render({
            file: path,
            success: function (css) {
                cb(null, css);
            },
            error: cb
        })

    },
    css: function (path, cb) {
        fs.readFile(path, "utf8", cb);
    }
};

mod.metaKey = "style";<|endoftext|>


<commit_before>{
  "id":15,
  "name":"Apply for a budgeting loan",
  "description": "Lets users apply for a short-term cash loan so that they can afford to buy essential items or services.",
  "theme": "Working Age",
  "location": "Leeds",
  "phase":"beta",
  "sro":"Derek Hobbs",
  "service_man":"Zoe Gould",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "May 2015"}
    ],
    "alpha": [
      {"label":"Completed", "date": "November 2015"}
    ],
    "beta": [
      {"label":"Started", "date": "November 2015"},
      {"label":"Private Beta Predicted", "date": "January 2015"},
      {"label":"Public Beta Predicted", "date": "March 2016"}
    ]
  },
  "priority":"Low"
}
<commit_msg>Update budgeting-loans.js<commit_after>{
  "id":15,
  "name":"Apply for a Budgeting Loan",
  "description": "Lets users apply for a short-term cash loan so that they can afford to buy essential items or services.",
  "theme": "Working Age",
  "location": "Leeds",
  "phase":"beta",
  "sro":"Derek Hobbs",
  "service_man":"Zoe Gould",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "May 2015"}
    ],
    "alpha": [
      {"label":"Completed", "date": "November 2015"}
    ],
    "beta": [
      {"label":"Started", "date": "November 2015"},
      {"label":"Private Beta Predicted", "date": "January 2015"},
      {"label":"Public Beta Predicted", "date": "March 2016"}
    ]
  },
  "priority":"Low"
}
<|endoftext|>


<commit_before>
'use strict'
// Template version: 1.1.3
// see http://vuejs-templates.github.io/webpack for documentation.

const path = require('path')

module.exports = {
  build: {
    env: require('./prod.env'),
    index: path.resolve(__dirname, '../dist/index.html'),
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    productionSourceMap: true,
    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  },
  dev: {
    env: require('./dev.env'),
    port: process.env.PORT || 8080,
    autoOpenBrowser: true,
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false
  }
}
<commit_msg>Added proxyTable to client.<commit_after>
'use strict'
// Template version: 1.1.3
// see http://vuejs-templates.github.io/webpack for documentation.

const path = require('path')

module.exports = {
  build: {
    env: require('./prod.env'),
    index: path.resolve(__dirname, '../dist/index.html'),
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {
      '/api/*': {
        target: 'http://localhost:' + process.env.PORT || '5000',
        changeOrigin: true
      }
    },
    productionSourceMap: true,
    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  },
  dev: {
    env: require('./dev.env'),
    port: process.env.PORT || 8080,
    autoOpenBrowser: true,
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {
      '/api/*': {
        target: 'http://localhost:' + process.env.PORT || '5000',
        changeOrigin: true
      }
    },
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false
  }
}
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var async = require('async');
var Mustache = require('mustache');
var request = require('request');
var MessageBroker = require('./messagebroker');
var Storage = require('./storage');
var fs = require('fs');
var path = require('path');
var Utils = require('./common/utils');
var Model = require('./model');
var log = require('npmlog');
log.debug = log.verbose;

var PUSHNOTIFICATIONS_TYPES = {
  'NewCopayer': {
    filename: 'new_copayer',
  },
  'WalletComplete': {
    filename: 'wallet_complete',
  },
  'NewTxProposal': {
    filename: 'new_tx_proposal',
  },
  'NewOutgoingTx': {
    filename: 'new_outgoing_tx',
  },
  'NewIncomingTx': {
    filename: 'new_incoming_tx',
  },
  'TxProposalFinallyRejected': {
    filename: 'txp_finally_rejected',
  }
};

function PushNotificationsService() {};

PushNotificationsService.prototype.start = function(opts, cb) {
  var self = this;
  opts = opts || {};

  function _readDirectories(basePath, cb) {
    fs.readdir(basePath, function(err, files) {
      if (err) return cb(err);
      async.filter(files, function(file, next) {
        fs.stat(path.join(basePath, file), function(err, stats) {
          return next(!err && stats.isDirectory());
        });
      }, function(dirs) {
        return cb(null, dirs);
      });
    });
  };

  self.templatePath = opts.pushNotificationsOpts.templatePath || templatePathpath.normalize(((__dirname + '/templates')) + '/');
  self.defaultLanguage = opts.pushNotificationsOpts.defaultLanguage || 'en';
  self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc';
  self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || '';
  self.publicTxUrlTemplate = opts.pushNotificationsOpts.publicTxUrlTemplate || {};
  self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl;

  async.parallel([
    function(done) {
      _readDirectories(self.templatePath, function(err, res) {
        self.availableLanguages = res;
        done(err);
      });
    },
    function(done) {
      self.storage = new Storage();
      self.storage.connect(opts.storageOpts, done);
    },
    function(done) {
      self.messageBroker = opts.messageBroker || new MessageBroker(opts.messageBrokerOpts);
      self.messageBroker.onMessage(_.bind(self._sendPushNotifications, self));
      done();
    },
  ], function(err) {
    if (err) {
      log.error(err);
    }
    return cb(err);
  });
};

PushNotificationsService.prototype._sendPushNotifications = function(notification, cb) {
  var self = this;
  cb = cb || function() {};

  var notifType = PUSHNOTIFICATIONS_TYPES[notification.type];
  if (!notifType) return cb();

  self._getRecipientsList(notification, function(err, recipientsList) {
    if (err) return cb(err);

    async.waterfall([
      function(next) {
        self._readAndApplyTemplates(notification, notifType, recipientsList, next);
      },
      function(contents, next) {
        async.map(recipientsList, function(recipient, next) {
          var opts = {};
          var content = contents[recipient.language];
          opts.users = [notification.walletId + '$' + recipient.copayerId];
          opts.android = {
            "data": {
              "title": content.plain.subject,
              "message": content.plain.body
            }
          };
          opts.ios = {
            "alert": content.plain.body,
            "sound": ""
          };
          return next(err, opts);
        }, next);
      },
      function(optsList, next) {
        async.each(optsList,
          function(opts, next) {
            self._makeRequest(opts, next());
          },
          function(err) {
            log.error(err);
            return cb(err);
          }
        );
      },
    ], function(err) {
      if (err) {
        log.error('An error ocurred generating notification', err);
      }
      return cb(err);
    });
  });
};

PushNotificationsService.prototype._getRecipientsList = function(notification, cb) {
  var self = this;

  self.storage.fetchWallet(notification.walletId, function(err, wallet) {
    if (err) return cb(err);

    self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) {
      if (err) log.error(err);
      if (_.isEmpty(preferences)) preferences = [];

      var recipients = _.compact(_.map(preferences, function(p) {

        if (!_.contains(self.availableLanguages, p.language)) {
          if (p.language)
            log.warn('Language for notifications "' + p.language + '" not available.');
          p.language = self.defaultLanguage;
        }

        return {
          id: p.copayerId,
          language: p.language,
          unit: p.unit || self.defaultUnit,
        };
      }));

      var recipientsList = _.reject(self._join(wallet.copayers, recipients), {
        id: notification.creatorId
      });

      return cb(null, recipientsList);
    });
  });
};

PushNotificationsService.prototype._join = function(copayers, recipients) {
  var self = this;
  var recipientsList = _.compact(_.map(copayers, function(c) {

    var structure = {};

    _.forEach(recipients, function(r) {
      if (r.id == c.id) {
        structure.copayerId = r.id;
        structure.language = r.language;
        structure.unit = r.unit || self.defaultUnit;
      }
    });

    if (_.isEmpty(structure)) {
      structure.copayerId = c.id;
      structure.language = self.defaultLanguage;
      structure.unit = self.defaultUnit;
    }

    return structure;
  }));
  return recipientsList;
};

PushNotificationsService.prototype._readAndApplyTemplates = function(notification, notifType, recipientsList, cb) {
  var self = this;

  async.map(recipientsList, function(recipient, next) {
    async.waterfall([
      function(next) {
        self._getDataForTemplate(notification, recipient, next);
      },
      function(data, next) {
        async.map(['plain', 'html'], function(type, next) {
          self._loadTemplate(notifType, recipient, '.' + type, function(err, template) {
            if (err && type == 'html') return next();
            if (err) return next(err);

            self._applyTemplate(template, data, function(err, res) {
              return next(err, [type, res]);
            });
          });
        }, function(err, res) {
          return next(err, _.zipObject(res));
        });
      },
      function(result, next) {
        next(null, result);
      },
    ], function(err, res) {
      next(err, [recipient.language, res]);
    });
  }, function(err, res) {
    return cb(err, _.zipObject(res));
  });
};

PushNotificationsService.prototype._getDataForTemplate = function(notification, recipient, cb) {
  var self = this;
  var UNIT_LABELS = {
    btc: 'BTC',
    bit: 'bits'
  };

  var data = _.cloneDeep(notification.data);
  data.subjectPrefix = _.trim(self.subjectPrefix + ' ');
  if (data.amount) {
    try {
      var unit = recipient.unit.toLowerCase();
      data.amount = Utils.formatAmount(+data.amount, unit) + ' ' + UNIT_LABELS[unit];
    } catch (ex) {
      return cb(new Error('Could not format amount', ex));
    }
  }

  self.storage.fetchWallet(notification.walletId, function(err, wallet) {
    if (err) return cb(err);

    data.walletId = wallet.id;
    data.walletName = wallet.name;
    data.walletM = wallet.m;
    data.walletN = wallet.n;

    var copayer = _.find(wallet.copayers, {
      id: notification.creatorId
    });

    if (copayer) {
      data.copayerId = copayer.id;
      data.copayerName = copayer.name;
    }

    if (notification.type == 'TxProposalFinallyRejected' && data.rejectedBy) {
      var rejectors = _.map(data.rejectedBy, function(copayerId) {
        return _.find(wallet.copayers, {
          id: copayerId
        }).name
      });
      data.rejectorsNames = rejectors.join(', ');
    }

    if (_.contains(['NewIncomingTx', 'NewOutgoingTx'], notification.type) && data.txid) {
      var urlTemplate = self.publicTxUrlTemplate[wallet.network];
      if (urlTemplate) {
        try {
          data.urlForTx = Mustache.render(urlTemplate, data);
        } catch (ex) {
          log.warn('Could not render public url for tx', ex);
        }
      }
    }
    return cb(null, data);
  });
};

PushNotificationsService.prototype._applyTemplate = function(template, data, cb) {
  if (!data) return cb(new Error('Could not apply template to empty data'));

  var error;
  var result = _.mapValues(template, function(t) {
    try {
      return Mustache.render(t, data);
    } catch (e) {
      log.error('Could not apply data to template', e);
      error = e;
    }
  });

  if (error) return cb(error);
  return cb(null, result);
};

PushNotificationsService.prototype._loadTemplate = function(notifType, recipient, extension, cb) {
  var self = this;

  self._readTemplateFile(recipient.language, notifType.filename + extension, function(err, template) {
    if (err) return cb(err);
    return cb(null, self._compileTemplate(template, extension));
  });
};

PushNotificationsService.prototype._readTemplateFile = function(language, filename, cb) {
  var self = this;

  var fullFilename = path.join(self.templatePath, language, filename);
  fs.readFile(fullFilename, 'utf8', function(err, template) {
    if (err) {
      return cb(new Error('Could not read template file ' + fullFilename, err));
    }
    return cb(null, template);
  });
};

PushNotificationsService.prototype._compileTemplate = function(template, extension) {
  var lines = template.split('\n');
  if (extension == '.html') {
    lines.unshift('');
  }
  return {
    subject: lines[0],
    body: _.rest(lines).join('\n'),
  };
};

PushNotificationsService.prototype._makeRequest = function(opts, cb) {
  var self = this;

  request({
    url: self.pushServerUrl,
    method: 'POST',
    json: true,
    body: opts
  }, function(error, response) {
    if (error) log.error(error);
    log.debug('Post status : ', response);
    return;
  });
};

module.exports = PushNotificationsService;
<commit_msg>fix copayer creator id error<commit_after>'use strict';

var _ = require('lodash');
var async = require('async');
var Mustache = require('mustache');
var request = require('request');
var MessageBroker = require('./messagebroker');
var Storage = require('./storage');
var fs = require('fs');
var path = require('path');
var Utils = require('./common/utils');
var Model = require('./model');
var log = require('npmlog');
log.debug = log.verbose;

var PUSHNOTIFICATIONS_TYPES = {
  'NewCopayer': {
    filename: 'new_copayer',
  },
  'WalletComplete': {
    filename: 'wallet_complete',
  },
  'NewTxProposal': {
    filename: 'new_tx_proposal',
  },
  'NewOutgoingTx': {
    filename: 'new_outgoing_tx',
  },
  'NewIncomingTx': {
    filename: 'new_incoming_tx',
  },
  'TxProposalFinallyRejected': {
    filename: 'txp_finally_rejected',
  }
};

function PushNotificationsService() {};

PushNotificationsService.prototype.start = function(opts, cb) {
  var self = this;
  opts = opts || {};

  function _readDirectories(basePath, cb) {
    fs.readdir(basePath, function(err, files) {
      if (err) return cb(err);
      async.filter(files, function(file, next) {
        fs.stat(path.join(basePath, file), function(err, stats) {
          return next(!err && stats.isDirectory());
        });
      }, function(dirs) {
        return cb(null, dirs);
      });
    });
  };

  self.templatePath = opts.pushNotificationsOpts.templatePath || templatePathpath.normalize(((__dirname + '/templates')) + '/');
  self.defaultLanguage = opts.pushNotificationsOpts.defaultLanguage || 'en';
  self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc';
  self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || '';
  self.publicTxUrlTemplate = opts.pushNotificationsOpts.publicTxUrlTemplate || {};
  self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl;

  async.parallel([
    function(done) {
      _readDirectories(self.templatePath, function(err, res) {
        self.availableLanguages = res;
        done(err);
      });
    },
    function(done) {
      self.storage = new Storage();
      self.storage.connect(opts.storageOpts, done);
    },
    function(done) {
      self.messageBroker = opts.messageBroker || new MessageBroker(opts.messageBrokerOpts);
      self.messageBroker.onMessage(_.bind(self._sendPushNotifications, self));
      done();
    },
  ], function(err) {
    if (err) {
      log.error(err);
    }
    return cb(err);
  });
};

PushNotificationsService.prototype._sendPushNotifications = function(notification, cb) {
  var self = this;
  cb = cb || function() {};

  var notifType = PUSHNOTIFICATIONS_TYPES[notification.type];
  if (!notifType) return cb();

  console.log(notification);

  self._getRecipientsList(notification, function(err, recipientsList) {
    if (err) return cb(err);

    async.waterfall([
      function(next) {
        self._readAndApplyTemplates(notification, notifType, recipientsList, next);
      },
      function(contents, next) {
        async.map(recipientsList, function(recipient, next) {
          var opts = {};
          var content = contents[recipient.language];
          opts.users = [notification.walletId + '$' + recipient.copayerId];
          opts.android = {
            "data": {
              "title": content.plain.subject,
              "message": content.plain.body
            }
          };
          opts.ios = {
            "alert": content.plain.body,
            "sound": ""
          };
          return next(err, opts);
        }, next);
      },
      function(optsList, next) {
        async.each(optsList,
          function(opts, next) {
            self._makeRequest(opts, next());
          },
          function(err) {
            log.error(err);
            return cb(err);
          }
        );
      },
    ], function(err) {
      if (err) {
        log.error('An error ocurred generating notification', err);
      }
      return cb(err);
    });
  });
};

PushNotificationsService.prototype._getRecipientsList = function(notification, cb) {
  var self = this;

  self.storage.fetchWallet(notification.walletId, function(err, wallet) {
    if (err) return cb(err);

    self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) {
      if (err) log.error(err);
      if (_.isEmpty(preferences)) preferences = [];

      var recipients = _.compact(_.map(preferences, function(p) {

        if (!_.contains(self.availableLanguages, p.language)) {
          if (p.language)
            log.warn('Language for notifications "' + p.language + '" not available.');
          p.language = self.defaultLanguage;
        }

        return {
          id: p.copayerId,
          language: p.language,
          unit: p.unit || self.defaultUnit,
        };
      }));

      var recipientsList = _.reject(self._join(wallet.copayers, recipients), {
        copayerId: notification.creatorId
      });

      return cb(null, recipientsList);
    });
  });
};

PushNotificationsService.prototype._join = function(copayers, recipients) {
  var self = this;
  var recipientsList = _.compact(_.map(copayers, function(c) {

    var structure = {};

    _.forEach(recipients, function(r) {
      if (r.id == c.id) {
        structure.copayerId = r.id;
        structure.language = r.language;
        structure.unit = r.unit || self.defaultUnit;
      }
    });

    if (_.isEmpty(structure)) {
      structure.copayerId = c.id;
      structure.language = self.defaultLanguage;
      structure.unit = self.defaultUnit;
    }

    return structure;
  }));
  return recipientsList;
};

PushNotificationsService.prototype._readAndApplyTemplates = function(notification, notifType, recipientsList, cb) {
  var self = this;

  async.map(recipientsList, function(recipient, next) {
    async.waterfall([
      function(next) {
        self._getDataForTemplate(notification, recipient, next);
      },
      function(data, next) {
        async.map(['plain', 'html'], function(type, next) {
          self._loadTemplate(notifType, recipient, '.' + type, function(err, template) {
            if (err && type == 'html') return next();
            if (err) return next(err);

            self._applyTemplate(template, data, function(err, res) {
              return next(err, [type, res]);
            });
          });
        }, function(err, res) {
          return next(err, _.zipObject(res));
        });
      },
      function(result, next) {
        next(null, result);
      },
    ], function(err, res) {
      next(err, [recipient.language, res]);
    });
  }, function(err, res) {
    return cb(err, _.zipObject(res));
  });
};

PushNotificationsService.prototype._getDataForTemplate = function(notification, recipient, cb) {
  var self = this;
  var UNIT_LABELS = {
    btc: 'BTC',
    bit: 'bits'
  };

  var data = _.cloneDeep(notification.data);
  data.subjectPrefix = _.trim(self.subjectPrefix + ' ');
  if (data.amount) {
    try {
      var unit = recipient.unit.toLowerCase();
      data.amount = Utils.formatAmount(+data.amount, unit) + ' ' + UNIT_LABELS[unit];
    } catch (ex) {
      return cb(new Error('Could not format amount', ex));
    }
  }

  self.storage.fetchWallet(notification.walletId, function(err, wallet) {
    if (err) return cb(err);

    data.walletId = wallet.id;
    data.walletName = wallet.name;
    data.walletM = wallet.m;
    data.walletN = wallet.n;

    var copayer = _.find(wallet.copayers, {
      id: notification.creatorId
    });

    if (copayer) {
      data.copayerId = copayer.id;
      data.copayerName = copayer.name;
    }

    if (notification.type == 'TxProposalFinallyRejected' && data.rejectedBy) {
      var rejectors = _.map(data.rejectedBy, function(copayerId) {
        return _.find(wallet.copayers, {
          id: copayerId
        }).name
      });
      data.rejectorsNames = rejectors.join(', ');
    }

    if (_.contains(['NewIncomingTx', 'NewOutgoingTx'], notification.type) && data.txid) {
      var urlTemplate = self.publicTxUrlTemplate[wallet.network];
      if (urlTemplate) {
        try {
          data.urlForTx = Mustache.render(urlTemplate, data);
        } catch (ex) {
          log.warn('Could not render public url for tx', ex);
        }
      }
    }
    return cb(null, data);
  });
};

PushNotificationsService.prototype._applyTemplate = function(template, data, cb) {
  if (!data) return cb(new Error('Could not apply template to empty data'));

  var error;
  var result = _.mapValues(template, function(t) {
    try {
      return Mustache.render(t, data);
    } catch (e) {
      log.error('Could not apply data to template', e);
      error = e;
    }
  });

  if (error) return cb(error);
  return cb(null, result);
};

PushNotificationsService.prototype._loadTemplate = function(notifType, recipient, extension, cb) {
  var self = this;

  self._readTemplateFile(recipient.language, notifType.filename + extension, function(err, template) {
    if (err) return cb(err);
    return cb(null, self._compileTemplate(template, extension));
  });
};

PushNotificationsService.prototype._readTemplateFile = function(language, filename, cb) {
  var self = this;

  var fullFilename = path.join(self.templatePath, language, filename);
  fs.readFile(fullFilename, 'utf8', function(err, template) {
    if (err) {
      return cb(new Error('Could not read template file ' + fullFilename, err));
    }
    return cb(null, template);
  });
};

PushNotificationsService.prototype._compileTemplate = function(template, extension) {
  var lines = template.split('\n');
  if (extension == '.html') {
    lines.unshift('');
  }
  return {
    subject: lines[0],
    body: _.rest(lines).join('\n'),
  };
};

PushNotificationsService.prototype._makeRequest = function(opts, cb) {
  var self = this;

  request({
    url: self.pushServerUrl,
    method: 'POST',
    json: true,
    body: opts
  }, function(error, response) {
    if (error) log.error(error);
    log.debug('Post status : ', response);
    return;
  });
};

module.exports = PushNotificationsService;
<|endoftext|>