<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
 *
 */

/**
 *  DocumentHolder.js
 *  Document Editor
 *
 *  Created by Alexander Yuzhin on 11/8/16
 *  Copyright (c) 2018 Ascensio System SIA. All rights reserved.
 *
 */

define([
    'core',
    'jquery',
    'underscore',
    'backbone',
    'documenteditor/mobile/app/view/DocumentHolder',
    'common/main/lib/collection/Users'
], function (core, $, _, Backbone) {
    'use strict';

    DE.Controllers.DocumentHolder = Backbone.Controller.extend(_.extend((function() {
        // private
        var _stack,
            _view,
            _fastCoAuthTips = [],
            _actionSheets = [],
            _isEdit = false,
            _canAcceptChanges = false,
            _inRevisionChange = false,
            _menuPos = [],
            _timer = 0;

        return {
            models: [],
            collections: [
                'Common.Collections.Users'
            ],
            views: [
                'DocumentHolder'
            ],

            initialize: function() {
                this.addListeners({
                    'DocumentHolder': {
                        'contextmenu:click' : this.onContextMenuClick
                    }
                });
            },

            setApi: function(api) {
                var me = this;

                me.api = api;

                me.api.asc_registerCallback('asc_onShowPopMenu',            _.bind(me.onApiShowPopMenu, me));
                me.api.asc_registerCallback('asc_onHidePopMenu',            _.bind(me.onApiHidePopMenu, me));
                me.api.asc_registerCallback('asc_onShowForeignCursorLabel', _.bind(me.onApiShowForeignCursorLabel, me));
                me.api.asc_registerCallback('asc_onHideForeignCursorLabel', _.bind(me.onApiHideForeignCursorLabel, me));
                me.api.asc_registerCallback('asc_onAuthParticipantsChanged',_.bind(me.onApiUsersChanged, me));
                me.api.asc_registerCallback('asc_onConnectionStateChanged', _.bind(me.onApiUserConnection, me));
                me.api.asc_registerCallback('asc_onDocumentContentReady',   _.bind(me.onApiDocumentContentReady, me));
                Common.NotificationCenter.on('api:disconnect',              _.bind(me.onCoAuthoringDisconnect, me));
                me.api.asc_registerCallback('asc_onCoAuthoringDisconnect',  _.bind(me.onCoAuthoringDisconnect,me));
                me.api.asc_registerCallback('asc_onShowRevisionsChange',    _.bind(me.onApiShowChange, me));
                me.api.asc_coAuthoringGetUsers();
            },

            setMode: function (mode) {
                _isEdit = mode.isEdit;
                _canAcceptChanges = mode.canReview && !mode.isReviewOnly;
            },

            // When our application is ready, lets get started
            onLaunch: function() {
                var me = this;

                _view = me.createView('DocumentHolder').render();

                $$(window).on('resize', _.bind(me.onEditorResize, me));
            },

            // Handlers

            onContextMenuClick: function (view, eventName) {
                var me = this;

                if ('cut' == eventName) {
                    me.api.Cut();
                } else if ('copy' == eventName) {
                    me.api.Copy();
                } else if ('paste' == eventName) {
                    me.api.Paste();
                } else if ('delete' == eventName) {
                    me.api.asc_Remove();
                } else if ('edit' == eventName) {
                    _view.hideMenu();

                    DE.getController('EditContainer').showModal();
                } else if ('addlink' == eventName) {
                    _view.hideMenu();

                    DE.getController('AddContainer').showModal();
                    DE.getController('AddOther').getView('AddOther').showLink(false);
                } else if ('openlink' == eventName) {
                    _.some(_stack, function (item) {
                        if (item.get_ObjectType() == Asc.c_oAscTypeSelectElement.Hyperlink) {
                            me._openLink(item.get_ObjectValue().get_Value());
                            return true;
                        }
                    });
                } else if ('accept' == eventName) {
                    me.api.asc_GetNextRevisionsChange();
                    me.api.asc_AcceptChanges();
                } else if ('acceptall' == eventName) {
                    me.api.asc_AcceptAllChanges();
                } else if ('reject' == eventName) {
                    me.api.asc_GetNextRevisionsChange();
                    me.api.asc_RejectChanges();
                } else if ('rejectall' == eventName) {
                    me.api.asc_RejectAllChanges();
                } else if ('review' == eventName) {
                    if (Common.SharedSettings.get('phone')) {
                        _actionSheets = me._initReviewMenu();
                        me.onContextMenuClick(view, 'showActionSheet');
                    } else {
                        _.delay(function () {
                            _view.showMenu(me._initReviewMenu(), _menuPos[0] || 0, _menuPos[1] || 0);
                            _timer = (new Date).getTime();
                        }, 100);
                    }
                } else if ('showActionSheet' == eventName && _actionSheets.length > 0) {
                    _.delay(function () {
                        _.each(_actionSheets, function (action) {
                            action.text = action.caption;
                            action.onClick = function () {
                                me.onContextMenuClick(null, action.event)
                            }
                        });

                        uiApp.actions([_actionSheets, [
                            {
                                text: me.sheetCancel,
                                bold: true
                            }
                        ]]);
                    }, 100);
                }

                _view.hideMenu();
            },

            // API Handlers

            onEditorResize: function(cmp) {
                // Hide context menu
            },

            onApiShowPopMenu: function(posX, posY) {
                if ($('.popover.settings, .popup.settings, .picker-modal.settings, .modal.modal-in, .actions-modal').length > 0) {
                    return;
                }
                var now = (new Date).getTime();
                if (now - _timer < 1000) return;
                _timer = 0;

                _menuPos = [posX, posY];

                var me = this,
                    items;

                _stack = me.api.getSelectedElements();
                items = me._initMenu(_stack);

                _view.showMenu(items, posX, posY);
            },

            onApiHidePopMenu: function() {
                _view && _view.hideMenu();
            },

            onApiShowForeignCursorLabel: function(userId, X, Y, color) {
                var me = this,
                    tipHeight = 20;

                var getUserName = function(id) {
                    var usersStore = DE.getCollection('Common.Collections.Users');

                    if (usersStore){
                        var rec = usersStore.findUser(id);
                        if (rec)
                            return rec.get('username');
                    }
                    return me.textGuest;
                };

                /** coauthoring begin **/
                var src = _.find(_fastCoAuthTips, function(tip){ return tip.attr('userid') == userId; });

                if (!src) {
                    src = $(document.createElement('div'));
                    src.addClass('username-tip');
                    src.attr('userid', userId);
                    src.css({
                        height: tipHeight + 'px',
                        position: 'absolute',
                        zIndex: '900',
                        display: 'none',
                        'pointer-events': 'none',
                        'background-color': '#' + Common.Utils.ThemeColor.getHexColor(color.get_r(), color.get_g(), color.get_b())});
                    src.text(getUserName(userId));
                    $('#id_main_view').append(src);

                    _fastCoAuthTips.push(src);

                    src.fadeIn(150);
                }
                src.css({
                    top: (Y - tipHeight) + 'px',
                    left: X + 'px'});
                /** coauthoring end **/
            },

            onApiHideForeignCursorLabel: function(userId) {
                /** coauthoring begin **/
                for (var i=0; i<_fastCoAuthTips.length; i++) {
                    if (_fastCoAuthTips[i].attr('userid') == userId) {
                        var src = _fastCoAuthTips[i];
                        _fastCoAuthTips[i].fadeOut(150, function(){src.remove()});
                        _fastCoAuthTips.splice(i, 1);
                        break;
                    }
                }
                /** coauthoring end **/
            },

            onApiUsersChanged: function(users){
                var usersStore = this.getApplication().getCollection('Common.Collections.Users');

                if (usersStore) {
                    var arrUsers = [], name, user;

                    for (name in users) {
                        if (undefined !== name) {
                            user = users[name];
                            if (user) {
                                arrUsers.push(new Common.Models.User({
                                    id          : user.asc_getId(),
                                    username    : user.asc_getUserName(),
                                    online      : true,
                                    color       : user.asc_getColor(),
                                    view        : user.asc_getView()
                                }));
                            }
                        }
                    }

                    usersStore[usersStore.size() > 0 ? 'add' : 'reset'](arrUsers);
                }
            },

            onApiUserConnection: function(change){
                var usersStore = this.getApplication().getCollection('Common.Collections.Users');

                if (usersStore){
                    var user = usersStore.findUser(change.asc_getId());
                    if (!user) {
                        usersStore.add(new Common.Models.User({
                            id          : change.asc_getId(),
                            username    : change.asc_getUserName(),
                            online      : change.asc_getState(),
                            color       : change.asc_getColor(),
                            view        : change.asc_getView()
                        }));
                    } else {
                        user.set({online: change.asc_getState()});
                    }
                }
            },

            onApiDocumentContentReady: function () {
                _view = this.createView('DocumentHolder').render();
            },

            onApiShowChange: function(sdkchange) {
                _inRevisionChange = sdkchange && sdkchange.length>0;
            },

            // Internal

            _openLink: function(url) {
                if (this.api.asc_getUrlType(url) > 0) {
                    var newDocumentPage = window.open(url, '_blank');

                    if (newDocumentPage) {
                        newDocumentPage.focus();
                    }
                }
            },

            _initMenu: function (stack) {
                var me = this,
                    menuItems = [],
                    canCopy = me.api.can_CopyCut();

                _actionSheets = [];

                if (canCopy) {
                    menuItems.push({
                        caption: me.menuCopy,
                        event: 'copy'
                    });
                }

                var isText = false,
                    isTable = false,
                    isImage = false,
                    isChart = false,
                    isShape = false,
                    isLink = false;

                _.each(stack, function (item) {
                    var objectType = item.get_ObjectType(),
                        objectValue = item.get_ObjectValue();

                    if (objectType == Asc.c_oAscTypeSelectElement.Text) {
                        isText = true;
                    } else if (objectType == Asc.c_oAscTypeSelectElement.Image) {
                        if (objectValue && objectValue.get_ChartProperties()) {
                            isChart = true;
                        } else if (objectType && objectValue.get_ShapeProperties()) {
                            isShape = true;
                        } else {
                            isImage = true;
                        }
                    } else if (objectType == Asc.c_oAscTypeSelectElement.Table) {
                        isTable = true;
                    } else if (objectType == Asc.c_oAscTypeSelectElement.Hyperlink) {
                        isLink = true;
                    }
                });

                if (stack.length > 0) {
                    var topObject = _.find(stack.reverse(), function(obj){ return obj.get_ObjectType() != Asc.c_oAscTypeSelectElement.SpellCheck; }),
                        topObjectValue = topObject.get_ObjectValue(),
                        objectLocked = _.isFunction(topObjectValue.get_Locked) ? topObjectValue.get_Locked() : false;

                    var swapItems = function(items, indexBefore, indexAfter) {
                        items[indexAfter] = items.splice(indexBefore, 1, items[indexAfter])[0];
                    };

                    if (!objectLocked && _isEdit && !me.isDisconnected) {
                        if (canCopy) {
                            menuItems.push({
                                caption: me.menuCut,
                                event: 'cut'
                            });

                            // Swap 'Copy' and 'Cut'
                            swapItems(menuItems, 0, 1);
                        }

                        menuItems.push({
                            caption: me.menuPaste,
                            event: 'paste'
                        });

                        menuItems.push({
                            caption: me.menuDelete,
                            event: 'delete'
                        });

                        menuItems.push({
                            caption: me.menuEdit,
                            event: 'edit'
                        });

                        if (!_.isEmpty(me.api.can_AddHyperlink())) {
                            menuItems.push({
                                caption: me.menuAddLink,
                                event: 'addlink'
                            });
                        }

                        if (_canAcceptChanges && _inRevisionChange) {
                            menuItems.push({
                                caption: me.menuReview,
                                event: 'review'
                            });
                        }
                    }
                }

                if (isLink) {
                    menuItems.push({
                        caption: me.menuOpenLink,
                        event: 'openlink'
                    });
                }

                if (Common.SharedSettings.get('phone') && menuItems.length > 3) {
                    _actionSheets = menuItems.slice(3);

                    menuItems = menuItems.slice(0, 3);
                    menuItems.push({
                        caption: me.menuMore,
                        event: 'showActionSheet'
                    });
                }

                return menuItems;
            },

            _initReviewMenu: function (stack) {
                var me = this,
                    menuItems = [];

                menuItems.push({
                    caption: me.menuAccept,
                    event: 'accept'
                });

                menuItems.push({
                    caption: me.menuReject,
                    event: 'reject'
                });

                menuItems.push({
                    caption: me.menuAcceptAll,
                    event: 'acceptall'
                });

                menuItems.push({
                    caption: me.menuRejectAll,
                    event: 'rejectall'
                });

                return menuItems;
            },

            onCoAuthoringDisconnect: function() {
                this.isDisconnected = true;
            },

            textGuest: 'Guest',
            menuCut: 'Cut',
            menuCopy: 'Copy',
            menuPaste: 'Paste',
            menuEdit: 'Edit',
            menuDelete: 'Delete',
            menuAddLink: 'Add Link',
            menuOpenLink: 'Open Link',
            menuMore: 'More',
            sheetCancel: 'Cancel',
            menuReview: 'Review',
            menuAccept: 'Accept',
            menuAcceptAll: 'Accept All',
            menuReject: 'Reject',
            menuRejectAll: 'Reject All'
        }
    })(), DE.Controllers.DocumentHolder || {}))
});<commit_msg>[DE mobile] Sailfish: show review menu<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
 *
 */

/**
 *  DocumentHolder.js
 *  Document Editor
 *
 *  Created by Alexander Yuzhin on 11/8/16
 *  Copyright (c) 2018 Ascensio System SIA. All rights reserved.
 *
 */

define([
    'core',
    'jquery',
    'underscore',
    'backbone',
    'documenteditor/mobile/app/view/DocumentHolder',
    'common/main/lib/collection/Users'
], function (core, $, _, Backbone) {
    'use strict';

    DE.Controllers.DocumentHolder = Backbone.Controller.extend(_.extend((function() {
        // private
        var _stack,
            _view,
            _fastCoAuthTips = [],
            _actionSheets = [],
            _isEdit = false,
            _canAcceptChanges = false,
            _inRevisionChange = false,
            _menuPos = [],
            _timer = 0;

        return {
            models: [],
            collections: [
                'Common.Collections.Users'
            ],
            views: [
                'DocumentHolder'
            ],

            initialize: function() {
                this.addListeners({
                    'DocumentHolder': {
                        'contextmenu:click' : this.onContextMenuClick
                    }
                });
            },

            setApi: function(api) {
                var me = this;

                me.api = api;

                me.api.asc_registerCallback('asc_onShowPopMenu',            _.bind(me.onApiShowPopMenu, me));
                me.api.asc_registerCallback('asc_onHidePopMenu',            _.bind(me.onApiHidePopMenu, me));
                me.api.asc_registerCallback('asc_onShowForeignCursorLabel', _.bind(me.onApiShowForeignCursorLabel, me));
                me.api.asc_registerCallback('asc_onHideForeignCursorLabel', _.bind(me.onApiHideForeignCursorLabel, me));
                me.api.asc_registerCallback('asc_onAuthParticipantsChanged',_.bind(me.onApiUsersChanged, me));
                me.api.asc_registerCallback('asc_onConnectionStateChanged', _.bind(me.onApiUserConnection, me));
                me.api.asc_registerCallback('asc_onDocumentContentReady',   _.bind(me.onApiDocumentContentReady, me));
                Common.NotificationCenter.on('api:disconnect',              _.bind(me.onCoAuthoringDisconnect, me));
                me.api.asc_registerCallback('asc_onCoAuthoringDisconnect',  _.bind(me.onCoAuthoringDisconnect,me));
                me.api.asc_registerCallback('asc_onShowRevisionsChange',    _.bind(me.onApiShowChange, me));
                me.api.asc_coAuthoringGetUsers();
            },

            setMode: function (mode) {
                _isEdit = mode.isEdit;
                _canAcceptChanges = mode.canReview && !mode.isReviewOnly;
            },

            // When our application is ready, lets get started
            onLaunch: function() {
                var me = this;

                _view = me.createView('DocumentHolder').render();

                $$(window).on('resize', _.bind(me.onEditorResize, me));
            },

            // Handlers

            onContextMenuClick: function (view, eventName) {
                var me = this;

                if ('cut' == eventName) {
                    me.api.Cut();
                } else if ('copy' == eventName) {
                    me.api.Copy();
                } else if ('paste' == eventName) {
                    me.api.Paste();
                } else if ('delete' == eventName) {
                    me.api.asc_Remove();
                } else if ('edit' == eventName) {
                    _view.hideMenu();

                    DE.getController('EditContainer').showModal();
                } else if ('addlink' == eventName) {
                    _view.hideMenu();

                    DE.getController('AddContainer').showModal();
                    DE.getController('AddOther').getView('AddOther').showLink(false);
                } else if ('openlink' == eventName) {
                    _.some(_stack, function (item) {
                        if (item.get_ObjectType() == Asc.c_oAscTypeSelectElement.Hyperlink) {
                            me._openLink(item.get_ObjectValue().get_Value());
                            return true;
                        }
                    });
                } else if ('accept' == eventName) {
                    me.api.asc_GetNextRevisionsChange();
                    me.api.asc_AcceptChanges();
                } else if ('acceptall' == eventName) {
                    me.api.asc_AcceptAllChanges();
                } else if ('reject' == eventName) {
                    me.api.asc_GetNextRevisionsChange();
                    me.api.asc_RejectChanges();
                } else if ('rejectall' == eventName) {
                    me.api.asc_RejectAllChanges();
                } else if ('review' == eventName) {
                    if (Common.SharedSettings.get('phone')) {
                        _actionSheets = me._initReviewMenu();
                        me.onContextMenuClick(view, 'showActionSheet');
                    } else {
                        _.delay(function () {
                            _view.showMenu(me._initReviewMenu(), _menuPos[0] || 0, _menuPos[1] || 0);
                            _timer = (new Date).getTime();
                        }, 100);
                    }
                } else if ('showActionSheet' == eventName && _actionSheets.length > 0) {
                    _.delay(function () {
                        _.each(_actionSheets, function (action) {
                            action.text = action.caption;
                            action.onClick = function () {
                                me.onContextMenuClick(null, action.event)
                            }
                        });

                        uiApp.actions([_actionSheets, [
                            {
                                text: me.sheetCancel,
                                bold: true
                            }
                        ]]);
                    }, 100);
                }

                _view.hideMenu();
            },

            // API Handlers

            onEditorResize: function(cmp) {
                // Hide context menu
            },

            onApiShowPopMenu: function(posX, posY) {
                if ($('.popover.settings, .popup.settings, .picker-modal.settings, .modal.modal-in, .actions-modal').length > 0) {
                    return;
                }
                var now = (new Date).getTime();
                if (now - _timer < 1000) return;
                _timer = 0;

                _menuPos = [posX, posY];

                var me = this,
                    items;

                _stack = me.api.getSelectedElements();
                items = me._initMenu(_stack);

                _view.showMenu(items, posX, posY);
            },

            onApiHidePopMenu: function() {
                var now = (new Date).getTime();
                if (now - _timer < 1000) return;
                _view && _view.hideMenu();
            },

            onApiShowForeignCursorLabel: function(userId, X, Y, color) {
                var me = this,
                    tipHeight = 20;

                var getUserName = function(id) {
                    var usersStore = DE.getCollection('Common.Collections.Users');

                    if (usersStore){
                        var rec = usersStore.findUser(id);
                        if (rec)
                            return rec.get('username');
                    }
                    return me.textGuest;
                };

                /** coauthoring begin **/
                var src = _.find(_fastCoAuthTips, function(tip){ return tip.attr('userid') == userId; });

                if (!src) {
                    src = $(document.createElement('div'));
                    src.addClass('username-tip');
                    src.attr('userid', userId);
                    src.css({
                        height: tipHeight + 'px',
                        position: 'absolute',
                        zIndex: '900',
                        display: 'none',
                        'pointer-events': 'none',
                        'background-color': '#' + Common.Utils.ThemeColor.getHexColor(color.get_r(), color.get_g(), color.get_b())});
                    src.text(getUserName(userId));
                    $('#id_main_view').append(src);

                    _fastCoAuthTips.push(src);

                    src.fadeIn(150);
                }
                src.css({
                    top: (Y - tipHeight) + 'px',
                    left: X + 'px'});
                /** coauthoring end **/
            },

            onApiHideForeignCursorLabel: function(userId) {
                /** coauthoring begin **/
                for (var i=0; i<_fastCoAuthTips.length; i++) {
                    if (_fastCoAuthTips[i].attr('userid') == userId) {
                        var src = _fastCoAuthTips[i];
                        _fastCoAuthTips[i].fadeOut(150, function(){src.remove()});
                        _fastCoAuthTips.splice(i, 1);
                        break;
                    }
                }
                /** coauthoring end **/
            },

            onApiUsersChanged: function(users){
                var usersStore = this.getApplication().getCollection('Common.Collections.Users');

                if (usersStore) {
                    var arrUsers = [], name, user;

                    for (name in users) {
                        if (undefined !== name) {
                            user = users[name];
                            if (user) {
                                arrUsers.push(new Common.Models.User({
                                    id          : user.asc_getId(),
                                    username    : user.asc_getUserName(),
                                    online      : true,
                                    color       : user.asc_getColor(),
                                    view        : user.asc_getView()
                                }));
                            }
                        }
                    }

                    usersStore[usersStore.size() > 0 ? 'add' : 'reset'](arrUsers);
                }
            },

            onApiUserConnection: function(change){
                var usersStore = this.getApplication().getCollection('Common.Collections.Users');

                if (usersStore){
                    var user = usersStore.findUser(change.asc_getId());
                    if (!user) {
                        usersStore.add(new Common.Models.User({
                            id          : change.asc_getId(),
                            username    : change.asc_getUserName(),
                            online      : change.asc_getState(),
                            color       : change.asc_getColor(),
                            view        : change.asc_getView()
                        }));
                    } else {
                        user.set({online: change.asc_getState()});
                    }
                }
            },

            onApiDocumentContentReady: function () {
                _view = this.createView('DocumentHolder').render();
            },

            onApiShowChange: function(sdkchange) {
                _inRevisionChange = sdkchange && sdkchange.length>0;
            },

            // Internal

            _openLink: function(url) {
                if (this.api.asc_getUrlType(url) > 0) {
                    var newDocumentPage = window.open(url, '_blank');

                    if (newDocumentPage) {
                        newDocumentPage.focus();
                    }
                }
            },

            _initMenu: function (stack) {
                var me = this,
                    menuItems = [],
                    canCopy = me.api.can_CopyCut();

                _actionSheets = [];

                if (canCopy) {
                    menuItems.push({
                        caption: me.menuCopy,
                        event: 'copy'
                    });
                }

                var isText = false,
                    isTable = false,
                    isImage = false,
                    isChart = false,
                    isShape = false,
                    isLink = false;

                _.each(stack, function (item) {
                    var objectType = item.get_ObjectType(),
                        objectValue = item.get_ObjectValue();

                    if (objectType == Asc.c_oAscTypeSelectElement.Text) {
                        isText = true;
                    } else if (objectType == Asc.c_oAscTypeSelectElement.Image) {
                        if (objectValue && objectValue.get_ChartProperties()) {
                            isChart = true;
                        } else if (objectType && objectValue.get_ShapeProperties()) {
                            isShape = true;
                        } else {
                            isImage = true;
                        }
                    } else if (objectType == Asc.c_oAscTypeSelectElement.Table) {
                        isTable = true;
                    } else if (objectType == Asc.c_oAscTypeSelectElement.Hyperlink) {
                        isLink = true;
                    }
                });

                if (stack.length > 0) {
                    var topObject = _.find(stack.reverse(), function(obj){ return obj.get_ObjectType() != Asc.c_oAscTypeSelectElement.SpellCheck; }),
                        topObjectValue = topObject.get_ObjectValue(),
                        objectLocked = _.isFunction(topObjectValue.get_Locked) ? topObjectValue.get_Locked() : false;

                    var swapItems = function(items, indexBefore, indexAfter) {
                        items[indexAfter] = items.splice(indexBefore, 1, items[indexAfter])[0];
                    };

                    if (!objectLocked && _isEdit && !me.isDisconnected) {
                        if (canCopy) {
                            menuItems.push({
                                caption: me.menuCut,
                                event: 'cut'
                            });

                            // Swap 'Copy' and 'Cut'
                            swapItems(menuItems, 0, 1);
                        }

                        menuItems.push({
                            caption: me.menuPaste,
                            event: 'paste'
                        });

                        menuItems.push({
                            caption: me.menuDelete,
                            event: 'delete'
                        });

                        menuItems.push({
                            caption: me.menuEdit,
                            event: 'edit'
                        });

                        if (!_.isEmpty(me.api.can_AddHyperlink())) {
                            menuItems.push({
                                caption: me.menuAddLink,
                                event: 'addlink'
                            });
                        }

                        if (_canAcceptChanges && _inRevisionChange) {
                            menuItems.push({
                                caption: me.menuReview,
                                event: 'review'
                            });
                        }
                    }
                }

                if (isLink) {
                    menuItems.push({
                        caption: me.menuOpenLink,
                        event: 'openlink'
                    });
                }

                if (Common.SharedSettings.get('phone') && menuItems.length > 3) {
                    _actionSheets = menuItems.slice(3);

                    menuItems = menuItems.slice(0, 3);
                    menuItems.push({
                        caption: me.menuMore,
                        event: 'showActionSheet'
                    });
                }

                return menuItems;
            },

            _initReviewMenu: function (stack) {
                var me = this,
                    menuItems = [];

                menuItems.push({
                    caption: me.menuAccept,
                    event: 'accept'
                });

                menuItems.push({
                    caption: me.menuReject,
                    event: 'reject'
                });

                menuItems.push({
                    caption: me.menuAcceptAll,
                    event: 'acceptall'
                });

                menuItems.push({
                    caption: me.menuRejectAll,
                    event: 'rejectall'
                });

                return menuItems;
            },

            onCoAuthoringDisconnect: function() {
                this.isDisconnected = true;
            },

            textGuest: 'Guest',
            menuCut: 'Cut',
            menuCopy: 'Copy',
            menuPaste: 'Paste',
            menuEdit: 'Edit',
            menuDelete: 'Delete',
            menuAddLink: 'Add Link',
            menuOpenLink: 'Open Link',
            menuMore: 'More',
            sheetCancel: 'Cancel',
            menuReview: 'Review',
            menuAccept: 'Accept',
            menuAcceptAll: 'Accept All',
            menuReject: 'Reject',
            menuRejectAll: 'Reject All'
        }
    })(), DE.Controllers.DocumentHolder || {}))
});<|endoftext|>


<commit_before>'use strict';

var has = Object.prototype.hasOwnProperty;

//
// Attempt to detect if we have support for setImmediate or process.nextTick so
// we can use it in our setImmediate function.
//
var next = 'object' === typeof process && 'function' === process.nextTick
  ? process.nextTick
  : null;

//
// The process.nexTick doesn't have a way to cancel the scheduled tick so we
// detect it first and know that when next is a function we cannot clear it.
//
if ('function' === typeof setImmediate) next = {
  clearImmediate: clearImmediate,
  setImmediate: setImmediate
};

/**
 * Simple timer management.
 *
 * @constructor
 * @param {Mixed} context Context of the callbacks that we execute.
 * @api public
 */
function Tick(context) {
  if (!(this instanceof Tick)) return new Tick(context);

  this.timers = {};
  this.context = context || this;
}

/**
 * Return a function which will just iterate over all assigned callbacks and
 * optionally clear the timers from memory if needed.
 *
 * @param {String} name Name of the timer we need to execute.
 * @param {Boolean} clear Also clear from memory.
 * @returns {Function}
 * @api private
 */
Tick.prototype.tock = function ticktock(name, clear) {
  var tock = this;

  return function tickedtock() {
    var timer = tock.timers[name]
      , fns;

    if (!timer) return;
    fns = timer.fns.slice();

    if (clear) tock.clear(name);
    for (var i = 0, l = fns.length; i < l; i++) {
      fns[i].call(tock.context);
    }
  };
};

/**
 * Add a new timeout.
 *
 * @param {String} name Name of the timer.
 * @param {Function} fn Completion callback.
 * @param {Mixed} ms Duration of the timer.
 * @returns {Tick}
 * @api public
 */
Tick.prototype.setTimeout = function timeout(name, fn, ms) {
  if (this.timers[name]) {
    this.timers[name].fns.push(fn);
    return this;
  }

  this.timers[name] = {
    timer: setTimeout(this.tock(name, true), Tick.parse(ms)),
    clear: clearTimeout,
    fns: [fn]
  };

  return this;
};

/**
 * Add a new interval.
 *
 * @param {String} name Name of the timer.
 * @param {Function} fn Completion callback.
 * @param {Mixed} ms Interval of the timer.
 * @returns {Tick}
 * @api public
 */
Tick.prototype.setInterval = function interval(name, fn, ms) {
  if (this.timers[name]) {
    this.timers[name].fns.push(fn);
    return this;
  }

  this.timers[name] = {
    timer: setInterval(this.tock(name), Tick.parse(ms)),
    clear: clearInterval,
    fns: [fn]
  };

  return this;
};

/**
 * Add a new setImmediate.
 *
 * @param {String} name Name of the timer.
 * @param {Function} fn Completion callback.
 * @returns {Tick}
 * @api public
 */
Tick.prototype.setImmediate = function immediate(name, fn) {
  if (!next) return this.setTimeout(name, fn, 0);

  if (this.timers[name]) {
    this.timers[name].fns.push(fn);
    return this;
  }

  this.timers[name] = {
    timer: (next.setImmediate || next)(this.tock(name, true)),
    clear: next.clearImmediate,
    fns: [fn]
  };

  return this;
};

/**
 * Check if we have a timer set.
 *
 * @param {String} name
 * @returns {Boolean}
 * @api public
 */
Tick.prototype.active = function active(name) {
  return name in this.timers;
};

/**
 * Properly clean up all timeout references. If no arguments are supplied we
 * will attempt to clear every single timer that is present.
 *
 * @param {Arguments} ..args.. The names of the timeouts we need to clear
 * @returns {Tick}
 * @api public
 */
Tick.prototype.clear = function clear() {
  var args = arguments.length ? arguments : []
    , timer, i, l;

  if (args.length === 1 && 'string' === typeof args[0]) {
    args = args[0].split(/[\,|\s]+/);
  }

  if (!args.length) {
    for (timer in this.timers) {
      if (has.call(this.timers, timer)) args.push(timer);
    }
  }

  for (i = 0, l = args.length; i < l; i++) {
    timer = this.timers[args[i]];

    if (!timer) continue;
    if (timer.clear) timer.clear(timer.timer);

    timer.fns.length = 0;
    delete this.timers[args[i]];
  }

  return this;
};

/**
 * We will no longer use this module, prepare your self for global cleanups.
 *
 * @returns {Boolean}
 * @api public
 */
Tick.prototype.end = Tick.prototype.destroy = function end() {
  if (!this.context) return false;

  this.clear();
  this.context = this.timers = null;

  return true;
};

/**
 * Adjust a timeout or interval to a new duration.
 *
 * @returns {Tick}
 * @api public
 */
Tick.prototype.adjust = function adjust(name, ms) {
  var timer = this.timers[name]
    , interval;

  if (!timer) return this;

  interval = timer.clear === clearInterval;
  timer.clear(timer.timer);
  timer.timer = (interval ? setInterval : setTimeout)(this.tock(name, !interval), Tick.parse(ms));

  return this;
};

/**
 * Parse a time string and return the number value of it.
 *
 * @returns {Number}
 * @api private
 */
Tick.parse = require('millisecond');
//
// Expose the timer factory.
//
module.exports = Tick;
<commit_msg>[fix] Clean up timers and references [fix] minify `this` [fix] Nuke `Tick.parse`<commit_after>'use strict';

var has = Object.prototype.hasOwnProperty
  , ms = require('millisecond');

//
// Attempt to detect if we have support for setImmediate or process.nextTick so
// we can use it in our setImmediate function.
//
var next = 'object' === typeof process && 'function' === process.nextTick
  ? process.nextTick
  : null;

//
// The process.nexTick doesn't have a way to cancel the scheduled tick so we
// detect it first and know that when next is a function we cannot clear it.
//
if ('function' === typeof setImmediate) next = {
  clearImmediate: clearImmediate,
  setImmediate: setImmediate
};

/**
 * Simple timer management.
 *
 * @constructor
 * @param {Mixed} context Context of the callbacks that we execute.
 * @api public
 */
function Tick(context) {
  if (!(this instanceof Tick)) return new Tick(context);

  this.timers = {};
  this.context = context || this;
}

/**
 * Return a function which will just iterate over all assigned callbacks and
 * optionally clear the timers from memory if needed.
 *
 * @param {String} name Name of the timer we need to execute.
 * @param {Boolean} clear Also clear from memory.
 * @returns {Function}
 * @api private
 */
Tick.prototype.tock = function ticktock(name, clear) {
  var tock = this;

  return function tickedtock() {
    if (!(name in tock.timers)) return;

    var timer = tock.timers[name]
      , fns = timer.fns.slice()
      , l = fns.length
      , i = 0;

    if (clear) tock.clear(name);

    for (; i < l; i++) {
      fns[i].call(tock.context);
    }
  };
};

/**
 * Add a new timeout.
 *
 * @param {String} name Name of the timer.
 * @param {Function} fn Completion callback.
 * @param {Mixed} time Duration of the timer.
 * @returns {Tick}
 * @api public
 */
Tick.prototype.setTimeout = function timeout(name, fn, time) {
  var tick = this;

  if (tick.timers[name]) {
    tick.timers[name].fns.push(fn);
    return tick;
  }

  tick.timers[name] = {
    timer: setTimeout(tick.tock(name, true), ms(time)),
    clear: clearTimeout,
    fns: [fn]
  };

  return tick;
};

/**
 * Add a new interval.
 *
 * @param {String} name Name of the timer.
 * @param {Function} fn Completion callback.
 * @param {Mixed} time Interval of the timer.
 * @returns {Tick}
 * @api public
 */
Tick.prototype.setInterval = function interval(name, fn, time) {
  var tick = this;

  if (tick.timers[name]) {
    tick.timers[name].fns.push(fn);
    return tick;
  }

  tick.timers[name] = {
    timer: setInterval(tick.tock(name), ms(time)),
    clear: clearInterval,
    fns: [fn]
  };

  return tick;
};

/**
 * Add a new setImmediate.
 *
 * @param {String} name Name of the timer.
 * @param {Function} fn Completion callback.
 * @returns {Tick}
 * @api public
 */
Tick.prototype.setImmediate = function immediate(name, fn) {
  var tick = this;

  if (!next) return tick.setTimeout(name, fn, 0);

  if (tick.timers[name]) {
    tick.timers[name].fns.push(fn);
    return tick;
  }

  tick.timers[name] = {
    timer: (next.setImmediate || next)(tick.tock(name, true)),
    clear: next.clearImmediate,
    fns: [fn]
  };

  return tick;
};

/**
 * Check if we have a timer set.
 *
 * @param {String} name
 * @returns {Boolean}
 * @api public
 */
Tick.prototype.active = function active(name) {
  return name in this.timers;
};

/**
 * Properly clean up all timeout references. If no arguments are supplied we
 * will attempt to clear every single timer that is present.
 *
 * @param {Arguments} ..args.. The names of the timeouts we need to clear
 * @returns {Tick}
 * @api public
 */
Tick.prototype.clear = function clear() {
  var args = arguments.length ? arguments : []
    , tick = this
    , timer, i, l;

  if (args.length === 1 && 'string' === typeof args[0]) {
    args = args[0].split(/[\,|\s]+/);
  }

  if (!args.length) {
    for (timer in tick.timers) {
      if (has.call(tick.timers, timer)) args.push(timer);
    }
  }

  for (i = 0, l = args.length; i < l; i++) {
    timer = tick.timers[args[i]];

    if (!timer) continue;
    if (timer.clear) timer.clear(timer.timer);

    timer.fns = timer.timer = timer.clear = null;
    delete tick.timers[args[i]];
  }

  return tick;
};

/**
 * We will no longer use this module, prepare your self for global cleanups.
 *
 * @returns {Boolean}
 * @api public
 */
Tick.prototype.end = Tick.prototype.destroy = function end() {
  if (!this.context) return false;

  this.clear();
  this.context = this.timers = null;

  return true;
};

/**
 * Adjust a timeout or interval to a new duration.
 *
 * @returns {Tick}
 * @api public
 */
Tick.prototype.adjust = function adjust(name, time) {
  var interval
    , tick = this
    , timer = tick.timers[name];

  if (!timer) return tick;

  interval = timer.clear === clearInterval;
  timer.clear(timer.timer);
  timer.timer = (interval ? setInterval : setTimeout)(tick.tock(name, !interval), ms(time));

  return tick;
};

//
// Expose the timer factory.
//
module.exports = Tick;
<|endoftext|>


<commit_before>"use strict";

var onetime = require('onetime');

module.exports = function () {

    return (function () {

        var cb,
            tasks = [],
            preparedTasks = [],
            index = 0,
            active = 0,
            failed = false,
            count = 0;

        function AsyncTasks() {

            this.do = function (func, args) {

                if (typeof func !== 'function') {
                    throw new TypeError('First argument must be a function');
                }

                tasks.push({func: func, args: args});
                return this;
            };

            this.wait = function () {

                if (tasks.length > 0 && !tasks[tasks.length - 1].wait) {
                    tasks.push({wait: true});
                }

                return this;
            };

            this.start = function (callback) {

                cb = callback || function () {};

                var self = this,
                    task;

                function done(error) {

                    count += 1;
                    active -= 1;

                    if (failed) {
                        return;
                    }

                    if (error !== undefined && error !== null) {
                        failed = true;
                        cb(error);

                        preparedTasks.forEach(function (task) {
                            clearImmediate(task);
                        });

                        return;
                    }

                    if (count === tasks.length) {
                        cb();
                        return;
                    }

                    if (active === 0) {
                        self.start(cb);
                    }
                }

                if(tasks.length === 0){
                    cb();
                    return;
                }

                while (!failed && index < tasks.length) {

                    task = tasks[index];

                    if (task.wait) {
                        index += 1;
                        count += 1;
                        return;
                    }

                    preparedTasks.push(setImmediate(task.func, task.args, index, onetime(done, true)));

                    index += 1;
                    active += 1;

                }

                return this;
            };
        }

        return new AsyncTasks();

    })();
};<commit_msg>Fixed an issue when start is called while waiting<commit_after>"use strict";

var onetime = require('onetime');

module.exports = function () {

    return (function () {

        var cb,
            tasks = [],
            preparedTasks = [],
            index = 0,
            active = 0,
            failed = false,
            count = 0,
            waiting = false;

        function AsyncTasks() {

            this.do = function (func, args) {

                if (typeof func !== 'function') {
                    throw new TypeError('First argument must be a function');
                }

                tasks.push({func: func, args: args});
                return this;
            };

            this.wait = function () {

                if (tasks.length > 0 && !tasks[tasks.length - 1].wait) {
                    tasks.push({wait: true});
                }

                return this;
            };

            this.start = function (callback) {

                if(waiting){
                    return this;
                }

                cb = callback || function () {};

                var self = this,
                    task;

                function done(error) {

                    count += 1;
                    active -= 1;

                    if (failed) {
                        return;
                    }

                    if (error !== undefined && error !== null) {
                        failed = true;
                        cb(error);

                        preparedTasks.forEach(function (task) {
                            clearImmediate(task);
                        });

                        return;
                    }

                    if (count === tasks.length) {
                        cb();
                        return;
                    }

                    if (active === 0) {
                        waiting = false;
                        self.start(cb);
                    }
                }

                if(tasks.length === 0){
                    cb();
                    return;
                }

                while (!failed && index < tasks.length) {

                    task = tasks[index];

                    if (task.wait) {
                        index += 1;
                        count += 1;
                        waiting = true;
                        return;
                    }

                    preparedTasks.push(setImmediate(task.func, task.args, index, onetime(done, true)));

                    index += 1;
                    active += 1;

                }

                return this;
            };
        }

        return new AsyncTasks();

    })();
};<|endoftext|>


<commit_before>export default {
  lang: 'fa-ir',
  rtl: true,
  label: {
    clear: 'پاک‌سازی',
    ok: 'قبول',
    cancel: 'لغو',
    close: 'بستن',
    set: 'ثبت',
    select: 'انتخاب',
    reset: 'بازنشانی',
    remove: 'حذف',
    update: 'بروزرسانی',
    create: 'ساخت',
    search: 'جستجو',
    filter: 'فیلتر',
    refresh: 'تازه‌سازی'
  },
  date: {
    days: 'یکشنبه/دوشنبه/سه‌شنبه/چهارشنبه/پنجشنبه/جمعه/شنبه'.split('/'),
    daysShort: 'ی/د/س/چ/پ/ج/ش'.split('/'),
    months: 'ژانویه/فوریه/مارس/آپریل/مه/ژوئن/ژولای/آگوست/سپتامبر/اکتبر/نوامبر/دسامبر'.split('/'),
    monthsShort: 'ژانویه/فوریه/مارس/آپریل/مه/ژوئن/ژولای/آگوست/سپتامبر/اکتبر/نوامبر/دسامبر'.split('/'),
    firstDayOfWeek: 5,
    format24h: false
  },
  pullToRefresh: {
    pull: 'برای تازه‌سازی بکشید',
    release: 'برای تازه‌سازی رهاکنید',
    refresh: 'تازه‌سازی ...'
  },
  table: {
    noData: 'تاریخ دردسترس نیست',
    noResults: 'هیچ موردی یافت نشد',
    loading: 'در حال بارگذاری ...',
    selectedRows: function (rows) {
      return rows === 0 ? 'ردیفی انتخاب نشده': rows + ' ردیف انتخاب شده'
    },
    rowsPerPage: 'ردیف بر صفحه:',
    allRows: 'همه',
    pagination: function (start, end, total) {
      return start + '-' + end + ' از ' + total
    },
    columns: 'ستون'
  },
  editor: {
    url: 'آدرس',
    bold: 'کلفت',
    italic: 'کج',
    strikethrough: 'خط‌خورده',
    underline: 'زیرخط',
    unorderedList: 'فهرست غیرترتیبی',
    orderedList: 'فهرست ترتیبی',
    subscript: 'زیرنویس',
    superscript: 'بالانویس',
    hyperlink: 'پیوند',
    toggleFullscreen: 'تغییر حالت تمام صفحه',
    quote: 'نقل قول',
    left: 'چپ تراز',
    center: 'وسط تراز',
    right: 'راست تراز',
    justify: 'هم‌تراز',
    print: 'چاپ',
    outdent: 'کاهش دندانه',
    indent: 'افزایش دندانه',
    removeFormat: 'حذف قالب‌بندی',
    formatting: 'قالب‌بندی',
    fontSize: 'اندازه قلم',
    align: 'تراز کردن',
    hr: 'درج خطکش افقی',
    undo: 'عمل قبلی',
    redo: 'عملی بعدی',
    header1: 'سرفصل 1',
    header2: 'سرفصل 2',
    header3: 'سرفصل 3',
    header4: 'سرفصل 4',
    header5: 'سرفصل 5',
    header6: 'سرفصل 6',
    paragraph: 'پاراگراف',
    code: 'کد',
    size1: 'خیلی کوچک',
    size2: 'کمی کوچک',
    size3: 'معمولی',
    size4: 'متوسط-بزرگ',
    size5: 'بزرگ',
    size6: 'خیلی بزرگ',
    size7: 'بیشترین',
    defaultFont: 'قلم پیش‌فرض'
  },
  tree: {
    noNodes: 'گره‌ای در دسترس نیست',
    noResults: 'گره‌ی منطبقی یافت نشد'
  }
}
<commit_msg>fix: Lint error in fa-ir language pack<commit_after>export default {
  lang: 'fa-ir',
  rtl: true,
  label: {
    clear: 'پاک‌سازی',
    ok: 'قبول',
    cancel: 'لغو',
    close: 'بستن',
    set: 'ثبت',
    select: 'انتخاب',
    reset: 'بازنشانی',
    remove: 'حذف',
    update: 'بروزرسانی',
    create: 'ساخت',
    search: 'جستجو',
    filter: 'فیلتر',
    refresh: 'تازه‌سازی'
  },
  date: {
    days: 'یکشنبه/دوشنبه/سه‌شنبه/چهارشنبه/پنجشنبه/جمعه/شنبه'.split('/'),
    daysShort: 'ی/د/س/چ/پ/ج/ش'.split('/'),
    months: 'ژانویه/فوریه/مارس/آپریل/مه/ژوئن/ژولای/آگوست/سپتامبر/اکتبر/نوامبر/دسامبر'.split('/'),
    monthsShort: 'ژانویه/فوریه/مارس/آپریل/مه/ژوئن/ژولای/آگوست/سپتامبر/اکتبر/نوامبر/دسامبر'.split('/'),
    firstDayOfWeek: 5,
    format24h: false
  },
  pullToRefresh: {
    pull: 'برای تازه‌سازی بکشید',
    release: 'برای تازه‌سازی رهاکنید',
    refresh: 'تازه‌سازی ...'
  },
  table: {
    noData: 'تاریخ دردسترس نیست',
    noResults: 'هیچ موردی یافت نشد',
    loading: 'در حال بارگذاری ...',
    selectedRows: function (rows) {
      return rows === 0 ? 'ردیفی انتخاب نشده' : rows + ' ردیف انتخاب شده'
    },
    rowsPerPage: 'ردیف بر صفحه:',
    allRows: 'همه',
    pagination: function (start, end, total) {
      return start + '-' + end + ' از ' + total
    },
    columns: 'ستون'
  },
  editor: {
    url: 'آدرس',
    bold: 'کلفت',
    italic: 'کج',
    strikethrough: 'خط‌خورده',
    underline: 'زیرخط',
    unorderedList: 'فهرست غیرترتیبی',
    orderedList: 'فهرست ترتیبی',
    subscript: 'زیرنویس',
    superscript: 'بالانویس',
    hyperlink: 'پیوند',
    toggleFullscreen: 'تغییر حالت تمام صفحه',
    quote: 'نقل قول',
    left: 'چپ تراز',
    center: 'وسط تراز',
    right: 'راست تراز',
    justify: 'هم‌تراز',
    print: 'چاپ',
    outdent: 'کاهش دندانه',
    indent: 'افزایش دندانه',
    removeFormat: 'حذف قالب‌بندی',
    formatting: 'قالب‌بندی',
    fontSize: 'اندازه قلم',
    align: 'تراز کردن',
    hr: 'درج خطکش افقی',
    undo: 'عمل قبلی',
    redo: 'عملی بعدی',
    header1: 'سرفصل 1',
    header2: 'سرفصل 2',
    header3: 'سرفصل 3',
    header4: 'سرفصل 4',
    header5: 'سرفصل 5',
    header6: 'سرفصل 6',
    paragraph: 'پاراگراف',
    code: 'کد',
    size1: 'خیلی کوچک',
    size2: 'کمی کوچک',
    size3: 'معمولی',
    size4: 'متوسط-بزرگ',
    size5: 'بزرگ',
    size6: 'خیلی بزرگ',
    size7: 'بیشترین',
    defaultFont: 'قلم پیش‌فرض'
  },
  tree: {
    noNodes: 'گره‌ای در دسترس نیست',
    noResults: 'گره‌ی منطبقی یافت نشد'
  }
}
<|endoftext|>


<commit_before>'use strict';

var configPageHtml = require('./tmp/config-page.html');
var toSource = require('tosource');
var standardComponents = require('./src/scripts/components');
var deepcopy = require('deepcopy/build/deepcopy.min');
var version = require('./package.json').version;
var messageKeys = require('message_keys');

/**
 * @param {Array} config - the Clay config
 * @param {function} [customFn] - Custom code to run from the config page. Will run
 *   with the ClayConfig instance as context
 * @param {Object} [options] - Additional options to pass to Clay
 * @param {boolean} [options.autoHandleEvents=true] - If false, Clay will not
 *   automatically handle the 'showConfiguration' and 'webviewclosed' events
 * @param {*} [options.userData={}] - Arbitrary data to pass to the config page. Will
 *   be available as `clayConfig.meta.userData`
 * @constructor
 */
function Clay(config, customFn, options) {
  var self = this;

  if (!Array.isArray(config)) {
    throw new Error('config must be an Array');
  }

  if (customFn && typeof customFn !== 'function') {
    throw new Error('customFn must be a function or "null"');
  }

  options = options || {};

  self.config = deepcopy(config);
  self.customFn = customFn || function() {};
  self.components = {};
  self.meta = {
    activeWatchInfo: null,
    accountToken: '',
    watchToken: '',
    userData: {}
  };
  self.version = version;

  /**
   * Populate the meta with data from the Pebble object. Make sure to run this inside
   * either the "showConfiguration" or "ready" event handler
   * @return {void}
   */
  function _populateMeta() {
    self.meta = {
      activeWatchInfo: Pebble.getActiveWatchInfo && Pebble.getActiveWatchInfo(),
      accountToken: Pebble.getAccountToken(),
      watchToken: Pebble.getWatchToken(),
      userData: deepcopy(options.userData || {})
    };
  }

  // Let Clay handle all the magic
  if (options.autoHandleEvents !== false && typeof Pebble !== 'undefined') {

    Pebble.addEventListener('showConfiguration', function() {
      _populateMeta();
      Pebble.openURL(self.generateUrl());
    });

    Pebble.addEventListener('webviewclosed', function(e) {

      if (!e || !e.response) { return; }

      // Send settings to Pebble watchapp
      Pebble.sendAppMessage(self.getSettings(e.response), function() {
        console.log('Sent config data to Pebble');
      }, function(error) {
        console.log('Failed to send config data!');
        console.log(JSON.stringify(error));
      });
    });
  } else if (typeof Pebble !== 'undefined') {
    Pebble.addEventListener('ready', function() {
      _populateMeta();
    });
  }

  /**
   * If this function returns true then the callback will be executed
   * @callback _scanConfig_testFn
   * @param {Clay~ConfigItem} item
   */

  /**
   * @callback _scanConfig_callback
   * @param {Clay~ConfigItem} item
   */

  /**
   * Scan over the config and run the callback if the testFn resolves to true
   * @private
   * @param {Clay~ConfigItem|Array} item
   * @param {_scanConfig_testFn} testFn
   * @param {_scanConfig_callback} callback
   * @return {void}
   */
  function _scanConfig(item, testFn, callback) {
    if (Array.isArray(item)) {
      item.forEach(function(item) {
        _scanConfig(item, testFn, callback);
      });
    } else if (item.type === 'section') {
      _scanConfig(item.items, testFn, callback);
    } else if (testFn(item)) {
      callback(item);
    }
  }

  // register standard components
  _scanConfig(self.config, function(item) {
    return standardComponents[item.type];
  }, function(item) {
    self.registerComponent(standardComponents[item.type]);
  });

  // validate config against teh use of appKeys
  _scanConfig(self.config, function(item) {
    return item.appKey;
  }, function() {
    throw new Error('appKeys are no longer supported. ' +
                    'Please follow the migration guide to upgrade your project');
  });
}

/**
 * Register a component to Clay.
 * @param {Object} component - the clay component to register
 * @param {string} component.name - the name of the component
 * @param {string} component.template - HTML template to use for the component
 * @param {string|Object} component.manipulator - methods to attach to the component
 * @param {function} component.manipulator.set - set manipulator method
 * @param {function} component.manipulator.get - get manipulator method
 * @param {Object} [component.defaults] - template defaults
 * @param {function} [component.initialize] - method to scaffold the component
 * @return {boolean} - Returns true if component was registered correctly
 */
Clay.prototype.registerComponent = function(component) {
  this.components[component.name] = component;
};

/**
 * Generate the Data URI used by the config Page with settings injected
 * @return {string}
 */
Clay.prototype.generateUrl = function() {
  var settings = {};
  var emulator = !Pebble || Pebble.platform === 'pypkjs';
  var returnTo = emulator ? '$$$RETURN_TO$$$' : 'pebblejs://close#';

  try {
    settings = JSON.parse(localStorage.getItem('clay-settings')) || {};
  } catch (e) {
    console.error(e.toString());
  }

  var compiledHtml = configPageHtml
    .replace('$$RETURN_TO$$', returnTo)
    .replace('$$CUSTOM_FN$$', toSource(this.customFn))
    .replace('$$CONFIG$$', toSource(this.config))
    .replace('$$SETTINGS$$', toSource(settings))
    .replace('$$COMPONENTS$$', toSource(this.components))
    .replace('$$META$$', toSource(this.meta));

  // if we are in the emulator then we need to proxy the data via a webpage to
  // obtain the return_to.
  // @todo calculate this from the Pebble object or something
  if (emulator) {
    return Clay.encodeDataUri(
      compiledHtml,
      'http://clay.pebble.com.s3-website-us-west-2.amazonaws.com/#'
    );
  }

  return Clay.encodeDataUri(compiledHtml);
};

/**
 * Parse the response from the webviewclosed event data
 * @param {string} response
 * @param {boolean} [convert=true]
 * @returns {Object}
 */
Clay.prototype.getSettings = function(response, convert) {
  // Decode and parse config data as JSON
  var settings = {};
  response = response.match(/^\{/) ? response : decodeURIComponent(response);

  try {
    settings = JSON.parse(response);
  } catch (e) {
    throw new Error('The provided response was not valid JSON');
  }

  // flatten the settings for localStorage
  var settingsStorage = {};
  Object.keys(settings).forEach(function(key) {
    if (typeof settings[key] === 'object' && settings[key]) {
      settingsStorage[key] = settings[key].value;
    } else {
      settingsStorage[key] = settings[key];
    }
  });

  localStorage.setItem('clay-settings', JSON.stringify(settingsStorage));

  return convert === false ?
    settings :
    Clay.prepareSettingsForAppMessage(settings, messageKeys);
};

/**
 * @param {string} input
 * @param {string} [prefix='data:text/html;charset=utf-8,']
 * @returns {string}
 */
Clay.encodeDataUri = function(input, prefix) {
  prefix = typeof prefix !== 'undefined' ? prefix : 'data:text/html;charset=utf-8,';
  return prefix + encodeURIComponent(input);
};

/**
 * Converts the val into a type compatible with Pebble.sendAppMessage().
 *  - Strings will be returned without modification
 *  - Numbers will be returned without modification
 *  - Booleans will be converted to a 0 or 1
 *  - Arrays that contain strings will be returned without modification
 *    eg: ['one', 'two'] becomes ['one', 'two']
 *  - Arrays that contain numbers will be returned without modification
 *    eg: [1, 2] becomes [1, 2]
 *  - Arrays that contain booleans will be converted to a 0 or 1
 *    eg: [true, false] becomes [1, 0]
 *  - Arrays must be single dimensional
 *  - Objects that have a "value" property will apply the above rules to the type of
 *    value. If the value is a number or an array of numbers and the optional
 *    property: "precision" is provided, then the number will be multiplied by 10 to
 *    the power of precision (value * 10 ^ precision) and then floored.
 *    Eg: 1.4567 with a precision set to 3 will become 1456
 * @param {number|string|boolean|Array|Object} val
 * @param {number|string|boolean|Array} val.value
 * @param {number|undefined} [val.precision=0]
 * @returns {number|string|Array}
 */
Clay.prepareForAppMessage = function(val) {

  /**
   * moves the decimal place of a number by precision then drop any remaining decimal
   * places.
   * @param {number} number
   * @param {number} precision - number of decimal places to move
   * @returns {number}
   * @private
   */
  function _normalizeToPrecision(number, precision) {
    return Math.floor(number * Math.pow(10, precision || 0));
  }

  var result;

  if (Array.isArray(val)) {
    result = [];
    val.forEach(function(item, index) {
      result[index] = Clay.prepareForAppMessage(item);
    });
  } else if (typeof val === 'object' && val) {
    if (typeof val.value === 'number') {
      result = _normalizeToPrecision(val.value, val.precision);
    } else if (Array.isArray(val.value)) {
      result = val.value.map(function(item) {
        if (typeof item === 'number') {
          return _normalizeToPrecision(item, val.precision);
        }
        return item;
      });
    } else {
      result = Clay.prepareForAppMessage(val.value);
    }
  } else if (typeof val === 'boolean') {
    result = val ? 1 : 0;
  } else {
    result = val;
  }

  return result;
};

/**
 * Converts a Clay settings dict into one that is compatible with
 * Pebble.sendAppMessage(); It also uses the provided messageKeys to correctly
 * assign arrays into individual keys
 * @see {prepareForAppMessage}
 * @param {Object} settings
 * @returns {{}}
 */
Clay.prepareSettingsForAppMessage = function(settings) {

  // flatten settings
  var flatSettings = {};
  Object.keys(settings).forEach(function(key) {
    var val = settings[key];
    var matches = key.match(/(.+?)(?:\[(\d*)\])?$/);

    if (!matches[2]) {
      flatSettings[key] = val;
      return;
    }

    var position = parseInt(matches[2], 10);
    key = matches[1];

    if (typeof flatSettings[key] === 'undefined') {
      flatSettings[key] = [];
    }

    flatSettings[key][position] = val;
  });

  var result = {};
  Object.keys(flatSettings).forEach(function(key) {
    var messageKey = messageKeys[key];
    var settingArr = Clay.prepareForAppMessage(flatSettings[key]);
    settingArr = Array.isArray(settingArr) ? settingArr : [settingArr];

    settingArr.forEach(function(setting, index) {
      result[messageKey + index] = setting;
    });
  });

  // validate the settings
  Object.keys(result).forEach(function(key) {
    if (Array.isArray(result[key])) {
      throw new Error('Clay does not support 2 dimensional arrays for item ' +
                      'values. Make sure you are not attempting to use array ' +
                      'syntax (eg: "myMessageKey[2]") in the messageKey for ' +
                      'components that return an array, such as a checkboxgroup');
    }
  });

  return result;
};

module.exports = Clay;
<commit_msg>Remove unused function parameter in clay.getSettings()<commit_after>'use strict';

var configPageHtml = require('./tmp/config-page.html');
var toSource = require('tosource');
var standardComponents = require('./src/scripts/components');
var deepcopy = require('deepcopy/build/deepcopy.min');
var version = require('./package.json').version;
var messageKeys = require('message_keys');

/**
 * @param {Array} config - the Clay config
 * @param {function} [customFn] - Custom code to run from the config page. Will run
 *   with the ClayConfig instance as context
 * @param {Object} [options] - Additional options to pass to Clay
 * @param {boolean} [options.autoHandleEvents=true] - If false, Clay will not
 *   automatically handle the 'showConfiguration' and 'webviewclosed' events
 * @param {*} [options.userData={}] - Arbitrary data to pass to the config page. Will
 *   be available as `clayConfig.meta.userData`
 * @constructor
 */
function Clay(config, customFn, options) {
  var self = this;

  if (!Array.isArray(config)) {
    throw new Error('config must be an Array');
  }

  if (customFn && typeof customFn !== 'function') {
    throw new Error('customFn must be a function or "null"');
  }

  options = options || {};

  self.config = deepcopy(config);
  self.customFn = customFn || function() {};
  self.components = {};
  self.meta = {
    activeWatchInfo: null,
    accountToken: '',
    watchToken: '',
    userData: {}
  };
  self.version = version;

  /**
   * Populate the meta with data from the Pebble object. Make sure to run this inside
   * either the "showConfiguration" or "ready" event handler
   * @return {void}
   */
  function _populateMeta() {
    self.meta = {
      activeWatchInfo: Pebble.getActiveWatchInfo && Pebble.getActiveWatchInfo(),
      accountToken: Pebble.getAccountToken(),
      watchToken: Pebble.getWatchToken(),
      userData: deepcopy(options.userData || {})
    };
  }

  // Let Clay handle all the magic
  if (options.autoHandleEvents !== false && typeof Pebble !== 'undefined') {

    Pebble.addEventListener('showConfiguration', function() {
      _populateMeta();
      Pebble.openURL(self.generateUrl());
    });

    Pebble.addEventListener('webviewclosed', function(e) {

      if (!e || !e.response) { return; }

      // Send settings to Pebble watchapp
      Pebble.sendAppMessage(self.getSettings(e.response), function() {
        console.log('Sent config data to Pebble');
      }, function(error) {
        console.log('Failed to send config data!');
        console.log(JSON.stringify(error));
      });
    });
  } else if (typeof Pebble !== 'undefined') {
    Pebble.addEventListener('ready', function() {
      _populateMeta();
    });
  }

  /**
   * If this function returns true then the callback will be executed
   * @callback _scanConfig_testFn
   * @param {Clay~ConfigItem} item
   */

  /**
   * @callback _scanConfig_callback
   * @param {Clay~ConfigItem} item
   */

  /**
   * Scan over the config and run the callback if the testFn resolves to true
   * @private
   * @param {Clay~ConfigItem|Array} item
   * @param {_scanConfig_testFn} testFn
   * @param {_scanConfig_callback} callback
   * @return {void}
   */
  function _scanConfig(item, testFn, callback) {
    if (Array.isArray(item)) {
      item.forEach(function(item) {
        _scanConfig(item, testFn, callback);
      });
    } else if (item.type === 'section') {
      _scanConfig(item.items, testFn, callback);
    } else if (testFn(item)) {
      callback(item);
    }
  }

  // register standard components
  _scanConfig(self.config, function(item) {
    return standardComponents[item.type];
  }, function(item) {
    self.registerComponent(standardComponents[item.type]);
  });

  // validate config against teh use of appKeys
  _scanConfig(self.config, function(item) {
    return item.appKey;
  }, function() {
    throw new Error('appKeys are no longer supported. ' +
                    'Please follow the migration guide to upgrade your project');
  });
}

/**
 * Register a component to Clay.
 * @param {Object} component - the clay component to register
 * @param {string} component.name - the name of the component
 * @param {string} component.template - HTML template to use for the component
 * @param {string|Object} component.manipulator - methods to attach to the component
 * @param {function} component.manipulator.set - set manipulator method
 * @param {function} component.manipulator.get - get manipulator method
 * @param {Object} [component.defaults] - template defaults
 * @param {function} [component.initialize] - method to scaffold the component
 * @return {boolean} - Returns true if component was registered correctly
 */
Clay.prototype.registerComponent = function(component) {
  this.components[component.name] = component;
};

/**
 * Generate the Data URI used by the config Page with settings injected
 * @return {string}
 */
Clay.prototype.generateUrl = function() {
  var settings = {};
  var emulator = !Pebble || Pebble.platform === 'pypkjs';
  var returnTo = emulator ? '$$$RETURN_TO$$$' : 'pebblejs://close#';

  try {
    settings = JSON.parse(localStorage.getItem('clay-settings')) || {};
  } catch (e) {
    console.error(e.toString());
  }

  var compiledHtml = configPageHtml
    .replace('$$RETURN_TO$$', returnTo)
    .replace('$$CUSTOM_FN$$', toSource(this.customFn))
    .replace('$$CONFIG$$', toSource(this.config))
    .replace('$$SETTINGS$$', toSource(settings))
    .replace('$$COMPONENTS$$', toSource(this.components))
    .replace('$$META$$', toSource(this.meta));

  // if we are in the emulator then we need to proxy the data via a webpage to
  // obtain the return_to.
  // @todo calculate this from the Pebble object or something
  if (emulator) {
    return Clay.encodeDataUri(
      compiledHtml,
      'http://clay.pebble.com.s3-website-us-west-2.amazonaws.com/#'
    );
  }

  return Clay.encodeDataUri(compiledHtml);
};

/**
 * Parse the response from the webviewclosed event data
 * @param {string} response
 * @param {boolean} [convert=true]
 * @returns {Object}
 */
Clay.prototype.getSettings = function(response, convert) {
  // Decode and parse config data as JSON
  var settings = {};
  response = response.match(/^\{/) ? response : decodeURIComponent(response);

  try {
    settings = JSON.parse(response);
  } catch (e) {
    throw new Error('The provided response was not valid JSON');
  }

  // flatten the settings for localStorage
  var settingsStorage = {};
  Object.keys(settings).forEach(function(key) {
    if (typeof settings[key] === 'object' && settings[key]) {
      settingsStorage[key] = settings[key].value;
    } else {
      settingsStorage[key] = settings[key];
    }
  });

  localStorage.setItem('clay-settings', JSON.stringify(settingsStorage));

  return convert === false ? settings : Clay.prepareSettingsForAppMessage(settings);
};

/**
 * @param {string} input
 * @param {string} [prefix='data:text/html;charset=utf-8,']
 * @returns {string}
 */
Clay.encodeDataUri = function(input, prefix) {
  prefix = typeof prefix !== 'undefined' ? prefix : 'data:text/html;charset=utf-8,';
  return prefix + encodeURIComponent(input);
};

/**
 * Converts the val into a type compatible with Pebble.sendAppMessage().
 *  - Strings will be returned without modification
 *  - Numbers will be returned without modification
 *  - Booleans will be converted to a 0 or 1
 *  - Arrays that contain strings will be returned without modification
 *    eg: ['one', 'two'] becomes ['one', 'two']
 *  - Arrays that contain numbers will be returned without modification
 *    eg: [1, 2] becomes [1, 2]
 *  - Arrays that contain booleans will be converted to a 0 or 1
 *    eg: [true, false] becomes [1, 0]
 *  - Arrays must be single dimensional
 *  - Objects that have a "value" property will apply the above rules to the type of
 *    value. If the value is a number or an array of numbers and the optional
 *    property: "precision" is provided, then the number will be multiplied by 10 to
 *    the power of precision (value * 10 ^ precision) and then floored.
 *    Eg: 1.4567 with a precision set to 3 will become 1456
 * @param {number|string|boolean|Array|Object} val
 * @param {number|string|boolean|Array} val.value
 * @param {number|undefined} [val.precision=0]
 * @returns {number|string|Array}
 */
Clay.prepareForAppMessage = function(val) {

  /**
   * moves the decimal place of a number by precision then drop any remaining decimal
   * places.
   * @param {number} number
   * @param {number} precision - number of decimal places to move
   * @returns {number}
   * @private
   */
  function _normalizeToPrecision(number, precision) {
    return Math.floor(number * Math.pow(10, precision || 0));
  }

  var result;

  if (Array.isArray(val)) {
    result = [];
    val.forEach(function(item, index) {
      result[index] = Clay.prepareForAppMessage(item);
    });
  } else if (typeof val === 'object' && val) {
    if (typeof val.value === 'number') {
      result = _normalizeToPrecision(val.value, val.precision);
    } else if (Array.isArray(val.value)) {
      result = val.value.map(function(item) {
        if (typeof item === 'number') {
          return _normalizeToPrecision(item, val.precision);
        }
        return item;
      });
    } else {
      result = Clay.prepareForAppMessage(val.value);
    }
  } else if (typeof val === 'boolean') {
    result = val ? 1 : 0;
  } else {
    result = val;
  }

  return result;
};

/**
 * Converts a Clay settings dict into one that is compatible with
 * Pebble.sendAppMessage(); It also uses the provided messageKeys to correctly
 * assign arrays into individual keys
 * @see {prepareForAppMessage}
 * @param {Object} settings
 * @returns {{}}
 */
Clay.prepareSettingsForAppMessage = function(settings) {

  // flatten settings
  var flatSettings = {};
  Object.keys(settings).forEach(function(key) {
    var val = settings[key];
    var matches = key.match(/(.+?)(?:\[(\d*)\])?$/);

    if (!matches[2]) {
      flatSettings[key] = val;
      return;
    }

    var position = parseInt(matches[2], 10);
    key = matches[1];

    if (typeof flatSettings[key] === 'undefined') {
      flatSettings[key] = [];
    }

    flatSettings[key][position] = val;
  });

  var result = {};
  Object.keys(flatSettings).forEach(function(key) {
    var messageKey = messageKeys[key];
    var settingArr = Clay.prepareForAppMessage(flatSettings[key]);
    settingArr = Array.isArray(settingArr) ? settingArr : [settingArr];

    settingArr.forEach(function(setting, index) {
      result[messageKey + index] = setting;
    });
  });

  // validate the settings
  Object.keys(result).forEach(function(key) {
    if (Array.isArray(result[key])) {
      throw new Error('Clay does not support 2 dimensional arrays for item ' +
                      'values. Make sure you are not attempting to use array ' +
                      'syntax (eg: "myMessageKey[2]") in the messageKey for ' +
                      'components that return an array, such as a checkboxgroup');
    }
  });

  return result;
};

module.exports = Clay;
<|endoftext|>


<commit_before>'use strict';
var assert = require('assert');

function HtmlWebpackExcludeAssetsPlugin (options) {
  assert.equal(options, undefined, 'The HtmlWebpackExcludeAssetsPlugin does not accept any options');
}

HtmlWebpackExcludeAssetsPlugin.prototype.apply = function (compiler) {
  var self = this;

  // Hook into the html-webpack-plugin processing
  compiler.plugin('compilation', function (compilation) {
    compilation.plugin('html-webpack-plugin-alter-asset-tags', function (htmlPluginData, callback) {
      var excludeAssets = htmlPluginData.plugin.options.excludeAssets;
      // Skip if the plugin configuration didn't set `excludeAssets`
      if (!excludeAssets) {
        return callback(null);
      }

      if (excludeAssets.constructor !== Array) {
        excludeAssets = [excludeAssets];
      }

      // Skip invalid RegExp patterns
      var excludePatterns = excludeAssets.filter(function (excludePattern) {
        return excludePattern.constructor === RegExp;
      });

      var result = self.processAssets(excludePatterns, htmlPluginData);
      callback(null, result);
    });
  });
};

HtmlWebpackExcludeAssetsPlugin.prototype.isExcluded = function (excludePatterns, assetPath) {
  return excludePatterns.filter(function (excludePattern) {
    return excludePattern.test(assetPath);
  }).length > 0;
};

HtmlWebpackExcludeAssetsPlugin.prototype.processAssets = function (excludePatterns, pluginData) {
  var self = this;
  var body = [];
  var head = [];

  pluginData.head.forEach(function (tag) {
    if (!tag.attributes || !self.isExcluded(excludePatterns, tag.attributes.src || tag.attributes.href)) {
      head.push(tag);
    }
  });

  pluginData.body.forEach(function (tag) {
    if (!tag.attributes || !self.isExcluded(excludePatterns, tag.attributes.src || tag.attributes.href)) {
      body.push(tag);
    }
  });

  return { head: head, body: body };
};

module.exports = HtmlWebpackExcludeAssetsPlugin;
<commit_msg>Fix interop with other plugins<commit_after>'use strict';
var assert = require('assert');

function HtmlWebpackExcludeAssetsPlugin (options) {
  assert.equal(options, undefined, 'The HtmlWebpackExcludeAssetsPlugin does not accept any options');
}

HtmlWebpackExcludeAssetsPlugin.prototype.apply = function (compiler) {
  var self = this;

  // Hook into the html-webpack-plugin processing
  compiler.plugin('compilation', function (compilation) {
    compilation.plugin('html-webpack-plugin-alter-asset-tags', function (htmlPluginData, callback) {
      var excludeAssets = htmlPluginData.plugin.options.excludeAssets;
      // Skip if the plugin configuration didn't set `excludeAssets`
      if (!excludeAssets) {
        return callback(null, htmlPluginData);
      }

      if (excludeAssets.constructor !== Array) {
        excludeAssets = [excludeAssets];
      }

      // Skip invalid RegExp patterns
      var excludePatterns = excludeAssets.filter(function (excludePattern) {
        return excludePattern.constructor === RegExp;
      });

      var result = self.processAssets(excludePatterns, htmlPluginData);
      callback(null, result);
    });
  });
};

HtmlWebpackExcludeAssetsPlugin.prototype.isExcluded = function (excludePatterns, assetPath) {
  return excludePatterns.filter(function (excludePattern) {
    return excludePattern.test(assetPath);
  }).length > 0;
};

HtmlWebpackExcludeAssetsPlugin.prototype.processAssets = function (excludePatterns, pluginData) {
  var self = this;
  var body = [];
  var head = [];

  pluginData.head.forEach(function (tag) {
    if (!tag.attributes || !self.isExcluded(excludePatterns, tag.attributes.src || tag.attributes.href)) {
      head.push(tag);
    }
  });

  pluginData.body.forEach(function (tag) {
    if (!tag.attributes || !self.isExcluded(excludePatterns, tag.attributes.src || tag.attributes.href)) {
      body.push(tag);
    }
  });

  return { head: head, body: body, plugin: pluginData.plugin, chunks: pluginData.chunks, outputName: pluginData.outputName };
};

module.exports = HtmlWebpackExcludeAssetsPlugin;
<|endoftext|>