<commit_before>
    angular
        .module('openeApp.documents')
        .controller('DocumentController', DocumentController);

    function DocumentController($scope, $state, $stateParams, $mdDialog, $translate, documentService, caseDocumentsService, documentPreviewService,
                caseDocumentFileDialogService, casePartiesService, alfrescoFolderService, sessionService, caseService, fileUtilsService) {

        var caseId = $stateParams.caseId;
        var vm = this;
        vm.caseId = caseId;
        vm.pageSize = 10;
        vm.isAdmin = sessionService.isAdmin();

        vm.uploadDocument = uploadDocument;
        vm.previewDocument = previewDocument;
        vm.emailDocuments = emailDocuments;
        vm.createDocumentFromTemplate = createDocumentFromTemplate;
        vm.noDocuments = noDocuments;
        vm.deleteDocument = deleteDocument;

        activate();

        function activate(){
            // If dashboard, load my documents
            if($state.is('dashboard')) {
                loadMyDocuments();
            } else {
                loadDocumentsByCase();
            }
        }

        function loadMyDocuments() {
            vm.documents = documentService.getDocuments().then(function(response) {
                vm.documents = response.items;
                addThumbnailUrl();
            });
        }

        function loadDocumentsByCase(page){
            if(page == undefined){
                page = 1;
            }
            var res = caseDocumentsService.getDocumentsByCaseId(caseId, page, vm.pageSize);
            res.then(function(response) {
                vm.documents = response.documents;
                vm.contentRange = response.contentRange;
                addThumbnailUrl();
                var pages = [];
                var pagesCount = Math.ceil(response.contentRange.totalItems / vm.pageSize);
                for(var i=0; i < pagesCount; i++){
                    pages.push(i+1);
                }
                vm.pages = pages;
            });
        }

        function addThumbnailUrl () {
            // Mimetype has different paths on caseDocs vs MyDocuments
            var mimeTypeProperty = $state.is('dashboard') ? 'mimetype' : 'fileMimeType';
            vm.documents.forEach(function(document){
                document.thumbNailURL = fileUtilsService.getFileIconByMimetype(document[mimeTypeProperty],24);
            });
        }

        function uploadDocument(){
            caseDocumentFileDialogService.uploadCaseDocument(caseId).then(function(result){
                loadDocumentsByCase();
            });
        }

        function previewDocument(nodeRef){
            documentPreviewService.previewDocument(nodeRef);
        }

        function deleteDocument(document){
            var confirm = $mdDialog.confirm()
                .title($translate.instant('COMMON.CONFIRM'))
                .content($translate.instant('DOCUMENT.ARE_YOU_SURE_YOU_WANT_TO_DELETE_THE_DOCUMENT', {document_title: document["cm:title"]}))
                .ariaLabel('')
                .targetEvent(null)
                .ok($translate.instant('COMMON.YES'))
                .cancel($translate.instant('COMMON.CANCEL'));
            $mdDialog.show(confirm).then(function() {
                alfrescoFolderService.deleteFolder(document.nodeRef).then(function(result){
                   setTimeout(loadDocumentsByCase, 500);
                });
            });
        }

        function emailDocuments() {
            caseDocumentsService.getDocumentsByCaseId(vm.caseId, 1, 100).then(function(response) {
                vm.docs = response.documents;
                $mdDialog.show({
                    templateUrl: '/app/src/documents/view/emailDialog.html',
                    controller: EmailDocumentsDialogController,
                    controllerAs: 'vm',
                    clickOutsideToClose: true,
                    locals: {
                        docs: vm.docs,
                        caseId: vm.caseId
                    }
                });
            });
        }

        function EmailDocumentsDialogController($mdDialog, docs, caseId, $timeout, $q) {
            var vm = this;

            vm.documents = docs;
            vm.caseId = caseId;
            vm.emailDocuments = emailDocuments;
            vm.cancel = cancel;
            vm.querySearch = querySearch;
            vm.to = [];
            vm.selectedItem = null;
            vm.searchText = null;

            activate()

            function activate() {
                casePartiesService.getCaseParties(caseId).then(function(response) {
                    vm.parties = response;

                })
            }

            function querySearch(query) {
                var results = query ? vm.parties.filter(createFilterFor(query)) : [];
                return results;
            }

            function createFilterFor(query) {
                var lowercaseQuery = angular.lowercase(query);
                return function filterFn(party) {
                    return (party.displayName.toLowerCase().indexOf(lowercaseQuery) != -1) || (party.displayName.toLowerCase().indexOf(lowercaseQuery) === 0);
                };

            }
            function emailDocuments() {
                // Send the email
                var toList = [];
                for (var person in vm.to) {
                  // Backend still expects objects with nodeRef property
                  toList.push({nodeRef: vm.to[person].nodeRef});
                }
                caseService.sendEmail(caseId, {
                    'to': toList,
                    'subject': vm.subject,
                    'message': vm.message,
                    'documents': vm.documents.filter(function(document) {
                        return document.selected;
                    })
                });
                $mdDialog.hide();
            }

            function cancel(form) {
                $mdDialog.cancel();
            }
        }

        function noDocuments() {
            return typeof vm.documents === 'undefined' || vm.documents.length === 0;
        }

        function createDocumentFromTemplate() {
            $mdDialog.show({
                templateUrl: '/app/src/documents/view/createDocumentFromTemplateDialog.html',
                controller: CreateDocumentFromTemplateDialogController,
                controllerAs: 'vm',
                clickOutsideToClose: true,
                locals: {
                    caseId: vm.caseId
                }
            });
        }

        function CreateDocumentFromTemplateDialogController($scope, $filter, $mdDialog, $translate, officeTemplateService, sessionService, contactsService, alfrescoNodeUtils, caseId) {
            var vm = this;

            $scope.vm = vm;

            vm.caseId = caseId;
            vm.template = null;
            vm.cancel = cancel;
            vm.fillAndSaveToCase = fillAndSaveToCase;
            vm.fieldData = {};

            activate();

            function activate() {
                $scope.$watch(function (scope) {
                    return vm.template;
                }, function (newValue, oldValue) {
                    if (typeof newValue !== 'undefined' && newValue != null) {
                        officeTemplateService.getTemplate(newValue.nodeRef).then(function (template) {
                            vm.currentTemplate = template;
                        });
                    }
                });

                $scope.$watch(function (scope) {
                    return vm.receiver;
                }, function (newValue, oldValue) {
                    if (typeof newValue !== 'undefined' && newValue != null) {
                        // Update the field values based on the selected
                        // contact info.
                        var nodeRefParts = alfrescoNodeUtils.processNodeRef(newValue.nodeRef);
                        contactsService.getContact(nodeRefParts.storeType, nodeRefParts.storeId, nodeRefParts.id).then(function (contact) {
                            console.log(contact);
                            for (var prop in contact) {
                                if (!contact.hasOwnProperty(prop)) continue;
                                vm.fieldData["receiver." + prop] = contact[prop];
                            }

                            // Annoyingly, the name returned by
                            // contactService is an auto-generated ID, so
                            // we have to overwrite it here. This should
                            // really be fixed in the backend.
                            if ("organizationName" in contact) {
                                vm.fieldData["receiver.name"] = contact.organizationName;
                            } else if ("firstName" in contact) {
                                vm.fieldData["receiver.name"] = contact.firstName + " " + contact.lastName;
                            }
                        });
                    }
                });

                casePartiesService.getCaseParties(caseId).then(function(response) {
                    vm.parties = response;
                    vm.receiver = [];
                });

                // Load the necessary data
                caseService.getCaseInfo(caseId).then(function (caseInfo) {
                    function getPropValue(prop) {
                        if (prop in caseInfo.properties && typeof caseInfo.properties[prop] !== null) {
                            var val = caseInfo.properties[prop];
                            if (typeof val != "object") {
                                return null;
                            } else if ("displayValue" in val) {
                                return val.displayValue;
                            } else if ("value" in val) {
                                return val.value;
                            } else {
                                return null;
                            }
                        }
                    }

                    angular.extend(vm.fieldData, {
                        "case.id": getPropValue("oe:id"),
                        "case.title": getPropValue("cm:title"),
                        "case.description": getPropValue("cm:description"),
                        "case.journalKey": getPropValue("oe:journalKey"),
                        "case.journalFacet": getPropValue("oe:journalFacet"),
                        "case.type": $filter('caseType')(caseInfo.type)
                    });
                });

                var user = sessionService.getUserInfo().user;
                for (var prop in user) {
                    if (!user.hasOwnProperty(prop)) continue;
                    vm.fieldData["user." + prop] = user[prop];
                }
                vm.fieldData["user.name"] = user.firstName + " " + user.lastName;
            }

            function fillAndSaveToCase(template, fieldData) {
                var documentProperties = {};
                officeTemplateService.fillTemplate(template.nodeRef, fieldData).then(function (blob) {
                    var uniqueStr = new Date().getTime();

                    // Convert the Blob to a File
                    // (http://stackoverflow.com/a/29390393)
                    blob.lastModifiedDate = new Date();
                    blob.name = template.name.split('.').slice(0, -1).join(".") + "-" + uniqueStr + ".pdf";

                    caseDocumentFileDialogService.uploadCaseDocument(caseId, blob).then(function (result) {
                        loadDocumentsByCase(1);
                    });
                });
            }

            function cancel(form) {
                $mdDialog.cancel();
            }
        }
    }
<commit_msg>Fix problem sending of documents via email when message is empty.<commit_after>
    angular
        .module('openeApp.documents')
        .controller('DocumentController', DocumentController);

    function DocumentController($scope, $state, $stateParams, $mdDialog, $translate, documentService, caseDocumentsService, documentPreviewService,
                caseDocumentFileDialogService, casePartiesService, alfrescoFolderService, sessionService, caseService, fileUtilsService) {

        var caseId = $stateParams.caseId;
        var vm = this;
        vm.caseId = caseId;
        vm.pageSize = 10;
        vm.isAdmin = sessionService.isAdmin();

        vm.uploadDocument = uploadDocument;
        vm.previewDocument = previewDocument;
        vm.emailDocuments = emailDocuments;
        vm.createDocumentFromTemplate = createDocumentFromTemplate;
        vm.noDocuments = noDocuments;
        vm.deleteDocument = deleteDocument;

        activate();

        function activate(){
            // If dashboard, load my documents
            if($state.is('dashboard')) {
                loadMyDocuments();
            } else {
                loadDocumentsByCase();
            }
        }

        function loadMyDocuments() {
            vm.documents = documentService.getDocuments().then(function(response) {
                vm.documents = response.items;
                addThumbnailUrl();
            });
        }

        function loadDocumentsByCase(page){
            if(page == undefined){
                page = 1;
            }
            var res = caseDocumentsService.getDocumentsByCaseId(caseId, page, vm.pageSize);
            res.then(function(response) {
                vm.documents = response.documents;
                vm.contentRange = response.contentRange;
                addThumbnailUrl();
                var pages = [];
                var pagesCount = Math.ceil(response.contentRange.totalItems / vm.pageSize);
                for(var i=0; i < pagesCount; i++){
                    pages.push(i+1);
                }
                vm.pages = pages;
            });
        }

        function addThumbnailUrl () {
            // Mimetype has different paths on caseDocs vs MyDocuments
            var mimeTypeProperty = $state.is('dashboard') ? 'mimetype' : 'fileMimeType';
            vm.documents.forEach(function(document){
                document.thumbNailURL = fileUtilsService.getFileIconByMimetype(document[mimeTypeProperty],24);
            });
        }

        function uploadDocument(){
            caseDocumentFileDialogService.uploadCaseDocument(caseId).then(function(result){
                loadDocumentsByCase();
            });
        }

        function previewDocument(nodeRef){
            documentPreviewService.previewDocument(nodeRef);
        }

        function deleteDocument(document){
            var confirm = $mdDialog.confirm()
                .title($translate.instant('COMMON.CONFIRM'))
                .content($translate.instant('DOCUMENT.ARE_YOU_SURE_YOU_WANT_TO_DELETE_THE_DOCUMENT', {document_title: document["cm:title"]}))
                .ariaLabel('')
                .targetEvent(null)
                .ok($translate.instant('COMMON.YES'))
                .cancel($translate.instant('COMMON.CANCEL'));
            $mdDialog.show(confirm).then(function() {
                alfrescoFolderService.deleteFolder(document.nodeRef).then(function(result){
                   setTimeout(loadDocumentsByCase, 500);
                });
            });
        }

        function emailDocuments() {
            caseDocumentsService.getDocumentsByCaseId(vm.caseId, 1, 100).then(function(response) {
                vm.docs = response.documents;
                $mdDialog.show({
                    templateUrl: '/app/src/documents/view/emailDialog.html',
                    controller: EmailDocumentsDialogController,
                    controllerAs: 'vm',
                    clickOutsideToClose: true,
                    locals: {
                        docs: vm.docs,
                        caseId: vm.caseId
                    }
                });
            });
        }

        function EmailDocumentsDialogController($mdDialog, docs, caseId, $timeout, $q) {
            var vm = this;

            vm.documents = docs;
            vm.caseId = caseId;
            vm.emailDocuments = emailDocuments;
            vm.cancel = cancel;
            vm.querySearch = querySearch;
            vm.to = [];
            vm.selectedItem = null;
            vm.searchText = null;

            activate()

            function activate() {
                casePartiesService.getCaseParties(caseId).then(function(response) {
                    vm.parties = response;

                })
            }

            function querySearch(query) {
                var results = query ? vm.parties.filter(createFilterFor(query)) : [];
                return results;
            }

            function createFilterFor(query) {
                var lowercaseQuery = angular.lowercase(query);
                return function filterFn(party) {
                    return (party.displayName.toLowerCase().indexOf(lowercaseQuery) != -1) || (party.displayName.toLowerCase().indexOf(lowercaseQuery) === 0);
                };

            }
            function emailDocuments() {
                // Send the email
                var toList = [];
                for (var person in vm.to) {
                  // Backend still expects objects with nodeRef property
                  toList.push({nodeRef: vm.to[person].nodeRef});
                }
                if (vm.message == null) {
                    vm.message = "";
                }
                caseService.sendEmail(caseId, {
                    'to': toList,
                    'subject': vm.subject,
                    'message': vm.message,
                    'documents': vm.documents.filter(function(document) {
                        return document.selected;
                    })
                });
                $mdDialog.hide();
            }

            function cancel(form) {
                $mdDialog.cancel();
            }
        }

        function noDocuments() {
            return typeof vm.documents === 'undefined' || vm.documents.length === 0;
        }

        function createDocumentFromTemplate() {
            $mdDialog.show({
                templateUrl: '/app/src/documents/view/createDocumentFromTemplateDialog.html',
                controller: CreateDocumentFromTemplateDialogController,
                controllerAs: 'vm',
                clickOutsideToClose: true,
                locals: {
                    caseId: vm.caseId
                }
            });
        }

        function CreateDocumentFromTemplateDialogController($scope, $filter, $mdDialog, $translate, officeTemplateService, sessionService, contactsService, alfrescoNodeUtils, caseId) {
            var vm = this;

            $scope.vm = vm;

            vm.caseId = caseId;
            vm.template = null;
            vm.cancel = cancel;
            vm.fillAndSaveToCase = fillAndSaveToCase;
            vm.fieldData = {};

            activate();

            function activate() {
                $scope.$watch(function (scope) {
                    return vm.template;
                }, function (newValue, oldValue) {
                    if (typeof newValue !== 'undefined' && newValue != null) {
                        officeTemplateService.getTemplate(newValue.nodeRef).then(function (template) {
                            vm.currentTemplate = template;
                        });
                    }
                });

                $scope.$watch(function (scope) {
                    return vm.receiver;
                }, function (newValue, oldValue) {
                    if (typeof newValue !== 'undefined' && newValue != null) {
                        // Update the field values based on the selected
                        // contact info.
                        var nodeRefParts = alfrescoNodeUtils.processNodeRef(newValue.nodeRef);
                        contactsService.getContact(nodeRefParts.storeType, nodeRefParts.storeId, nodeRefParts.id).then(function (contact) {
                            console.log(contact);
                            for (var prop in contact) {
                                if (!contact.hasOwnProperty(prop)) continue;
                                vm.fieldData["receiver." + prop] = contact[prop];
                            }

                            // Annoyingly, the name returned by
                            // contactService is an auto-generated ID, so
                            // we have to overwrite it here. This should
                            // really be fixed in the backend.
                            if ("organizationName" in contact) {
                                vm.fieldData["receiver.name"] = contact.organizationName;
                            } else if ("firstName" in contact) {
                                vm.fieldData["receiver.name"] = contact.firstName + " " + contact.lastName;
                            }
                        });
                    }
                });

                casePartiesService.getCaseParties(caseId).then(function(response) {
                    vm.parties = response;
                    vm.receiver = [];
                });

                // Load the necessary data
                caseService.getCaseInfo(caseId).then(function (caseInfo) {
                    function getPropValue(prop) {
                        if (prop in caseInfo.properties && typeof caseInfo.properties[prop] !== null) {
                            var val = caseInfo.properties[prop];
                            if (typeof val != "object") {
                                return null;
                            } else if ("displayValue" in val) {
                                return val.displayValue;
                            } else if ("value" in val) {
                                return val.value;
                            } else {
                                return null;
                            }
                        }
                    }

                    angular.extend(vm.fieldData, {
                        "case.id": getPropValue("oe:id"),
                        "case.title": getPropValue("cm:title"),
                        "case.description": getPropValue("cm:description"),
                        "case.journalKey": getPropValue("oe:journalKey"),
                        "case.journalFacet": getPropValue("oe:journalFacet"),
                        "case.type": $filter('caseType')(caseInfo.type)
                    });
                });

                var user = sessionService.getUserInfo().user;
                for (var prop in user) {
                    if (!user.hasOwnProperty(prop)) continue;
                    vm.fieldData["user." + prop] = user[prop];
                }
                vm.fieldData["user.name"] = user.firstName + " " + user.lastName;
            }

            function fillAndSaveToCase(template, fieldData) {
                var documentProperties = {};
                officeTemplateService.fillTemplate(template.nodeRef, fieldData).then(function (blob) {
                    var uniqueStr = new Date().getTime();

                    // Convert the Blob to a File
                    // (http://stackoverflow.com/a/29390393)
                    blob.lastModifiedDate = new Date();
                    blob.name = template.name.split('.').slice(0, -1).join(".") + "-" + uniqueStr + ".pdf";

                    caseDocumentFileDialogService.uploadCaseDocument(caseId, blob).then(function (result) {
                        loadDocumentsByCase(1);
                    });
                });
            }

            function cancel(form) {
                $mdDialog.cancel();
            }
        }
    }
<|endoftext|>


<commit_before>$(document).ready(function(){

    /// STACKEDINLINE / INLINERELATED
    $('div[name="inlinerelated"]').each(function(i) {
        $(this).addClass("collapsed");
        $(this).find('h3:first').attr("class", "collapse-toggle");
    });
    $('div[name="inlinerelated"] h3.collapse-toggle').bind("click", function(e){
        $(this).parent().toggleClass('collapsed');
    });

    /// INLINEGROUPS (STACKED & TABULAR)
    $('div.inline-group').each(function() {
        $(this).addClass("collapsed");
        $(this).find('h2:first').attr("class", "collapse-toggle");
    });
    $('div[name="inlinegroup"] h2.collapse-toggle:first').bind("click", function(){
        $(this).parent().toggleClass('collapsed');
    });
    $('div[name="inlinegrouptabular"] h2.collapse-toggle:first').bind("click", function(){
        $(this).parent().toggleClass('collapsed');
    });

    /// OPEN STACKEDINLINE WITH ERRORS
    $('div[name="inlinerelated"]').find('div[class*="errors"]:first').each(function(i) {
        /// toggle each inlinerelated with error-rows inside
        $(this).parent().parent().toggleClass("collapsed");
    });
    $('div[name="inlinegroup"]').find('div[class*="errors"]:first').each(function(i) {
        /// toggle each inlinegroup with error-rows inside
        $(this).parent().parent().parent().toggleClass("collapsed");
    });
    /// OPEN TABULARINLINE WITH ERRORS
    $('div[name="inlinerelatedtabular"]').find('td[class*="error"]:first').each(function(i) {
        /// toggle each inlinerelated with error-rows inside
        $(this).parent().parent().parent().parent().parent().parent().toggleClass("collapsed");
    });

});
<commit_msg>js for inlines, update<commit_after>$(document).ready(function(){

    /// INLINE ELEMENTS
    /// collapsible elements for stacked inlines
    $('div[name="inlinerelated"]').each(function(i) {
        /// inlines are closed by default
        $(this).addClass("collapsed");
        /// add collapse-class to the first headline
        $(this).find('h3:first').attr("class", "collapse-toggle");
    });
    $('div[name="inlinerelated"] h3.collapse-toggle').bind("click", function(){
        /// add collapse-handler
        $(this).parent().toggleClass('collapsed');
    });

    /// INLINEGROUPS (STACKED & TABULAR)
    $('div.inline-group').each(function() {
        /// inlinegroups are closed by default
        $(this).addClass("collapsed");
        /// add collapse-class to the first headline
        $(this).find('h2:first').attr("class", "collapse-toggle");
    });
    $('div[name="inlinegroup"] h2.collapse-toggle').bind("click", function(){
        /// add collapse-handler for stacked inlines
        $(this).parent().toggleClass('collapsed');
    });
    $('div[name="inlinegrouptabular"] h2.collapse-toggle').bind("click", function(){
        /// add collapse-handler for tabular inlines
        $(this).parent().toggleClass('collapsed');
    });

    /// OPEN STACKEDINLINE WITH ERRORS
    $('div[name="inlinerelated"]').find('div[class*="errors"]:first').each(function(){
        /// open inline element
        $(this).parent().parent().toggleClass("collapsed");
    });
    $('div[name="inlinegroup"]').find('div[class*="errors"]:first').each(function(){
        /// open inline group
        $(this).parent().parent().parent().parent().toggleClass("collapsed");
    });

    /// OPEN TABULARINLINE WITH ERRORS
    $('div[name="inlinerelatedtabular"]').find('div[class*="error"]:first').each(function(i) {
        /// open inline group
        $(this).parent().parent().parent().parent().toggleClass("collapsed");
    });

});
<|endoftext|>


<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is MiniTrayR extension
 *
 * The Initial Developer of the Original Code is
 * Nils Maier.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Nils Maier <MaierMan@web.de>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var gMinTrayR = {};
addEventListener(
  'load',
  function() {
    removeEventListener("load", arguments.callee, true);

    Components.utils.import("resource://mintrayr/mintrayr.jsm", gMinTrayR);
    gMinTrayR = new (function() {
      gMinTrayR.MinTrayR.call(this, document.getElementById('MinTrayR_context'), 'browser.watchbrowser');
      this.cloneToMenu('MinTrayR_sep-top', ['tasksMenuNavigator', 'tasksMenuMail'], false);
      this.cloneToMenu('MinTrayR_sep-bottom', ['menu_closeWindow', 'menu_FileQuitItem'], true);
    });
  },
  true
);
<commit_msg>Fix Seamonkey<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is MiniTrayR extension
 *
 * The Initial Developer of the Original Code is
 * Nils Maier.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Nils Maier <MaierMan@web.de>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var gMinTrayR = {};
addEventListener(
	"load",
	function() {
		removeEventListener("load", arguments.callee, true);

		Components.utils.import("resource://mintrayr/mintrayr.jsm", gMinTrayR);
		gMinTrayR = new gMinTrayR.MinTrayR(
			document.getElementById("MinTrayR_context"),
			"browser.watchbrowser",
			function() {
				this.cloneToMenu('MinTrayR_sep-top', ['tasksMenuNavigator', 'tasksMenuMail'], false);
				this.cloneToMenu('MinTrayR_sep-bottom', ['menu_closeWindow', 'menu_FileQuitItem'], true);
			});
	},
	true
);
<|endoftext|>


<commit_before>var abp = abp || {};
(function ($) {
    if (!sweetAlert || !$) {
        return;
    }

    /* DEFAULTS *************************************************/

    abp.libs = abp.libs || {};
    abp.libs.sweetAlert = {
        config: {
            'default': {

            },
            info: {
                icon: 'info'
            },
            success: {
                icon: 'success'
            },
            warn: {
                icon: 'warning'
            },
            error: {
                icon: 'error'
            },
            confirm: {
                icon: 'warning',
                title: 'Are you sure?',
                buttons: ['Cancel', 'Yes']
            }
        }
    };

    /* MESSAGE **************************************************/

    var showMessage = function (type, message, title) {
        if (!title) {
            title = message;
            message = undefined;
        }

        var opts = $.extend(
            {},
            abp.libs.sweetAlert.config['default'],
            abp.libs.sweetAlert.config[type],
            {
                title: title,
                text: message
            }
        );

        return $.Deferred(function ($dfd) {
            sweetAlert(opts).then(() => {
                $dfd.resolve();
            });
        });
    };

    abp.message.info = function (message, title) {
        return showMessage('info', message, title);
    };

    abp.message.success = function (message, title) {
        return showMessage('success', message, title);
    };

    abp.message.warn = function (message, title) {
        return showMessage('warn', message, title);
    };

    abp.message.error = function (message, title) {
        return showMessage('error', message, title);
    };

    abp.message.confirm = function (message, titleOrCallback, callback) {
        var userOpts = {
            text: message
        };

        if ($.isFunction(titleOrCallback)) {
            callback = titleOrCallback;
        } else if (titleOrCallback) {
            userOpts.title = titleOrCallback;
        };

        var opts = $.extend(
            {},
            abp.libs.sweetAlert.config['default'],
            abp.libs.sweetAlert.config.confirm,
            userOpts
        );

        return $.Deferred(function ($dfd) {
            sweetAlert(opts).then((isConfirmed) => {
                if (isConfirmed) {
                    callback();
                }
                $dfd.resolve(isConfirmed);
            });
        });
    };

    abp.event.on('abp.dynamicScriptsInitialized', function () {
        abp.libs.sweetAlert.config.confirm.title = abp.localization.abpWeb('AreYouSure');
        abp.libs.sweetAlert.config.confirm.buttons = [abp.localization.abpWeb('Cancel'), abp.localization.abpWeb('Yes')];
    });

})(jQuery);<commit_msg>Bug fix - sweet alert confirm button does not fire<commit_after>var abp = abp || {};
(function ($) {
    if (!sweetAlert || !$) {
        return;
    }

    /* DEFAULTS *************************************************/

    abp.libs = abp.libs || {};
    abp.libs.sweetAlert = {
        config: {
            'default': {

            },
            info: {
                icon: 'info'
            },
            success: {
                icon: 'success'
            },
            warn: {
                icon: 'warning'
            },
            error: {
                icon: 'error'
            },
            confirm: {
                icon: 'warning',
                title: 'Are you sure?',
                buttons: ['Cancel', 'Yes']
            }
        }
    };

    /* MESSAGE **************************************************/

    var showMessage = function (type, message, title) {
        if (!title) {
            title = message;
            message = undefined;
        }

        var opts = $.extend(
            {},
            abp.libs.sweetAlert.config['default'],
            abp.libs.sweetAlert.config[type],
            {
                title: title,
                text: message
            }
        );

        return $.Deferred(function ($dfd) {
            sweetAlert(opts).then(() => {
                $dfd.resolve();
            });
        });
    };

    abp.message.info = function (message, title) {
        return showMessage('info', message, title);
    };

    abp.message.success = function (message, title) {
        return showMessage('success', message, title);
    };

    abp.message.warn = function (message, title) {
        return showMessage('warn', message, title);
    };

    abp.message.error = function (message, title) {
        return showMessage('error', message, title);
    };

    abp.message.confirm = function (message, titleOrCallback, callback) {
        var userOpts = {
            text: message
        };

        if ($.isFunction(titleOrCallback)) {
            callback = titleOrCallback;
        } else if (titleOrCallback) {
            userOpts.title = titleOrCallback;
        };

        var opts = $.extend(
            {},
            abp.libs.sweetAlert.config['default'],
            abp.libs.sweetAlert.config.confirm,
            userOpts
        );

        return $.Deferred(function ($dfd) {
            sweetAlert(opts).then((isConfirmed) => {
                if (isConfirmed) {
                    callback && callback(isConfirmed);
                }
                $dfd.resolve(isConfirmed);
            });
        });
    };

    abp.event.on('abp.dynamicScriptsInitialized', function () {
        abp.libs.sweetAlert.config.confirm.title = abp.localization.abpWeb('AreYouSure');
        abp.libs.sweetAlert.config.confirm.buttons = [abp.localization.abpWeb('Cancel'), abp.localization.abpWeb('Yes')];
    });

})(jQuery);<|endoftext|>


<commit_before>import buildUrl from 'build-url';
import moment from 'moment';

import { BaseScraperWithBrowser, LOGIN_RESULT } from './base-scraper-with-browser';
import { waitForNavigationAndDomLoad, waitForRedirect } from '../helpers/navigation';
import { waitUntilElementFound, elementPresentOnPage, clickButton } from '../helpers/elements-interactions';
import {
  NORMAL_TXN_TYPE,
  INSTALLMENTS_TXN_TYPE,
  SHEKEL_CURRENCY_SYMBOL,
  SHEKEL_CURRENCY,
  TRANSACTION_STATUS,
} from '../constants';
import getAllMonthMoments from '../helpers/dates';
import { fixInstallments, sortTransactionsByDate, filterOldTransactions } from '../helpers/transactions';

const BASE_ACTIONS_URL = 'https://online.max.co.il';
const BASE_WELCOME_URL = 'https://www.max.co.il';
const DATE_FORMAT = 'DD/MM/YYYY';
const NORMAL_TYPE_NAME = 'רגילה';
const ATM_TYPE_NAME = 'חיוב עסקות מיידי';
const INTERNET_SHOPPING_TYPE_NAME = 'אינטרנט/חו"ל';
const INSTALLMENTS_TYPE_NAME = 'תשלומים';
const MONTHLY_CHARGE_TYPE_NAME = 'חיוב חודשי';
const ONE_MONTH_POSTPONED_TYPE_NAME = 'דחוי חודש';
const MONTHLY_POSTPONED_TYPE_NAME = 'דחוי לחיוב החודשי';
const THIRTY_DAYS_PLUS_TYPE_NAME = 'עסקת 30 פלוס';
const TWO_MONTHS_POSTPONED_TYPE_NAME = 'דחוי חודשיים';
const MONTHLY_CHARGE_PLUS_INTEREST_TYPE_NAME = 'חודשי + ריבית';
const CREDIT_TYPE_NAME = 'קרדיט';

function redirectOrDialog(page) {
  return Promise.race([
    waitForRedirect(page, 20000, false, [BASE_WELCOME_URL, `${BASE_WELCOME_URL}/`]),
    waitUntilElementFound(page, '#popupWrongDetails', true),
  ]);
}

function getTransactionsUrl(monthMoment) {
  let monthCharge = null;
  let actionType = 1;
  if (monthMoment) {
    const month = monthMoment.month() + 1;
    const monthStr = month < 10 ? `0${month}` : month.toString();
    const year = monthMoment.year();
    monthCharge = `${year}${monthStr}`;
    actionType = 2;
  }
  return buildUrl(BASE_ACTIONS_URL, {
    path: 'Registred/Transactions/ChargesDeals.aspx',
    queryParams: {
      ActionType: actionType,
      MonthCharge: monthCharge,
      Index: -2,
    },
  });
}

function getTransactionType(txnTypeStr) {
  const cleanedUpTxnTypeStr = txnTypeStr.replace('\t', ' ').trim();
  switch (cleanedUpTxnTypeStr) {
    case ATM_TYPE_NAME:
    case NORMAL_TYPE_NAME:
    case MONTHLY_CHARGE_TYPE_NAME:
    case ONE_MONTH_POSTPONED_TYPE_NAME:
    case MONTHLY_POSTPONED_TYPE_NAME:
    case THIRTY_DAYS_PLUS_TYPE_NAME:
    case TWO_MONTHS_POSTPONED_TYPE_NAME:
    case INTERNET_SHOPPING_TYPE_NAME:
    case MONTHLY_CHARGE_PLUS_INTEREST_TYPE_NAME:
      return NORMAL_TXN_TYPE;
    case INSTALLMENTS_TYPE_NAME:
    case CREDIT_TYPE_NAME:
      return INSTALLMENTS_TXN_TYPE;
    default:
      throw new Error(`Unknown transaction type ${cleanedUpTxnTypeStr}`);
  }
}

function getAmountData(amountStr) {
  const amountStrCopy = amountStr.replace(',', '');
  let currency = null;
  let amount = null;
  if (amountStrCopy.includes(SHEKEL_CURRENCY_SYMBOL)) {
    amount = parseFloat(amountStrCopy.replace(SHEKEL_CURRENCY_SYMBOL, ''));
    currency = SHEKEL_CURRENCY;
  } else {
    const parts = amountStrCopy.split(' ');
    amount = parseFloat(parts[0]);
    [, currency] = parts;
  }

  return {
    amount,
    currency,
  };
}

function getInstallmentsInfo(comments) {
  if (!comments) {
    return null;
  }
  const matches = comments.match(/\d+/g);
  if (!matches || matches.length < 2) {
    return null;
  }

  return {
    number: parseInt(matches[0], 10),
    total: parseInt(matches[1], 10),
  };
}

function convertTransactions(rawTxns) {
  return rawTxns.map((txn) => {
    const originalAmountData = getAmountData(txn.originalAmountStr);
    const chargedAmountData = getAmountData(txn.chargedAmountStr);
    return {
      type: getTransactionType(txn.typeStr),
      date: moment(txn.dateStr, DATE_FORMAT).toISOString(),
      processedDate: moment(txn.processedDateStr, DATE_FORMAT).toISOString(),
      originalAmount: -originalAmountData.amount,
      originalCurrency: originalAmountData.currency,
      chargedAmount: -chargedAmountData.amount,
      description: txn.description.trim(),
      memo: txn.comments,
      installments: getInstallmentsInfo(txn.comments),
      status: TRANSACTION_STATUS.COMPLETED,
    };
  });
}

async function getCardContainers(page) {
  return page.$$('.infoList_holder');
}

async function getCardContainer(page, cardIndex) {
  const cardContainers = await getCardContainers(page);
  const cardContainer = cardContainers[cardIndex];
  return cardContainer;
}

async function getCardSections(page, cardIndex) {
  const cardContainer = await getCardContainer(page, cardIndex);
  const cardSections = await cardContainer.$$('.NotPaddingTable');
  return cardSections;
}

async function getAccountNumber(page, cardIndex) {
  const cardContainer = await getCardContainer(page, cardIndex);
  const infoContainer = await cardContainer.$('.creditCard_name');
  const numberListItems = await infoContainer.$$('li');
  const numberListItem = numberListItems[1];
  const accountNumberStr = await page.evaluate((li) => {
    return li.innerText;
  }, numberListItem);
  const accountNumber = accountNumberStr.replace('(', '').replace(')', '');

  return accountNumber;
}

async function getTransactionsForSection(page, cardIndex, sectionIndex) {
  const cardSections = await getCardSections(page, cardIndex);
  const txnsRows = await cardSections[sectionIndex].$$('.jobs_regular');
  const expandedBusinessesNamesHeaders = await cardSections[sectionIndex].$$('.openedJob .bisName');
  let expandedBusinessesNames = await Promise.all(expandedBusinessesNamesHeaders.map(
    header => page.evaluate(y => y.innerText, header),
  ),
  expandedBusinessesNamesHeaders);

  // Leumicard keeps hidden open transactions without any content, filter them out
  expandedBusinessesNames = expandedBusinessesNames.filter(x => !!x);

  const txns = [];
  for (let txnIndex = 0; txnIndex < txnsRows.length; txnIndex += 1) {
    const txnColumns = await txnsRows[txnIndex].$$('td');

    const typeStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[4]);

    const dateStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[1]);

    const processedDateStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[2]);

    const originalAmountStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[5]);

    const chargedAmountStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[6]);

    // take the description from the hidden expanded info-card, as the one in the table is truncated
    const description = expandedBusinessesNames[txnIndex].replace(/\s+/g, ' ');

    const comments = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[7]);

    const txn = {
      typeStr,
      dateStr,
      processedDateStr,
      originalAmountStr,
      chargedAmountStr,
      description,
      comments,
    };
    txns.push(txn);
  }

  return txns;
}

async function getNextPageButtonForSection(page, cardIndex, sectionIndex) {
  const cardSections = await getCardSections(page, cardIndex);
  return cardSections[sectionIndex].$('.difdufLeft a');
}

async function getCurrentTransactions(page) {
  const result = {};
  const cardContainers = await getCardContainers(page);

  for (let cardIndex = 0; cardIndex < cardContainers.length; cardIndex += 1) {
    const txns = [];
    const cardSections = await getCardSections(page, cardIndex);
    for (let sectionIndex = 0; sectionIndex < cardSections.length; sectionIndex += 1) {
      let hasNext = true;
      while (hasNext) {
        const sectionTxns = await getTransactionsForSection(page, cardIndex, sectionIndex);
        txns.push(...sectionTxns);

        const nextPageBtn = await getNextPageButtonForSection(page, cardIndex, sectionIndex);
        if (nextPageBtn) {
          await nextPageBtn.click();
          await waitForNavigationAndDomLoad(page);
        } else {
          hasNext = false;
        }
      }
    }

    const accountNumber = await getAccountNumber(page, cardIndex);
    result[accountNumber] = convertTransactions(txns);
  }

  return result;
}

async function fetchTransactionsForMonth(browser, navigateToFunc, monthMoment) {
  const page = await browser.newPage();

  const url = getTransactionsUrl(monthMoment);
  await navigateToFunc(url, page);

  if (page.url() !== url) {
    throw new Error(`Error while trying to navigate to url ${url}`);
  }

  const txns = await getCurrentTransactions(page);
  await page.close();

  return txns;
}

function addResult(allResults, result) {
  const clonedResults = Object.assign({}, allResults);
  Object.keys(result).forEach((accountNumber) => {
    if (!clonedResults[accountNumber]) {
      clonedResults[accountNumber] = [];
    }
    clonedResults[accountNumber].push(...result[accountNumber]);
  });
  return clonedResults;
}

function prepareTransactions(txns, startMoment, combineInstallments) {
  let clonedTxns = Array.from(txns);
  if (!combineInstallments) {
    clonedTxns = fixInstallments(clonedTxns);
  }
  clonedTxns = sortTransactionsByDate(clonedTxns);
  clonedTxns = filterOldTransactions(clonedTxns, startMoment, combineInstallments);
  return clonedTxns;
}

async function fetchTransactions(browser, options, navigateToFunc) {
  const defaultStartMoment = moment().subtract(1, 'years');
  const startDate = options.startDate || defaultStartMoment.toDate();
  const startMoment = moment.max(defaultStartMoment, moment(startDate));
  const allMonths = getAllMonthMoments(startMoment, false);

  let allResults = {};
  for (let i = 0; i < allMonths.length; i += 1) {
    const result = await fetchTransactionsForMonth(browser, navigateToFunc, allMonths[i]);
    allResults = addResult(allResults, result);
  }

  const currentMonthResult = await fetchTransactionsForMonth(browser, navigateToFunc);
  allResults = addResult(allResults, currentMonthResult);

  Object.keys(allResults).forEach((accountNumber) => {
    let txns = allResults[accountNumber];
    txns = prepareTransactions(txns, startMoment, options.combineInstallments);
    allResults[accountNumber] = txns;
  });

  return allResults;
}

function getPossibleLoginResults() {
  const urls = {};
  urls[LOGIN_RESULT.SUCCESS] = [`${BASE_WELCOME_URL}/homepage/personal`];
  urls[LOGIN_RESULT.CHANGE_PASSWORD] = [`${BASE_ACTIONS_URL}/Anonymous/Login/PasswordExpired.aspx`];
  urls[LOGIN_RESULT.INVALID_PASSWORD] = [`${BASE_ACTIONS_URL}/Anonymous/Login/CardHoldersLogin.aspx`];
  return urls;
}

function createLoginFields(inputGroupName, credentials) {
  return [
    { selector: `#${inputGroupName}_txtUserName`, value: credentials.username },
    { selector: '#txtPassword', value: credentials.password },
  ];
}

class LeumiCardScraper extends BaseScraperWithBrowser {
  getLoginOptions(credentials) {
    const inputGroupName = 'PlaceHolderMain_CardHoldersLogin1';
    return {
      loginUrl: `${BASE_ACTIONS_URL}/Anonymous/Login/CardholdersLogin.aspx`,
      fields: createLoginFields(inputGroupName, credentials),
      submitButtonSelector: `#${inputGroupName}_btnLogin`,
      preAction: async () => {
        if (await elementPresentOnPage(this.page, '#closePopup')) {
          await clickButton(this.page, '#closePopup');
        }
      },
      postAction: async () => redirectOrDialog(this.page),
      possibleResults: getPossibleLoginResults(),
    };
  }

  async fetchData() {
    const results = await fetchTransactions(this.browser, this.options, this.navigateTo);
    const accounts = Object.keys(results).map((accountNumber) => {
      return {
        accountNumber,
        txns: results[accountNumber],
      };
    });

    return {
      success: true,
      accounts,
    };
  }
}

export default LeumiCardScraper;
<commit_msg>Fixed PR style formatting notes<commit_after>import buildUrl from 'build-url';
import moment from 'moment';

import { BaseScraperWithBrowser, LOGIN_RESULT } from './base-scraper-with-browser';
import { waitForNavigationAndDomLoad, waitForRedirect } from '../helpers/navigation';
import { waitUntilElementFound, elementPresentOnPage, clickButton } from '../helpers/elements-interactions';
import {
  NORMAL_TXN_TYPE,
  INSTALLMENTS_TXN_TYPE,
  SHEKEL_CURRENCY_SYMBOL,
  SHEKEL_CURRENCY,
  TRANSACTION_STATUS,
} from '../constants';
import getAllMonthMoments from '../helpers/dates';
import { fixInstallments, sortTransactionsByDate, filterOldTransactions } from '../helpers/transactions';

const BASE_ACTIONS_URL = 'https://online.max.co.il';
const BASE_WELCOME_URL = 'https://www.max.co.il';
const DATE_FORMAT = 'DD/MM/YYYY';
const NORMAL_TYPE_NAME = 'רגילה';
const ATM_TYPE_NAME = 'חיוב עסקות מיידי';
const INTERNET_SHOPPING_TYPE_NAME = 'אינטרנט/חו"ל';
const INSTALLMENTS_TYPE_NAME = 'תשלומים';
const MONTHLY_CHARGE_TYPE_NAME = 'חיוב חודשי';
const ONE_MONTH_POSTPONED_TYPE_NAME = 'דחוי חודש';
const MONTHLY_POSTPONED_TYPE_NAME = 'דחוי לחיוב החודשי';
const THIRTY_DAYS_PLUS_TYPE_NAME = 'עסקת 30 פלוס';
const TWO_MONTHS_POSTPONED_TYPE_NAME = 'דחוי חודשיים';
const MONTHLY_CHARGE_PLUS_INTEREST_TYPE_NAME = 'חודשי + ריבית';
const CREDIT_TYPE_NAME = 'קרדיט';

function redirectOrDialog(page) {
  return Promise.race([
    waitForRedirect(page, 20000, false, [BASE_WELCOME_URL, `${BASE_WELCOME_URL}/`]),
    waitUntilElementFound(page, '#popupWrongDetails', true),
  ]);
}

function getTransactionsUrl(monthMoment) {
  let monthCharge = null;
  let actionType = 1;
  if (monthMoment) {
    const month = monthMoment.month() + 1;
    const monthStr = month < 10 ? `0${month}` : month.toString();
    const year = monthMoment.year();
    monthCharge = `${year}${monthStr}`;
    actionType = 2;
  }
  return buildUrl(BASE_ACTIONS_URL, {
    path: 'Registred/Transactions/ChargesDeals.aspx',
    queryParams: {
      ActionType: actionType,
      MonthCharge: monthCharge,
      Index: -2,
    },
  });
}

function getTransactionType(txnTypeStr) {
  const cleanedUpTxnTypeStr = txnTypeStr.replace('\t', ' ').trim();
  switch (cleanedUpTxnTypeStr) {
    case ATM_TYPE_NAME:
    case NORMAL_TYPE_NAME:
    case MONTHLY_CHARGE_TYPE_NAME:
    case ONE_MONTH_POSTPONED_TYPE_NAME:
    case MONTHLY_POSTPONED_TYPE_NAME:
    case THIRTY_DAYS_PLUS_TYPE_NAME:
    case TWO_MONTHS_POSTPONED_TYPE_NAME:
    case INTERNET_SHOPPING_TYPE_NAME:
    case MONTHLY_CHARGE_PLUS_INTEREST_TYPE_NAME:
      return NORMAL_TXN_TYPE;
    case INSTALLMENTS_TYPE_NAME:
    case CREDIT_TYPE_NAME:
      return INSTALLMENTS_TXN_TYPE;
    default:
      throw new Error(`Unknown transaction type ${cleanedUpTxnTypeStr}`);
  }
}

function getAmountData(amountStr) {
  const amountStrCopy = amountStr.replace(',', '');
  let currency = null;
  let amount = null;
  if (amountStrCopy.includes(SHEKEL_CURRENCY_SYMBOL)) {
    amount = parseFloat(amountStrCopy.replace(SHEKEL_CURRENCY_SYMBOL, ''));
    currency = SHEKEL_CURRENCY;
  } else {
    const parts = amountStrCopy.split(' ');
    amount = parseFloat(parts[0]);
    [, currency] = parts;
  }

  return {
    amount,
    currency,
  };
}

function getInstallmentsInfo(comments) {
  if (!comments) {
    return null;
  }
  const matches = comments.match(/\d+/g);
  if (!matches || matches.length < 2) {
    return null;
  }

  return {
    number: parseInt(matches[0], 10),
    total: parseInt(matches[1], 10),
  };
}

function convertTransactions(rawTxns) {
  return rawTxns.map((txn) => {
    const originalAmountData = getAmountData(txn.originalAmountStr);
    const chargedAmountData = getAmountData(txn.chargedAmountStr);
    return {
      type: getTransactionType(txn.typeStr),
      date: moment(txn.dateStr, DATE_FORMAT).toISOString(),
      processedDate: moment(txn.processedDateStr, DATE_FORMAT).toISOString(),
      originalAmount: -originalAmountData.amount,
      originalCurrency: originalAmountData.currency,
      chargedAmount: -chargedAmountData.amount,
      description: txn.description.trim(),
      memo: txn.comments,
      installments: getInstallmentsInfo(txn.comments),
      status: TRANSACTION_STATUS.COMPLETED,
    };
  });
}

async function getCardContainers(page) {
  return page.$$('.infoList_holder');
}

async function getCardContainer(page, cardIndex) {
  const cardContainers = await getCardContainers(page);
  const cardContainer = cardContainers[cardIndex];
  return cardContainer;
}

async function getCardSections(page, cardIndex) {
  const cardContainer = await getCardContainer(page, cardIndex);
  const cardSections = await cardContainer.$$('.NotPaddingTable');
  return cardSections;
}

async function getAccountNumber(page, cardIndex) {
  const cardContainer = await getCardContainer(page, cardIndex);
  const infoContainer = await cardContainer.$('.creditCard_name');
  const numberListItems = await infoContainer.$$('li');
  const numberListItem = numberListItems[1];
  const accountNumberStr = await page.evaluate((li) => {
    return li.innerText;
  }, numberListItem);
  const accountNumber = accountNumberStr.replace('(', '').replace(')', '');

  return accountNumber;
}

async function getTransactionsForSection(page, cardIndex, sectionIndex) {
  const cardSections = await getCardSections(page, cardIndex);
  const txnsRows = await cardSections[sectionIndex].$$('.jobs_regular');
  const expandedBusinessesNamesHeaders = await cardSections[sectionIndex].$$('.openedJob .bisName');
  let expandedBusinessesNames = await Promise.all(expandedBusinessesNamesHeaders.map(
    header => page.evaluate(x => x.innerText, header),
  ), expandedBusinessesNamesHeaders);

  // Leumicard keeps hidden open transactions without any content, filter them out
  expandedBusinessesNames = expandedBusinessesNames.filter(x => !!x);

  const txns = [];
  for (let txnIndex = 0; txnIndex < txnsRows.length; txnIndex += 1) {
    const txnColumns = await txnsRows[txnIndex].$$('td');

    const typeStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[4]);

    const dateStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[1]);

    const processedDateStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[2]);

    const originalAmountStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[5]);

    const chargedAmountStr = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[6]);

    const description = expandedBusinessesNames[txnIndex].replace(/\s+/g, ' ');

    const comments = await page.evaluate((td) => {
      return td.innerText;
    }, txnColumns[7]);

    const txn = {
      typeStr,
      dateStr,
      processedDateStr,
      originalAmountStr,
      chargedAmountStr,
      description,
      comments,
    };
    txns.push(txn);
  }

  return txns;
}

async function getNextPageButtonForSection(page, cardIndex, sectionIndex) {
  const cardSections = await getCardSections(page, cardIndex);
  return cardSections[sectionIndex].$('.difdufLeft a');
}

async function getCurrentTransactions(page) {
  const result = {};
  const cardContainers = await getCardContainers(page);

  for (let cardIndex = 0; cardIndex < cardContainers.length; cardIndex += 1) {
    const txns = [];
    const cardSections = await getCardSections(page, cardIndex);
    for (let sectionIndex = 0; sectionIndex < cardSections.length; sectionIndex += 1) {
      let hasNext = true;
      while (hasNext) {
        const sectionTxns = await getTransactionsForSection(page, cardIndex, sectionIndex);
        txns.push(...sectionTxns);

        const nextPageBtn = await getNextPageButtonForSection(page, cardIndex, sectionIndex);
        if (nextPageBtn) {
          await nextPageBtn.click();
          await waitForNavigationAndDomLoad(page);
        } else {
          hasNext = false;
        }
      }
    }

    const accountNumber = await getAccountNumber(page, cardIndex);
    result[accountNumber] = convertTransactions(txns);
  }

  return result;
}

async function fetchTransactionsForMonth(browser, navigateToFunc, monthMoment) {
  const page = await browser.newPage();

  const url = getTransactionsUrl(monthMoment);
  await navigateToFunc(url, page);

  if (page.url() !== url) {
    throw new Error(`Error while trying to navigate to url ${url}`);
  }

  const txns = await getCurrentTransactions(page);
  await page.close();

  return txns;
}

function addResult(allResults, result) {
  const clonedResults = Object.assign({}, allResults);
  Object.keys(result).forEach((accountNumber) => {
    if (!clonedResults[accountNumber]) {
      clonedResults[accountNumber] = [];
    }
    clonedResults[accountNumber].push(...result[accountNumber]);
  });
  return clonedResults;
}

function prepareTransactions(txns, startMoment, combineInstallments) {
  let clonedTxns = Array.from(txns);
  if (!combineInstallments) {
    clonedTxns = fixInstallments(clonedTxns);
  }
  clonedTxns = sortTransactionsByDate(clonedTxns);
  clonedTxns = filterOldTransactions(clonedTxns, startMoment, combineInstallments);
  return clonedTxns;
}

async function fetchTransactions(browser, options, navigateToFunc) {
  const defaultStartMoment = moment().subtract(1, 'years');
  const startDate = options.startDate || defaultStartMoment.toDate();
  const startMoment = moment.max(defaultStartMoment, moment(startDate));
  const allMonths = getAllMonthMoments(startMoment, false);

  let allResults = {};
  for (let i = 0; i < allMonths.length; i += 1) {
    const result = await fetchTransactionsForMonth(browser, navigateToFunc, allMonths[i]);
    allResults = addResult(allResults, result);
  }

  const currentMonthResult = await fetchTransactionsForMonth(browser, navigateToFunc);
  allResults = addResult(allResults, currentMonthResult);

  Object.keys(allResults).forEach((accountNumber) => {
    let txns = allResults[accountNumber];
    txns = prepareTransactions(txns, startMoment, options.combineInstallments);
    allResults[accountNumber] = txns;
  });

  return allResults;
}

function getPossibleLoginResults() {
  const urls = {};
  urls[LOGIN_RESULT.SUCCESS] = [`${BASE_WELCOME_URL}/homepage/personal`];
  urls[LOGIN_RESULT.CHANGE_PASSWORD] = [`${BASE_ACTIONS_URL}/Anonymous/Login/PasswordExpired.aspx`];
  urls[LOGIN_RESULT.INVALID_PASSWORD] = [`${BASE_ACTIONS_URL}/Anonymous/Login/CardHoldersLogin.aspx`];
  return urls;
}

function createLoginFields(inputGroupName, credentials) {
  return [
    { selector: `#${inputGroupName}_txtUserName`, value: credentials.username },
    { selector: '#txtPassword', value: credentials.password },
  ];
}

class LeumiCardScraper extends BaseScraperWithBrowser {
  getLoginOptions(credentials) {
    const inputGroupName = 'PlaceHolderMain_CardHoldersLogin1';
    return {
      loginUrl: `${BASE_ACTIONS_URL}/Anonymous/Login/CardholdersLogin.aspx`,
      fields: createLoginFields(inputGroupName, credentials),
      submitButtonSelector: `#${inputGroupName}_btnLogin`,
      preAction: async () => {
        if (await elementPresentOnPage(this.page, '#closePopup')) {
          await clickButton(this.page, '#closePopup');
        }
      },
      postAction: async () => redirectOrDialog(this.page),
      possibleResults: getPossibleLoginResults(),
    };
  }

  async fetchData() {
    const results = await fetchTransactions(this.browser, this.options, this.navigateTo);
    const accounts = Object.keys(results).map((accountNumber) => {
      return {
        accountNumber,
        txns: results[accountNumber],
      };
    });

    return {
      success: true,
      accounts,
    };
  }
}

export default LeumiCardScraper;
<|endoftext|>


<commit_before>import Axios from '../../config/axios';

const helpers = {
  path(annotation) {
    return `/resources/${annotation.resource_id}/annotations/${annotation.id}`;
  }
};

const state = {
  all: window.STATE_BOOTSTRAP ? window.STATE_BOOTSTRAP.annotations || [] : []
};

const getters = {
  getById: (state) => (id) => {
    return state.all.find(obj => obj.id === id);
  }
};

const actions = {
  update({ commit }, payload) {
    Axios
      .patch(helpers.path(payload.obj), {annotation: payload.vals})
      .then(resp => {
        commit('update', payload);
        window.location.reload();
      });
  },
  destroy({ commit }, payload) {
    Axios
      .delete(helpers.path(payload))
      .then(resp => {
        // commit('destroy', payload);
        window.location.reload();
      });
  }
};

const mutations = {
  update(state, payload) {
    Object.assign(payload.obj, payload.vals);
  },
  destroy(state, payload) {
    state.all.splice(state.all.indexOf(payload), 1);
  }
};

export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
};
<commit_msg>Updates no longer require a refresh<commit_after>import Axios from '../../config/axios';

const helpers = {
  path(annotation) {
    return `/resources/${annotation.resource_id}/annotations/${annotation.id}`;
  }
};

const state = {
  all: window.STATE_BOOTSTRAP ? window.STATE_BOOTSTRAP.annotations || [] : []
};

const getters = {
  getById: (state) => (id) => {
    return state.all.find(obj => obj.id === id);
  }
};

const actions = {
  update({ commit }, payload) {
    Axios
      .patch(helpers.path(payload.obj), {annotation: payload.vals})
      .then(resp => {
        commit('update', payload);
      });
  },
  destroy({ commit }, payload) {
    Axios
      .delete(helpers.path(payload))
      .then(resp => {
        // commit('destroy', payload);
        window.location.reload();
      });
  }
};

const mutations = {
  update(state, payload) {
    Object.assign(payload.obj, payload.vals);
  },
  destroy(state, payload) {
    state.all.splice(state.all.indexOf(payload), 1);
  }
};

export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
};
<|endoftext|>


<commit_before>/*
---

name: Image

description: Provides a control that display an image.

license: MIT-style license.

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

requires:
	- Control

provides:
	- Image

...
*/

/**
 * @see    http://moobilejs.com/doc/latest/Control/Image
 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
 * @edited 0.2.0
 * @since  0.1.0
 */
Moobile.Image = new Class({

	Extends: Moobile.Control,

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_image: null,

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_source: null,

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_loaded: false,

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_originalSize: {
		x: 0,
		y: 0
	},

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	options: {
		tagName: 'img',
		preload: false
	},

	/**
	 * @overridden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @edited 0.2.0
	 * @since  0.1.0
	 */
	willBuild: function() {

		this.parent();

		this.hide();
		this.addClass('image');

		var source = this.element.get('src');
		if (source) {
			this.setSource(source);
		}
	},

	/**
	 * @overridden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	destroy: function() {
		this._image = null;
		this.parent();
	},

	/**
	 * @see    http://moobilejs.com/doc/latest/Control/Image#setSource
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	setSource: function(source) {

		this._source = source || '';

		if (this._image) {
			this._image.removeEvent('load', this.bound('_onLoad'));
			this._image = null;
		}

		if (source) {
			if (this.options.preload) {
				this._loaded = false;
				this._image = new Image();
				this._image.src = source;
				this._image.addEvent('load', this.bound('_onLoad'));
			} else {
				this._load();
			}
		} else {
			this._unload();
		}

		return this;
	},

	/**
	 * @see    http://moobilejs.com/doc/latest/Control/Image#getSource
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	getSource: function() {
		return this._source;
	},

	/**
	 * @see    http://moobilejs.com/doc/latest/Control/Image#getImage
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	getImage: function() {
		return this._image;
	},

	/**
	 * @see    http://moobilejs.com/doc/latest/Control/Image#getOriginalSize
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	getOriginalSize: function() {
		return this._originalSize;
	},

	/**
	 * @see    http://moobilejs.com/doc/latest/Control/Image#isLoaded
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	isLoaded: function() {
		return this._loaded;
	},

	/**
	 * @see    http://moobilejs.com/doc/latest/Control/Image#isEmpty
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.2.0
	 */
	isEmpty: function() {
		return !this.getSource();
	},

	/**
	 * @overridden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.2.0
	 */
	show: function() {
		return this.isEmpty() ? this : this.parent();
	},

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_load: function() {

		this._loaded = true;

		if (this.options.preload) {
			this._originalSize.x = this._image.width;
			this._originalSize.y = this._image.height;
		}

		this.element.set('src', this._source);
		this.show();
	},

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_unload: function() {

		this._loaded = false;

		if (this.options.preload) {
			this._originalSize.x = 0;
			this._originalSize.y = 0;
		}

		this.element.erase('src');
		this.fireEvent('unload');
		this.hide();
	},

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_onLoad: function() {
		this.fireEvent('preload');
		this._load();
	}

});

/**
 * @see    http://moobilejs.com/doc/latest/Control/Image#from
 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
 * @since  0.2.0
 */
Moobile.Image.from = function(source) {
	if (source instanceof Moobile.Image) return source;
	var image = new Moobile.Image();
	image.setSource(source);
	return image;
};

//------------------------------------------------------------------------------
// Roles
//------------------------------------------------------------------------------

Moobile.Component.defineRole('image', null, null, function(element) {
	this.addChildComponent(Moobile.Component.create(Moobile.Image, element, 'data-image'));
});<commit_msg>Properly unloading images<commit_after>/*
---

name: Image

description: Provides a control that display an image.

license: MIT-style license.

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

requires:
	- Control

provides:
	- Image

...
*/

/**
 * @see    http://moobilejs.com/doc/latest/Control/Image
 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
 * @edited 0.2.0
 * @since  0.1.0
 */
Moobile.Image = new Class({

	Extends: Moobile.Control,

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_image: null,

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_source: null,

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_loaded: false,

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_originalSize: {
		x: 0,
		y: 0
	},

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	options: {
		tagName: 'img',
		preload: false
	},

	/**
	 * @overridden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @edited 0.2.0
	 * @since  0.1.0
	 */
	willBuild: function() {

		this.parent();

		this.hide();
		this.addClass('image');

		var source = this.element.get('src');
		if (source) {
			this.setSource(source);
		}
	},

	/**
	 * @overridden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	destroy: function() {

		if (this._image) {
			this._image.removeEvent('load', this.bound('_onLoad'));
			this._image.src = 'data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
			this._image = null;
		}

		this.element.set('src', 'data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');

		this.parent();
	},

	/**
	 * @see    http://moobilejs.com/doc/latest/Control/Image#setSource
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	setSource: function(source) {

		this._source = source || '';

		if (this._image) {
			this._image.removeEvent('load', this.bound('_onLoad'));
			this._image = null;
		}

		if (source) {
			if (this.options.preload) {
				this._loaded = false;
				this._image = new Image();
				this._image.src = source;
				this._image.addEvent('load', this.bound('_onLoad'));
			} else {
				this._load();
			}
		} else {
			this._unload();
		}

		return this;
	},

	/**
	 * @see    http://moobilejs.com/doc/latest/Control/Image#getSource
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	getSource: function() {
		return this._source;
	},

	/**
	 * @see    http://moobilejs.com/doc/latest/Control/Image#getImage
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	getImage: function() {
		return this._image;
	},

	/**
	 * @see    http://moobilejs.com/doc/latest/Control/Image#getOriginalSize
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	getOriginalSize: function() {
		return this._originalSize;
	},

	/**
	 * @see    http://moobilejs.com/doc/latest/Control/Image#isLoaded
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	isLoaded: function() {
		return this._loaded;
	},

	/**
	 * @see    http://moobilejs.com/doc/latest/Control/Image#isEmpty
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.2.0
	 */
	isEmpty: function() {
		return !this.getSource();
	},

	/**
	 * @overridden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.2.0
	 */
	show: function() {
		return this.isEmpty() ? this : this.parent();
	},

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_load: function() {

		this._loaded = true;

		if (this.options.preload) {
			this._originalSize.x = this._image.width;
			this._originalSize.y = this._image.height;
		}

		this.element.set('src', this._source);
		this.show();
	},

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_unload: function() {

		this._loaded = false;

		if (this.options.preload) {
			this._originalSize.x = 0;
			this._originalSize.y = 0;
		}

		this.element.erase('src');
		this.fireEvent('unload');
		this.hide();
	},

	/**
	 * @hidden
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1.0
	 */
	_onLoad: function() {
		this.fireEvent('preload');
		this._load();
	}

});

/**
 * @see    http://moobilejs.com/doc/latest/Control/Image#from
 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
 * @since  0.2.0
 */
Moobile.Image.from = function(source) {
	if (source instanceof Moobile.Image) return source;
	var image = new Moobile.Image();
	image.setSource(source);
	return image;
};

//------------------------------------------------------------------------------
// Roles
//------------------------------------------------------------------------------

Moobile.Component.defineRole('image', null, null, function(element) {
	this.addChildComponent(Moobile.Component.create(Moobile.Image, element, 'data-image'));
});<|endoftext|>


<commit_before>import CompositeClosureHelper from '../../../Common/Core/CompositeClosureHelper';
import dataHelper from './dataHelper';

// ----------------------------------------------------------------------------
// Selection Provider
// ----------------------------------------------------------------------------

function selectionProvider(publicAPI, model) {
  const dataSubscriptions = [];

  if (!model.selectionData) {
    model.selectionData = {};
  }
  if (!model.selectionMetaData) {
    model.selectionMetaData = {};
  }

  function off() {
    let count = dataSubscriptions.length;
    while (count--) {
      dataSubscriptions[count] = null;
    }
  }

  function flushDataToListener(dataListener) {
    try {
      if (dataListener) {
        const event = dataHelper.getNotificationData(model.selectionData, dataListener.request);
        if (event) {
          dataListener.onDataReady(event);
        }
      }
    } catch (err) {
      console.log('flushDataToListener error caught:', err);
    }
  }

  // Method use to store received data
  publicAPI.setSelectionData = data => {
    dataHelper.set(model.selectionData, data);

    // Process all subscription to see if we can trigger a notification
    dataSubscriptions.forEach(flushDataToListener);
  };

  // Method use to access cached data. Will return undefined if not available
  publicAPI.getSelectionData = query => dataHelper.get(model.selectionData, query);

  // Use to extend data subscription
  publicAPI.updateSelectionMetadata = addon => {
    model.selectionMetaData[addon.type] = Object.assign({}, model.selectionMetaData[addon.type], addon.metadata);
  };

  // Get metadata for a given data type
  publicAPI.getSelectionMetadata = type => model.selectionMetaData[type];

  // --------------------------------

  publicAPI.setSelection = (selection) => {
    model.selection = selection;
    publicAPI.fireSelectionChange(selection);
  };

  // --------------------------------

  // annotation = {
  //    selection: {...},
  //    score: [0],
  //    weight: 1,
  //    rationale: 'why not...',
  // }

  publicAPI.setAnnotation = (annotation) => {
    model.annotation = annotation;
    if (annotation.selection) {
      publicAPI.setSelection(annotation.selection);
    } else {
      annotation.selection = model.selection;
    }
    model.shouldCreateNewAnnotation = false;
    publicAPI.fireAnnotationChange(annotation);
  };

  // --------------------------------

  publicAPI.shouldCreateNewAnnotation = () => model.shouldCreateNewAnnotation;
  publicAPI.setCreateNewAnnotationFlag = (shouldCreate) => (model.shouldCreateNewAnnotation = shouldCreate);

  // --------------------------------
  // When a new selection is made, data dependent on that selection will be pushed
  // to subscribers.
  // A subscriber should save the return value and call update() when they need to
  // change the variables or meta data which is pushed to them.
  publicAPI.subscribeToDataSelection = (type, onDataReady, variables = [], metadata = {}) => {
    const id = dataSubscriptions.length;
    const request = { id, type, variables, metadata };
    const dataListener = { onDataReady, request };
    dataSubscriptions.push(dataListener);
    publicAPI.fireDataSubscriptionChange(request);
    flushDataToListener(dataListener);
    return {
      unsubscribe() {
        request.action = 'unsubscribe';
        publicAPI.fireDataSubscriptionChange(request);
        dataSubscriptions[id] = null;
      },
      update(vars, meta) {
        request.variables = [].concat(vars);
        request.metadata = Object.assign({}, request.metadata, meta);
        publicAPI.fireDataSubscriptionChange(request);
        flushDataToListener(dataListener);
      },
    };
  };

  publicAPI.destroy = CompositeClosureHelper.chain(off, publicAPI.destroy);
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  // selection: null,
  // selectionData: null,
  // selectionMetaData: null,
  shouldCreateNewAnnotation: false,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  CompositeClosureHelper.destroy(publicAPI, model);
  CompositeClosureHelper.isA(publicAPI, model, 'SelectionProvider');
  CompositeClosureHelper.get(publicAPI, model, ['selection', 'annotation']);
  CompositeClosureHelper.event(publicAPI, model, 'selectionChange');
  CompositeClosureHelper.event(publicAPI, model, 'annotationChange');
  CompositeClosureHelper.event(publicAPI, model, 'dataSubscriptionChange');

  selectionProvider(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = CompositeClosureHelper.newInstance(extend);

// ----------------------------------------------------------------------------

export default { newInstance, extend };
<commit_msg>fix(SelectionProvider): Filter notification<commit_after>import CompositeClosureHelper from '../../../Common/Core/CompositeClosureHelper';
import dataHelper from './dataHelper';

// ----------------------------------------------------------------------------
// Selection Provider
// ----------------------------------------------------------------------------

function selectionProvider(publicAPI, model) {
  const dataSubscriptions = [];

  if (!model.selectionData) {
    model.selectionData = {};
  }
  if (!model.selectionMetaData) {
    model.selectionMetaData = {};
  }

  function off() {
    let count = dataSubscriptions.length;
    while (count--) {
      dataSubscriptions[count] = null;
    }
  }

  function flushDataToListener(dataListener, dataChanged) {
    try {
      if (dataListener) {
        const event = dataHelper.getNotificationData(model.selectionData, dataListener.request);
        if (event) {
          if (dataChanged && dataChanged.type === dataListener.request.type) {
            dataListener.onDataReady(event);
          } else if (!dataChanged) {
            dataListener.onDataReady(event);
          }
        }
      }
    } catch (err) {
      console.log('flushDataToListener error caught:', err);
    }
  }

  // Method use to store received data
  publicAPI.setSelectionData = data => {
    dataHelper.set(model.selectionData, data);

    // Process all subscription to see if we can trigger a notification
    dataSubscriptions.forEach(listener => flushDataToListener(listener, data));
  };

  // Method use to access cached data. Will return undefined if not available
  publicAPI.getSelectionData = query => dataHelper.get(model.selectionData, query);

  // Use to extend data subscription
  publicAPI.updateSelectionMetadata = addon => {
    model.selectionMetaData[addon.type] = Object.assign({}, model.selectionMetaData[addon.type], addon.metadata);
  };

  // Get metadata for a given data type
  publicAPI.getSelectionMetadata = type => model.selectionMetaData[type];

  // --------------------------------

  publicAPI.setSelection = (selection) => {
    model.selection = selection;
    publicAPI.fireSelectionChange(selection);
  };

  // --------------------------------

  // annotation = {
  //    selection: {...},
  //    score: [0],
  //    weight: 1,
  //    rationale: 'why not...',
  // }

  publicAPI.setAnnotation = (annotation) => {
    model.annotation = annotation;
    if (annotation.selection) {
      publicAPI.setSelection(annotation.selection);
    } else {
      annotation.selection = model.selection;
    }
    model.shouldCreateNewAnnotation = false;
    publicAPI.fireAnnotationChange(annotation);
  };

  // --------------------------------

  publicAPI.shouldCreateNewAnnotation = () => model.shouldCreateNewAnnotation;
  publicAPI.setCreateNewAnnotationFlag = (shouldCreate) => (model.shouldCreateNewAnnotation = shouldCreate);

  // --------------------------------
  // When a new selection is made, data dependent on that selection will be pushed
  // to subscribers.
  // A subscriber should save the return value and call update() when they need to
  // change the variables or meta data which is pushed to them.
  publicAPI.subscribeToDataSelection = (type, onDataReady, variables = [], metadata = {}) => {
    const id = dataSubscriptions.length;
    const request = { id, type, variables, metadata };
    const dataListener = { onDataReady, request };
    dataSubscriptions.push(dataListener);
    publicAPI.fireDataSubscriptionChange(request);
    flushDataToListener(dataListener, null);
    return {
      unsubscribe() {
        request.action = 'unsubscribe';
        publicAPI.fireDataSubscriptionChange(request);
        dataSubscriptions[id] = null;
      },
      update(vars, meta) {
        request.variables = [].concat(vars);
        request.metadata = Object.assign({}, request.metadata, meta);
        publicAPI.fireDataSubscriptionChange(request);
        flushDataToListener(dataListener, null);
      },
    };
  };

  publicAPI.destroy = CompositeClosureHelper.chain(off, publicAPI.destroy);
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  // selection: null,
  // selectionData: null,
  // selectionMetaData: null,
  shouldCreateNewAnnotation: false,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  CompositeClosureHelper.destroy(publicAPI, model);
  CompositeClosureHelper.isA(publicAPI, model, 'SelectionProvider');
  CompositeClosureHelper.get(publicAPI, model, ['selection', 'annotation']);
  CompositeClosureHelper.event(publicAPI, model, 'selectionChange');
  CompositeClosureHelper.event(publicAPI, model, 'annotationChange');
  CompositeClosureHelper.event(publicAPI, model, 'dataSubscriptionChange');

  selectionProvider(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = CompositeClosureHelper.newInstance(extend);

// ----------------------------------------------------------------------------

export default { newInstance, extend };
<|endoftext|>


<commit_before>/**
 *  \details © 2011  Open Ximdex Evolution SL [http://www.ximdex.org]
 *
 *  Ximdex a Semantic Content Management System (CMS)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  See the Affero GNU General Public License for more details.
 *  You should have received a copy of the Affero GNU General Public License
 *  version 3 along with Ximdex (see LICENSE file).
 *
 *  If not, visit http://gnu.org/licenses/agpl-3.0.html.
 *
 *  @author Ximdex DevTeam <dev@ximdex.com>
 *  @version $Revision: 8089 $
 */

window.com.ximdex = Object.extend(window.com.ximdex, {

	browser: {
		actionUrl: X.baseUrl + window.base_action,
		imagesUrl: X.baseUrl + window.base_action + 'resources/images',
		cssUrl: X.baseUrl + window.base_action + 'resources/css',
		jsUrl: X.baseUrl + window.base_action + 'resources/js',
		panels: {
			leftTabs: [],
			rightTabs: []
		},
		actionEventSelector: document
	},
	actionLoaded: function(cb) {
		$(X.browser.actionEventSelector).one('action-loaded', cb);
	},
	triggerActionLoaded: function(params) {
		// IMPORTANT: Contextualized wrapper for jQuery function.
		// For use inside any action javascript.
		var fn = (function(jq, context) {
			return function(selector, c) {
				c = (c === undefined) ? context : $(c, context);
				return jq(selector, c);
			}
		})(jQuery, params.context);
		$(X.browser.actionEventSelector).trigger('action-loaded', [fn, params]);
	}
});

(function(X) {

	var B = X.browser;

	var defaultAccels = [
		{event: 'searchpanel-open', ctrlKey: true, altKey: true, char: 's'},
		{event: 'tab-close', ctrlKey: true, altKey: true, char: 't'},
		{event: 'tab-all-close', ctrlKey: true, altKey: true, char: 'c'},
		{event: 'tab-left', ctrlKey: true, altKey: true, keyCode: 37},
		{event: 'tab-right', ctrlKey: true, altKey: true, keyCode: 39},
		{event: 'project-view-tree',  ctrlKey: true, altKey: true, char: '1'},
		{event: 'project-view-grid',  ctrlKey: true, altKey: true, char: '2'},
		{event: 'project-view-list',  ctrlKey: true, altKey: true, char: '3'},
		{event: 'tab-dummy-action', ctrlKey: true, char: '0'},
		{event: 'dialog-close', keyCode: 27}
	];

	function getConstructor(cname) {
		var Constructor = null;
		try {
			Constructor = eval('window.com.ximdex.browser.panels.' + cname);
		} catch (e) {
			console.error('[Browser] No se encuentra el constructor %s.'.printf(constructor));
		}
		return Constructor;
	}

	var browserwindow = {

		hbox: null,
		panels: null,
		eh: null,

		_init: function() {

//	console.info('Browser: ', this);
			X.ximModules.init();

			/** ************************* INIT SPLASH ************************** */
			$("#ximdex-splash")
			.width($(document).width() )
			.height($(document).height() );

			$("#ximdex-splash .progress").width("100%");
			//max progress width
			var max = $("#ximdex-splash .progress").width();

			$("#ximdex-splash .progress").width("7%");

			// Increment splash percent every 1 seconds
			intervalProgress = setInterval(function() {
				var width = $("#ximdex-splash .progress").width();
				if(0 != width && width < max ) {
					width = parseInt(width, 10) + 35;
					$("#ximdex-splash .progress").width(width);
				}else {
					clearInterval(intervalProgress);
					$("#ximdex-splash").fadeOut(1300);
				}
			}, 300);

			/** ****************************************************************** */

			this.panels = {};
			this.eh = new X.EventHandler(this.options.accels);

			$('.browser-window-content .browser-hbox', this.element).hbox({panels: this.options.panels});
			this.hbox = $('.browser-window-content .browser-hbox', this.element);

			for (var i=0,l=this.hbox.hbox('length'); i<l; i++) {

				var panel = this.hbox.hbox('getPanel', i);
				var constructor = this.options.panelsContent[i];
				var Constructor = getConstructor(constructor);
				var key = '%s.tab'.printf(constructor);

				var value = X.session.get(key);
				var c = new Constructor({
					name: constructor,
					browser: this.element,
					container: panel.content(),
					activeTab: value || 0
				});

				this.panels[constructor] = c;
			}

			this.registerTriggers();
			this.registerEvents();
			this.registerGlobalAjaxEvents();

		},

		appendView: function(panelName, view) {
			var panel = this.panels[panelName] || null;
			if (panel === null) return;
			panel.addTab(view);
		},

		openAction: function(action, nodes) {
			var panel = this.panels['RightPanel'];
			if (Object.isFunction(panel.openAction)) panel.openAction(action, nodes);
		},

		getActions: function(params) {

			params = $.extend({
				nodes: [],
				cb: function() {}
			}, params);

			var ids = [];
			for (var i=0,l=params.nodes.length; i<l; i++) {
				try {
					ids.push(params.nodes[i].nodeid.value);
				} catch(e) {}
			}

			// NOTE: Wrapping the callback function and the variables
			// will not produce collisions with others asynchronous calls.
			var success = (function(params, fn) {
				return function(data) {
					if (Object.isFunction(fn)) {
						fn($.extend({}, params, {actions: data}));
					}
				}
			})($.extend({ids: ids}, params, {cb: null}), params.cb);

			$.get(
				'?action=browser3&method=cmenu'.printf(X.restUrl),
				{'nodes[]': ids},
				success
			);
		},

		registerTriggers: function() {

			$(document).keydown(function(event) {

				var accel = false;

				if ((accel = this.eh.match(event)) !== false) {
					$(this.element).trigger(accel.event, [{event: event, char: accel.char}]);
				}

			}.bind(this));

			$(document).click(function(event) {
				$('.hide-on-click', document).hide();
				$('.destroy-on-click', document).unbind().remove();
			});
		},

		registerEvents: function() {

			$(this.element).bind('dialog-close', function() {
				$('.hide-on-click', document).hide();
				$('.destroy-on-click', document).unbind().remove();
			});
		},

		registerGlobalAjaxEvents: function() {

			$(this.element)
				.ajaxError(function(event, xhr, ajaxOptions, thrownError) {
					var xheader = xhr.getResponseHeader('X-XIMDEX');
					X.checkSession(xheader);
				})
				.ajaxComplete(function(event, xhr, ajaxOptions) {
					var xheader = xhr.getResponseHeader('X-XIMDEX');
					X.checkSession(xheader);
				});
		},

		options: {
			title: '',
			conf: {},
			panels: 2,
			panelsContent: ['LeftPanel', 'RightPanel'],
			accels: defaultAccels
		},

		getter: []
	};

	$.widget('ui.browserwindow', browserwindow);

})(com.ximdex);
<commit_msg>Added cache to node(s) actions to avoid requesting the same actions in every left/right click<commit_after>/**
 *  \details © 2011  Open Ximdex Evolution SL [http://www.ximdex.org]
 *
 *  Ximdex a Semantic Content Management System (CMS)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  See the Affero GNU General Public License for more details.
 *  You should have received a copy of the Affero GNU General Public License
 *  version 3 along with Ximdex (see LICENSE file).
 *
 *  If not, visit http://gnu.org/licenses/agpl-3.0.html.
 *
 *  @author Ximdex DevTeam <dev@ximdex.com>
 *  @version $Revision: 8089 $
 */

window.com.ximdex = Object.extend(window.com.ximdex, {

	browser: {
		actionUrl: X.baseUrl + window.base_action,
		imagesUrl: X.baseUrl + window.base_action + 'resources/images',
		cssUrl: X.baseUrl + window.base_action + 'resources/css',
		jsUrl: X.baseUrl + window.base_action + 'resources/js',
		panels: {
			leftTabs: [],
			rightTabs: []
		},
		actionEventSelector: document
	},
	actionLoaded: function(cb) {
		$(X.browser.actionEventSelector).one('action-loaded', cb);
	},
	triggerActionLoaded: function(params) {
		// IMPORTANT: Contextualized wrapper for jQuery function.
		// For use inside any action javascript.
		var fn = (function(jq, context) {
			return function(selector, c) {
				c = (c === undefined) ? context : $(c, context);
				return jq(selector, c);
			}
		})(jQuery, params.context);
		$(X.browser.actionEventSelector).trigger('action-loaded', [fn, params]);
	}
});

(function(X) {

	var B = X.browser;

	var defaultAccels = [
		{event: 'searchpanel-open', ctrlKey: true, altKey: true, char: 's'},
		{event: 'tab-close', ctrlKey: true, altKey: true, char: 't'},
		{event: 'tab-all-close', ctrlKey: true, altKey: true, char: 'c'},
		{event: 'tab-left', ctrlKey: true, altKey: true, keyCode: 37},
		{event: 'tab-right', ctrlKey: true, altKey: true, keyCode: 39},
		{event: 'project-view-tree',  ctrlKey: true, altKey: true, char: '1'},
		{event: 'project-view-grid',  ctrlKey: true, altKey: true, char: '2'},
		{event: 'project-view-list',  ctrlKey: true, altKey: true, char: '3'},
		{event: 'tab-dummy-action', ctrlKey: true, char: '0'},
		{event: 'dialog-close', keyCode: 27}
	];

	function getConstructor(cname) {
		var Constructor = null;
		try {
			Constructor = eval('window.com.ximdex.browser.panels.' + cname);
		} catch (e) {
			console.error('[Browser] No se encuentra el constructor %s.'.printf(constructor));
		}
		return Constructor;
	}

	var browserwindow = {

		hbox: null,
		panels: null,
		eh: null,
		cachedActions: new Object(),

		_init: function() {

//	console.info('Browser: ', this);
			X.ximModules.init();

			/** ************************* INIT SPLASH ************************** */
			$("#ximdex-splash")
			.width($(document).width() )
			.height($(document).height() );

			$("#ximdex-splash .progress").width("100%");
			//max progress width
			var max = $("#ximdex-splash .progress").width();

			$("#ximdex-splash .progress").width("7%");

			// Increment splash percent every 1 seconds
			intervalProgress = setInterval(function() {
				var width = $("#ximdex-splash .progress").width();
				if(0 != width && width < max ) {
					width = parseInt(width, 10) + 35;
					$("#ximdex-splash .progress").width(width);
				}else {
					clearInterval(intervalProgress);
					$("#ximdex-splash").fadeOut(1300);
				}
			}, 300);

			/** ****************************************************************** */

			this.panels = {};
			this.eh = new X.EventHandler(this.options.accels);

			$('.browser-window-content .browser-hbox', this.element).hbox({panels: this.options.panels});
			this.hbox = $('.browser-window-content .browser-hbox', this.element);

			for (var i=0,l=this.hbox.hbox('length'); i<l; i++) {

				var panel = this.hbox.hbox('getPanel', i);
				var constructor = this.options.panelsContent[i];
				var Constructor = getConstructor(constructor);
				var key = '%s.tab'.printf(constructor);

				var value = X.session.get(key);
				var c = new Constructor({
					name: constructor,
					browser: this.element,
					container: panel.content(),
					activeTab: value || 0
				});

				this.panels[constructor] = c;
			}

			this.registerTriggers();
			this.registerEvents();
			this.registerGlobalAjaxEvents();

		},

		appendView: function(panelName, view) {
			var panel = this.panels[panelName] || null;
			if (panel === null) return;
			panel.addTab(view);
		},

		openAction: function(action, nodes) {
			var panel = this.panels['RightPanel'];
			if (Object.isFunction(panel.openAction)) panel.openAction(action, nodes);
		},

		getActions: function(params) {
			var that = this;
			params = $.extend({
				nodes: [],
				cb: function() {}
			}, params);

			var ids = [];
			for (var i=0,l=params.nodes.length; i<l; i++) {
				try {
					ids.push(params.nodes[i].nodeid.value);
				} catch(e) {}
			}

			var cachedKey = ids.sort().join("-");

			// NOTE: Wrapping the callback function and the variables
			// will not produce collisions with others asynchronous calls.
			var success = (function(params, fn) {
				return function(data) {
					//Cache the result for this node (or nodes) using cachedKey and data
                                        that.cachedActions[cachedKey] = data;
					if (Object.isFunction(fn)) {
						fn($.extend({}, params, {actions: data}));
					}
				}
			})($.extend({ids: ids}, params, {cb: null}), params.cb);

			//If actions were already requested, use them.
                        if(that.cachedActions.hasOwnProperty(cachedKey)) {
                                // setTimeout is neccesary in order to prevent the modal dialog with actions dissapears
                                // after it appears
                                setTimeout(function() {
	                                if (Object.isFunction(params.cb)) {
        	                                params.cb.call(this, $.extend({}, params, {actions: that.cachedActions[cachedKey]}));
                                        }
	                        },100);
	                }
	                //Otherwise make the request
	               else {
	                        $.get(
	                                 '?action=browser3&method=cmenu'.printf(X.restUrl),
	                                        {'nodes[]': ids},
	                                        success
	                                );
	                        }
		},

		registerTriggers: function() {

			$(document).keydown(function(event) {

				var accel = false;

				if ((accel = this.eh.match(event)) !== false) {
					$(this.element).trigger(accel.event, [{event: event, char: accel.char}]);
				}

			}.bind(this));

			$(document).click(function(event) {
				$('.hide-on-click', document).hide();
				$('.destroy-on-click', document).unbind().remove();
			});
		},

		registerEvents: function() {

			$(this.element).bind('dialog-close', function() {
				$('.hide-on-click', document).hide();
				$('.destroy-on-click', document).unbind().remove();
			});
		},

		registerGlobalAjaxEvents: function() {

			$(this.element)
				.ajaxError(function(event, xhr, ajaxOptions, thrownError) {
					var xheader = xhr.getResponseHeader('X-XIMDEX');
					X.checkSession(xheader);
				})
				.ajaxComplete(function(event, xhr, ajaxOptions) {
					var xheader = xhr.getResponseHeader('X-XIMDEX');
					X.checkSession(xheader);
				});
		},

		options: {
			title: '',
			conf: {},
			panels: 2,
			panelsContent: ['LeftPanel', 'RightPanel'],
			accels: defaultAccels
		},

		getter: []
	};

	$.widget('ui.browserwindow', browserwindow);

})(com.ximdex);
<|endoftext|>