<commit_before>/**
 * Abstract module that controls the addon node settings. Includes Knockout view-model
 * for syncing data, and HGrid-folderpicker for selecting a folder.
 */
'use strict';
require('css/addon_folderpicker.css');
var ko = require('knockout');
require('knockout.punches');
var $ = require('jquery');
var bootbox = require('bootbox');
var Raven = require('raven-js');

var FolderPicker = require('js/folderpicker');
var ZeroClipboard = require('zeroclipboard');
ZeroClipboard.config('/static/vendor/bower_components/zeroclipboard/dist/ZeroClipboard.swf');
var $osf = require('js/osfHelpers');

var oop = require('js/oop');

ko.punches.enableAll();

/**
 * @class FolderPickerViewModel
 * @param {String} addonName Full display name of the addon
 * @param {String} url API url to initially fetch settings
 * @param {String} selector CSS selector for containing div
 * @param {String} folderPicker CSS selector for folderPicker div
 *
 * Notes:
 * - Subclasses of this VM can be created using the oop module like: oop.extend(FolderPickerViewModel, { ... });
 * - Subclasses must:
 *   - provide a VM.messages.submitSettingsSuccess()
 *   - override VM.treebeardOptions.onPickFolder and VM.treebeardOptions.resolveLazyloadUrl
 * - Subclasses can:
 *   - implement an _updateCustomFields method to capture additional parameters in updateFromData
 */
var FolderPickerViewModel = oop.defclass({
    constructor: function(addonName, url, selector, folderpickerSelector) {
        var self = this;
        self.url = url;
        self.addonName = addonName;
        self.selector = selector;
        self.folderpickerSelector = folderpickerSelector;
        self.folderpicker = null;
        // Auth information
        self.nodeHasAuth = ko.observable(false);
        // whether current user has an auth token
        self.userHasAuth = ko.observable(false);
        // whether current user is authorizer of the addon
        self.userIsOwner = ko.observable(false);
        // display name of owner of credentials
        self.ownerName = ko.observable('');
        // whether the auth token is valid
        self.validCredentials = ko.observable(true);
        // current folder
        self.folder = ko.observable({
            name: null,
            id: null,
            path: null
        });
        // set of urls used for API calls internally
        self.urls = ko.observable({});
        // Flashed messages
        self.message = ko.observable('');
        self.messageClass = ko.observable('text-info');
        // Display names
        self.PICKER = 'picker';
        // Currently selected folder name
        self.selected = ko.observable(false);
        self.loading = ko.observable(false);
        // Whether the initial data has been fetched form the server. Used for
        // error handling.
        self.loadedSettings = ko.observable(false);
        // Current folder display
        self.currentDisplay = ko.observable(null);
        // Whether the folders have been loaded from the API
        self.loadedFolders = ko.observable(false);

        self.messages = {
            invalidCredOwner: ko.pureComputed(function() {
                return 'Could not retrieve ' + self.addonName + ' settings at ' +
                    'this time. The ' + self.addonName + ' addon credentials may no longer be valid.' +
                    ' Try deauthorizing and reauthorizing ' + self.addonName + ' on your account settings page.';
            }),
            invalidCredNotOwner: ko.pureComputed(function() {
                return 'Could not retrieve ' + self.addonName + ' settings at ' +
                    'this time. The ' + self.addonName + ' addon credentials may no longer be valid.' +
                    ' Contact ' + self.ownerName() + ' to verify.';
            }),
            cantRetrieveSettings: ko.pureComputed(function() {
                return 'Could not retrieve ' + self.addonName + ' settings at ' +
                    'this time. Please refresh ' +
                    'the page. If the problem persists, email ' +
                    'support@osf.io.';
            }),
            updateAccountsError: ko.pureComputed(function() {
                return 'Could not retrieve ' + self.addonName + ' account list at ' +
                    'this time. Please refresh the page. If the problem persists, email ' +
                    'support@osf.io.';
            }),
            deauthorizeSuccess: ko.pureComputed(function() {
                return 'Deauthorized ' + self.addonName + '.';
            }),
            deauthorizeFail: ko.pureComputed(function() {
                return 'Could not deauthorize because of an error. Please try again later.';
            }),
            connectAccountSuccess: ko.pureComputed(function() {
                return 'Successfully created a ' + self.addonName + ' Access Token';
            }),
            submitSettingsSuccess: ko.pureComputed(function() {
                throw new Error('Subclasses of FolderPickerViewModel must provide a message for successful settings updates. ' +
                                'This should take the form: "Successfully linked \'{FOLDER_NAME}\'. Go to the  ' +
                                '{PAGE_NAME} to view your {CONTENT_TYPE}.');
            }),
            submitSettingsError: ko.pureComputed(function() {
                return 'Could not change settings. Please try again later.';
            }),
            confirmDeauth: ko.pureComputed(function() {
                return 'Are you sure you want to remove this ' + self.addonName + ' authorization?';
            }),
            confirmAuth: ko.pureComputed(function() {
                return 'Are you sure you want to authorize this project with your ' + self.addonName + ' access token?';
            }),
            tokenImportSuccess: ko.pureComputed(function() {
                return 'Successfully imported access token from profile.';
            }),
            tokenImportError: ko.pureComputed(function() {
                return 'Error occurred while importing access token.';
            }),
            connectError: ko.pureComputed(function() {
                return 'Could not connect to ' + self.addonName + ' at this time. Please try again later.';
            })
        };

        /**
         * Whether or not to show the Import Access Token Button
         */
        self.showImport = ko.pureComputed(function() {
            // Invoke the observables to ensure dependency tracking
            var userHasAuth = self.userHasAuth();
            var nodeHasAuth = self.nodeHasAuth();
            var loaded = self.loadedSettings();
            return userHasAuth && !nodeHasAuth && loaded;
        });

        /** Whether or not to show the full settings pane. */
        self.showSettings = ko.pureComputed(function() {
            return self.nodeHasAuth() && self.validCredentials();
        });

        /** Whether or not to show the Create Access Token button */
        self.showTokenCreateButton = ko.pureComputed(function() {
            // Invoke the observables to ensure dependency tracking
            var userHasAuth = self.userHasAuth();
            var nodeHasAuth = self.nodeHasAuth();
            var loaded = self.loadedSettings();
            return loaded && !userHasAuth && !nodeHasAuth;
        });

        /** Computed functions for the linked and selected folders' display text.*/
        self.folderName = ko.pureComputed(function() {
            var nodeHasAuth = self.nodeHasAuth();
            var folder = self.folder();
            return (nodeHasAuth && folder && folder.name) ? folder.name.trim() : '';
        });

        self.selectedFolderName = ko.pureComputed(function() {
            var userIsOwner = self.userIsOwner();
            var selected = self.selected();
            var name = selected.name || 'None';
            return userIsOwner ? name : '';
        });

        self.treebeardOptions = {
            lazyLoadPreprocess: function(data) {
                return data;
            },
            onPickFolder: function() {
                throw new Error('Subclasses of FolderPickerViewModel must implement a "onPickFolder(evt, item)" method');
            },
            resolveLazyloadUrl: function(item) {
                throw new Error('Subclasses of FolderPickerViewModel must implement a "resolveLazyloadUrl(item)" method');
            }
        };
    },
    /**
     * Change the flashed message.
     *
     * @param {String} text Text to show
     * @param {String} css CSS class of text to be show, defaults to 'text-info'
     * @param {Number} timeout Optional number of ms to wait until removing the flashed message
     */
    changeMessage: function(text, css, timeout) {
        var self = this;

        self.message(text);
        var cssClass = css || 'text-info';
        self.messageClass(cssClass);
        if (timeout) {
            // Reset message after timeout period
            setTimeout(function() {
                self.resetMessage();
            }, timeout);
        }
    },
    resetMessage: function() {
        this.message('');
        this.messageClass('text-info');
    },
    /**
     * Abstract hook where subclasses can capture extra data from the API response
     *
     * @param {Object} settings Settings passed from server response in #updateFromData
     */
    _updateCustomFields: function(settings){},
    /**
     * Update the view model from data returned from the server or data passed explicitly.
     *
     * @param {Object} data optional data to update from rather than from API
     */
    updateFromData: function(data) {
        var self = this;
        var ret = $.Deferred();
        var applySettings = function(settings){
            self.ownerName(settings.ownerName);
            self.nodeHasAuth(settings.nodeHasAuth);
            self.userIsOwner(settings.userIsOwner);
            self.userHasAuth(settings.userHasAuth);
            self.folder(settings.folder || {
                name: null,
                path: null,
                id: null
            });
            self.urls(settings.urls);
            self._updateCustomFields(settings);
            ret.resolve();
        };
        if (typeof data === 'undefined'){
            self.fetchFromServer()
                .done(applySettings)
                .fail(ret.reject);
        }
        else{
            applySettings(data);
        }
        return ret.promise();
    },
    fetchFromServer: function() {
        var self = this;
        var ret = $.Deferred();
        var request = $.ajax({
            url: self.url,
            type: 'GET',
            dataType: 'json'
        });
        request.done(function(response) {
            self.loadedSettings(true);
            ret.resolve(response.result);
        });
        request.fail(function(xhr, textStatus, error) {
            self.changeMessage(self.messages.cantRetrieveSettings(), 'text-warning');
            Raven.captureMessage('Could not GET ' + self.addonName + 'settings', {
                url: self.url,
                textStatus: textStatus,
                error: error
            });
            ret.reject(xhr, textStatus, error);
        });
        return ret.promise();
    },
    _serializeSettings: function(){
        return ko.toJS(this);
    },
    /**
     * Send a PUT request to change the linked folder.
     */
    submitSettings: function() {
        var self = this;
        function onSubmitSuccess(response) {
            // Update folder in ViewModel
            self.folder(response.result.folder);
            self.urls(response.result.urls);
            self.cancelSelection();
            self.changeMessage(self.messages.submitSettingsSuccess(), 'text-success');
        }
        function onSubmitError(xhr, status, error) {
            self.changeMessage(self.messages.submitSettingsError(), 'text-danger');
            Raven.captureMessage('Failed to update ' + self.addonName + ' settings.', {
                xhr: xhr,
                status: status,
                error: error
            });
        }
        return $osf.putJSON(self.urls().config, self._serializeSettings())
            .done(onSubmitSuccess)
            .fail(onSubmitError);
    },
    onImportSuccess: function(response) {
        var self = this;
        var msg = response.message || self.messages.tokenImportSuccess();
        // Update view model based on response
        self.changeMessage(msg, 'text-success', 3000);
        self.updateFromData(response.result);
        self.activatePicker();
    },
    onImportError: function(xhr, status, error) {
        var self = this;
        self.changeMessage(self.messages.tokenImportError(), 'text-danger');
        Raven.captureMessage('Failed to import ' + self.addonName + ' access token.', {
            xhr: xhr,
            status: status,
            error: error
        });
    },
    _importAuthPayload: function() {
        return {};
    },
    _importAuthConfirm: function() {
        var self = this;
        return $osf.putJSON(self.urls().importAuth, self._importAuthPayload())
            .done(self.onImportSuccess.bind(self))
            .fail(self.onImportError.bind(self));
    },
    /**
     * Send PUT request to import access token from user profile.
     */
    importAuth: function() {
        var self = this;
        bootbox.confirm({
            title: 'Import ' + self.addonName + ' Access Token?',
            message: self.messages.confirmAuth(),
            callback: function(confirmed) {
                if (confirmed) {
                    self._importAuthConfirm();
                }
            }
        });
    },
    /**
     * Send DELETE request to deauthorize this node.
     */
    _deauthorizeConfirm: function(){
        var self = this;
        var request = $.ajax({
            url: self.urls().deauthorize,
            type: 'DELETE'
        });
        request.done(function() {
            // Update observables
            self.nodeHasAuth(false);
            self.cancelSelection();
            self.currentDisplay(null);
            self.changeMessage(self.messages.deauthorizeSuccess(), 'text-warning', 3000);
            self.loadedFolders(false);
            self.destroyPicker();
        });
        request.fail(function(xhr, textStatus, error) {
            self.changeMessage(self.messages.deauthorizeFail(), 'text-danger');
            Raven.captureMessage('Could not deauthorize ' + self.addonName + ' account from node', {
                url: self.urls().deauthorize,
                textStatus: textStatus,
                error: error
            });
        });
        return request;
    },
    /** Pop up a confirmation to deauthorize addon from this node.
     *  Send DELETE request if confirmed.
     */
    deauthorize: function() {
        var self = this;
        bootbox.confirm({
            title: 'Deauthorize ' + self.addonName + '?',
            message: self.messages.confirmDeauth(),
            callback: function(confirmed) {
                if (confirmed) {
                    self._deauthorizeConfirm();
                }
            }
        });
    },
    /**
     * Must be used to update radio buttons and knockout view model simultaneously
     */
    cancelSelection: function() {
        this.selected(null);
    },
    /**
     *  Toggles the visibility of the folder picker.
     */
    togglePicker: function() {
        var shown = this.currentDisplay() === this.PICKER;
        if (!shown) {
            this.currentDisplay(this.PICKER);
            this.activatePicker();
        } else {
            this.currentDisplay(null);
            // Clear selection
            this.cancelSelection();
        }
    },
    destroyPicker: function() {
        this.folderpicker.destroy();
    },
    doActivatePicker: function(opts) {
        var self = this;
        // Show loading indicator
        self.loading(true);
        self.folderpicker = new FolderPicker(self.folderpickerSelector, opts);
    },
    /**
     *  Activates the HGrid folder picker.
     */
    activatePicker: function() {
        var self = this;
        var opts = $.extend({}, {
            initialFolderPath: self.folder().path || '',
            // Fetch folders with AJAX
            filesData: self.urls().folders, // URL for fetching folders
            // Lazy-load each folder's contents
            // Each row stores its url for fetching the folders it contains
            oddEvenClass: {
                odd: 'addon-folderpicker-odd',
                even: 'addon-folderpicker-even'
            },
            multiselect: false,
            allowMove: false,
            ajaxOptions: {
                error: function(xhr, textStatus, error) {
                    self.loading(false);
                    self.changeMessage(self.messages.connectError(), 'text-warning');
                    Raven.captureMessage('Could not GET get ' + self.addonName + ' contents.', {
                        textStatus: textStatus,
                        error: error
                    });
                }
            },
            folderPickerOnload: function() {
                // Hide loading indicator
                self.loading(false);
                // Set flag to prevent repeated requests
                self.loadedFolders(true);
            }
        }, self.treebeardOptions);
        self.currentDisplay(self.PICKER);
        // Only load folders if they haven't already been requested
        if (!self.loadedFolders()) {
            self.doActivatePicker(opts);
        }
    }
});

module.exports = FolderPickerViewModel;
<commit_msg>Change invalid cred message<commit_after>/**
 * Abstract module that controls the addon node settings. Includes Knockout view-model
 * for syncing data, and HGrid-folderpicker for selecting a folder.
 */
'use strict';
require('css/addon_folderpicker.css');
var ko = require('knockout');
require('knockout.punches');
var $ = require('jquery');
var bootbox = require('bootbox');
var Raven = require('raven-js');

var FolderPicker = require('js/folderpicker');
var ZeroClipboard = require('zeroclipboard');
ZeroClipboard.config('/static/vendor/bower_components/zeroclipboard/dist/ZeroClipboard.swf');
var $osf = require('js/osfHelpers');

var oop = require('js/oop');

ko.punches.enableAll();

/**
 * @class FolderPickerViewModel
 * @param {String} addonName Full display name of the addon
 * @param {String} url API url to initially fetch settings
 * @param {String} selector CSS selector for containing div
 * @param {String} folderPicker CSS selector for folderPicker div
 *
 * Notes:
 * - Subclasses of this VM can be created using the oop module like: oop.extend(FolderPickerViewModel, { ... });
 * - Subclasses must:
 *   - provide a VM.messages.submitSettingsSuccess()
 *   - override VM.treebeardOptions.onPickFolder and VM.treebeardOptions.resolveLazyloadUrl
 * - Subclasses can:
 *   - implement an _updateCustomFields method to capture additional parameters in updateFromData
 */
var FolderPickerViewModel = oop.defclass({
    constructor: function(addonName, url, selector, folderpickerSelector) {
        var self = this;
        self.url = url;
        self.addonName = addonName;
        self.selector = selector;
        self.folderpickerSelector = folderpickerSelector;
        self.folderpicker = null;
        // Auth information
        self.nodeHasAuth = ko.observable(false);
        // whether current user has an auth token
        self.userHasAuth = ko.observable(false);
        // whether current user is authorizer of the addon
        self.userIsOwner = ko.observable(false);
        // display name of owner of credentials
        self.ownerName = ko.observable('');
        // whether the auth token is valid
        self.validCredentials = ko.observable(true);
        // current folder
        self.folder = ko.observable({
            name: null,
            id: null,
            path: null
        });
        // set of urls used for API calls internally
        self.urls = ko.observable({});
        // Flashed messages
        self.message = ko.observable('');
        self.messageClass = ko.observable('text-info');
        // Display names
        self.PICKER = 'picker';
        // Currently selected folder name
        self.selected = ko.observable(false);
        self.loading = ko.observable(false);
        // Whether the initial data has been fetched form the server. Used for
        // error handling.
        self.loadedSettings = ko.observable(false);
        // Current folder display
        self.currentDisplay = ko.observable(null);
        // Whether the folders have been loaded from the API
        self.loadedFolders = ko.observable(false);

        self.messages = {
            invalidCredOwner: ko.pureComputed(function() {
                return 'Could not retrieve ' + self.addonName + ' settings at ' +
                    'this time. The ' + self.addonName + ' addon credentials may no longer be valid.' +
                    ' Try deauthorizing and reauthorizing ' + self.addonName + '.';
            }),
            invalidCredNotOwner: ko.pureComputed(function() {
                return 'Could not retrieve ' + self.addonName + ' settings at ' +
                    'this time. The ' + self.addonName + ' addon credentials may no longer be valid.' +
                    ' Contact ' + self.ownerName() + ' to verify.';
            }),
            cantRetrieveSettings: ko.pureComputed(function() {
                return 'Could not retrieve ' + self.addonName + ' settings at ' +
                    'this time. Please refresh ' +
                    'the page. If the problem persists, email ' +
                    'support@osf.io.';
            }),
            updateAccountsError: ko.pureComputed(function() {
                return 'Could not retrieve ' + self.addonName + ' account list at ' +
                    'this time. Please refresh the page. If the problem persists, email ' +
                    'support@osf.io.';
            }),
            deauthorizeSuccess: ko.pureComputed(function() {
                return 'Deauthorized ' + self.addonName + '.';
            }),
            deauthorizeFail: ko.pureComputed(function() {
                return 'Could not deauthorize because of an error. Please try again later.';
            }),
            connectAccountSuccess: ko.pureComputed(function() {
                return 'Successfully created a ' + self.addonName + ' Access Token';
            }),
            submitSettingsSuccess: ko.pureComputed(function() {
                throw new Error('Subclasses of FolderPickerViewModel must provide a message for successful settings updates. ' +
                                'This should take the form: "Successfully linked \'{FOLDER_NAME}\'. Go to the  ' +
                                '{PAGE_NAME} to view your {CONTENT_TYPE}.');
            }),
            submitSettingsError: ko.pureComputed(function() {
                return 'Could not change settings. Please try again later.';
            }),
            confirmDeauth: ko.pureComputed(function() {
                return 'Are you sure you want to remove this ' + self.addonName + ' authorization?';
            }),
            confirmAuth: ko.pureComputed(function() {
                return 'Are you sure you want to authorize this project with your ' + self.addonName + ' access token?';
            }),
            tokenImportSuccess: ko.pureComputed(function() {
                return 'Successfully imported access token from profile.';
            }),
            tokenImportError: ko.pureComputed(function() {
                return 'Error occurred while importing access token.';
            }),
            connectError: ko.pureComputed(function() {
                return 'Could not connect to ' + self.addonName + ' at this time. Please try again later.';
            })
        };

        /**
         * Whether or not to show the Import Access Token Button
         */
        self.showImport = ko.pureComputed(function() {
            // Invoke the observables to ensure dependency tracking
            var userHasAuth = self.userHasAuth();
            var nodeHasAuth = self.nodeHasAuth();
            var loaded = self.loadedSettings();
            return userHasAuth && !nodeHasAuth && loaded;
        });

        /** Whether or not to show the full settings pane. */
        self.showSettings = ko.pureComputed(function() {
            return self.nodeHasAuth() && self.validCredentials();
        });

        /** Whether or not to show the Create Access Token button */
        self.showTokenCreateButton = ko.pureComputed(function() {
            // Invoke the observables to ensure dependency tracking
            var userHasAuth = self.userHasAuth();
            var nodeHasAuth = self.nodeHasAuth();
            var loaded = self.loadedSettings();
            return loaded && !userHasAuth && !nodeHasAuth;
        });

        /** Computed functions for the linked and selected folders' display text.*/
        self.folderName = ko.pureComputed(function() {
            var nodeHasAuth = self.nodeHasAuth();
            var folder = self.folder();
            return (nodeHasAuth && folder && folder.name) ? folder.name.trim() : '';
        });

        self.selectedFolderName = ko.pureComputed(function() {
            var userIsOwner = self.userIsOwner();
            var selected = self.selected();
            var name = selected.name || 'None';
            return userIsOwner ? name : '';
        });

        self.treebeardOptions = {
            lazyLoadPreprocess: function(data) {
                return data;
            },
            onPickFolder: function() {
                throw new Error('Subclasses of FolderPickerViewModel must implement a "onPickFolder(evt, item)" method');
            },
            resolveLazyloadUrl: function(item) {
                throw new Error('Subclasses of FolderPickerViewModel must implement a "resolveLazyloadUrl(item)" method');
            }
        };
    },
    /**
     * Change the flashed message.
     *
     * @param {String} text Text to show
     * @param {String} css CSS class of text to be show, defaults to 'text-info'
     * @param {Number} timeout Optional number of ms to wait until removing the flashed message
     */
    changeMessage: function(text, css, timeout) {
        var self = this;

        self.message(text);
        var cssClass = css || 'text-info';
        self.messageClass(cssClass);
        if (timeout) {
            // Reset message after timeout period
            setTimeout(function() {
                self.resetMessage();
            }, timeout);
        }
    },
    resetMessage: function() {
        this.message('');
        this.messageClass('text-info');
    },
    /**
     * Abstract hook where subclasses can capture extra data from the API response
     *
     * @param {Object} settings Settings passed from server response in #updateFromData
     */
    _updateCustomFields: function(settings){},
    /**
     * Update the view model from data returned from the server or data passed explicitly.
     *
     * @param {Object} data optional data to update from rather than from API
     */
    updateFromData: function(data) {
        var self = this;
        var ret = $.Deferred();
        var applySettings = function(settings){
            self.ownerName(settings.ownerName);
            self.nodeHasAuth(settings.nodeHasAuth);
            self.userIsOwner(settings.userIsOwner);
            self.userHasAuth(settings.userHasAuth);
            self.folder(settings.folder || {
                name: null,
                path: null,
                id: null
            });
            self.urls(settings.urls);
            self._updateCustomFields(settings);
            ret.resolve();
        };
        if (typeof data === 'undefined'){
            self.fetchFromServer()
                .done(applySettings)
                .fail(ret.reject);
        }
        else{
            applySettings(data);
        }
        return ret.promise();
    },
    fetchFromServer: function() {
        var self = this;
        var ret = $.Deferred();
        var request = $.ajax({
            url: self.url,
            type: 'GET',
            dataType: 'json'
        });
        request.done(function(response) {
            self.loadedSettings(true);
            ret.resolve(response.result);
        });
        request.fail(function(xhr, textStatus, error) {
            self.changeMessage(self.messages.cantRetrieveSettings(), 'text-warning');
            Raven.captureMessage('Could not GET ' + self.addonName + 'settings', {
                url: self.url,
                textStatus: textStatus,
                error: error
            });
            ret.reject(xhr, textStatus, error);
        });
        return ret.promise();
    },
    _serializeSettings: function(){
        return ko.toJS(this);
    },
    /**
     * Send a PUT request to change the linked folder.
     */
    submitSettings: function() {
        var self = this;
        function onSubmitSuccess(response) {
            // Update folder in ViewModel
            self.folder(response.result.folder);
            self.urls(response.result.urls);
            self.cancelSelection();
            self.changeMessage(self.messages.submitSettingsSuccess(), 'text-success');
        }
        function onSubmitError(xhr, status, error) {
            self.changeMessage(self.messages.submitSettingsError(), 'text-danger');
            Raven.captureMessage('Failed to update ' + self.addonName + ' settings.', {
                xhr: xhr,
                status: status,
                error: error
            });
        }
        return $osf.putJSON(self.urls().config, self._serializeSettings())
            .done(onSubmitSuccess)
            .fail(onSubmitError);
    },
    onImportSuccess: function(response) {
        var self = this;
        var msg = response.message || self.messages.tokenImportSuccess();
        // Update view model based on response
        self.changeMessage(msg, 'text-success', 3000);
        self.updateFromData(response.result);
        self.activatePicker();
    },
    onImportError: function(xhr, status, error) {
        var self = this;
        self.changeMessage(self.messages.tokenImportError(), 'text-danger');
        Raven.captureMessage('Failed to import ' + self.addonName + ' access token.', {
            xhr: xhr,
            status: status,
            error: error
        });
    },
    _importAuthPayload: function() {
        return {};
    },
    _importAuthConfirm: function() {
        var self = this;
        return $osf.putJSON(self.urls().importAuth, self._importAuthPayload())
            .done(self.onImportSuccess.bind(self))
            .fail(self.onImportError.bind(self));
    },
    /**
     * Send PUT request to import access token from user profile.
     */
    importAuth: function() {
        var self = this;
        bootbox.confirm({
            title: 'Import ' + self.addonName + ' Access Token?',
            message: self.messages.confirmAuth(),
            callback: function(confirmed) {
                if (confirmed) {
                    self._importAuthConfirm();
                }
            }
        });
    },
    /**
     * Send DELETE request to deauthorize this node.
     */
    _deauthorizeConfirm: function(){
        var self = this;
        var request = $.ajax({
            url: self.urls().deauthorize,
            type: 'DELETE'
        });
        request.done(function() {
            // Update observables
            self.nodeHasAuth(false);
            self.cancelSelection();
            self.currentDisplay(null);
            self.changeMessage(self.messages.deauthorizeSuccess(), 'text-warning', 3000);
            self.loadedFolders(false);
            self.destroyPicker();
        });
        request.fail(function(xhr, textStatus, error) {
            self.changeMessage(self.messages.deauthorizeFail(), 'text-danger');
            Raven.captureMessage('Could not deauthorize ' + self.addonName + ' account from node', {
                url: self.urls().deauthorize,
                textStatus: textStatus,
                error: error
            });
        });
        return request;
    },
    /** Pop up a confirmation to deauthorize addon from this node.
     *  Send DELETE request if confirmed.
     */
    deauthorize: function() {
        var self = this;
        bootbox.confirm({
            title: 'Deauthorize ' + self.addonName + '?',
            message: self.messages.confirmDeauth(),
            callback: function(confirmed) {
                if (confirmed) {
                    self._deauthorizeConfirm();
                }
            }
        });
    },
    /**
     * Must be used to update radio buttons and knockout view model simultaneously
     */
    cancelSelection: function() {
        this.selected(null);
    },
    /**
     *  Toggles the visibility of the folder picker.
     */
    togglePicker: function() {
        var shown = this.currentDisplay() === this.PICKER;
        if (!shown) {
            this.currentDisplay(this.PICKER);
            this.activatePicker();
        } else {
            this.currentDisplay(null);
            // Clear selection
            this.cancelSelection();
        }
    },
    destroyPicker: function() {
        this.folderpicker.destroy();
    },
    doActivatePicker: function(opts) {
        var self = this;
        // Show loading indicator
        self.loading(true);
        self.folderpicker = new FolderPicker(self.folderpickerSelector, opts);
    },
    /**
     *  Activates the HGrid folder picker.
     */
    activatePicker: function() {
        var self = this;
        var opts = $.extend({}, {
            initialFolderPath: self.folder().path || '',
            // Fetch folders with AJAX
            filesData: self.urls().folders, // URL for fetching folders
            // Lazy-load each folder's contents
            // Each row stores its url for fetching the folders it contains
            oddEvenClass: {
                odd: 'addon-folderpicker-odd',
                even: 'addon-folderpicker-even'
            },
            multiselect: false,
            allowMove: false,
            ajaxOptions: {
                error: function(xhr, textStatus, error) {
                    self.loading(false);
                    self.changeMessage(self.messages.connectError(), 'text-warning');
                    Raven.captureMessage('Could not GET get ' + self.addonName + ' contents.', {
                        textStatus: textStatus,
                        error: error
                    });
                }
            },
            folderPickerOnload: function() {
                // Hide loading indicator
                self.loading(false);
                // Set flag to prevent repeated requests
                self.loadedFolders(true);
            }
        }, self.treebeardOptions);
        self.currentDisplay(self.PICKER);
        // Only load folders if they haven't already been requested
        if (!self.loadedFolders()) {
            self.doActivatePicker(opts);
        }
    }
});

module.exports = FolderPickerViewModel;
<|endoftext|>


<commit_before>(function () {
  'use strict';

  // bc.vc, shortcut, dirty hack
  $.register({
    rule: {
      host: /^bc\.vc$/,
      query: /^.+(https?:\/\/.+)/,
    },
    start: function (m) {
      $.openLink(m.query[1]);
    },
  });

  // bc.vc, shortcut
  // FIXME may cut hash or query string
  $.register({
    rule: {
      host: /^bc\.vc$/,
      path: /^.+(https?:\/\/.+)$/,
    },
    start: function (m) {
      $.openLink(m.path[1]);
    },
  });

  function decompress (script, unzip) {
    if (!unzip) {
      return script;
    }
    var matches = script.match(/eval(.*)/);
    matches = matches[1];
    script = eval(matches);
    return script;
  }

  function searchScript (unzip) {
    var content = $.$$('script').find(function (script) {
      if (script.innerHTML.indexOf('make_log') < 0) {
        return _.nop;
      }
      return script.innerHTML;
    });
    if (content) {
      return {
        direct: false,
        script: decompress(content.payload, unzip),
      };
    }
    content = $.$$('script').find(function (script) {
      if (script.innerHTML.indexOf('click_log') < 0) {
        return _.nop;
      }
      return script.innerHTML;
    });
    if (content) {
      return {
        direct: true,
        script: decompress(content.payload, unzip),
      };
    }
    throw _.NoPicAdsError('script changed');
  }

  function knockServer (script, dirtyFix) {
    var matches = script.match(/\$.post\('([^']*)'[^{]+(\{opt:'make_log'[^}]+\}\}),/i);
    var make_url = matches[1];
    var make_opts = eval('(' + matches[2] + ')');

    var i = setInterval(function () {
      $.post(make_url, make_opts, function (text) {
        if (dirtyFix) {
          // dirty fix for tr5.in
          text = text.match(/\{.+\}/)[0];
        }
        var jj = JSON.parse(text);
        if (jj.message) {
          clearInterval(i);
          $.openLink(jj.message.url);
        }
      });
    }, 1000);
  }

  function knockServer2 (script) {
    // somehow I must use jQuery AJAX
    var post = unsafeWindow.$.post;
    // mock a fake AJAX function
    unsafeWindow.$.post = function (a, b, c) {
      if (typeof c === 'function') {
        setTimeout(function () {
          var data = {
            error: false,
            message: {
              url: '#',
            },
          };
          c(JSON.stringify(data));
        }, 1000);
      }
    };

    var matches = script.match(/\$.post\('([^']*)'[^{]+(\{opt:'make_log'[^}]+\}\}),/i);
    var make_url = matches[1];
    var make_opts = eval('(' + matches[2] + ')');

    function makeLog () {
        make_opts.opt = 'make_log';
        post(make_url, make_opts, function (text) {
          var data = JSON.parse(text);
          _.info('make_log', data);
          if (!data.message) {
            checksLog();
            return;
          }

          $.openLink(data.message.url);
        });
    }

    function checkLog () {
      make_opts.opt = 'check_log';
      post(make_url, make_opts, function (text) {
        var data = JSON.parse(text);
        _.info('check_log', data);
        if (!data.message) {
          checkLog();
          return;
        }

        makeLog();
      });
    }

    function checksLog () {
      make_opts.opt = 'checks_log';
      post(make_url, make_opts, function () {
        _.info('checks_log');
        checkLog();
      });
    }

    checksLog();
  }

  // bc.vc
  $.register({
    rule: {
      host: /^bc\.vc$/,
      path: /^\/.+/,
    },
    ready: function () {
      $.removeNodes('iframe');

      var result = searchScript(false);
      if (!result.direct) {
        knockServer2(result.script);
      } else {
        result = result.script.match(/top\.location\.href = '([^']+)'/);
        if (!result) {
          throw new _.NoPicAdsError('script changed');
        }
        result = result[1];
        $.openLink(result);
      }
    },
  });

  function run (dirtyFix) {
    // prevent redirection by iframe
    $.removeNodes('iframe');

    var result = searchScript(true);
    if (!result.direct) {
      knockServer(result.script,dirtyFix);
    } else {
      result = result.script.match(/top\.location\.href='([^']+)'/);
      if (!result) {
        throw new _.NoPicAdsError('script changed');
      }
      result = result[1];
      $.openLink(result);
    }
  }

  // adcrun.ch
  $.register({
    rule: {
      host: /^adcrun\.ch$/,
      path: /\/\w+/
    },
    ready: function() {
      // Try to bypass the survey
      $.removeNodes('.user_content');

      var rSurveyLink = /http\.open\("GET", "api_ajax\.php\?sid=\d*&ip=[^&]*&longurl=([^"]+)" \+ first_time, (?:true|false)\);/;
      var l = $.searchScripts(rSurveyLink);
      // Redirect to the target link if we found it
      if(l) {
        $.openLink(l[1]);
        return;
      }

      // Otherwise it's most likely a simple bc.vc-like link
      // Malformed JSON
      run(true);
    }
  });

  function combineRegExp (res) {
    var re = res.map(function (re) {
      return re.source;
    }).join('|');
    re = new RegExp('^' + re + '$');
    return re;
  }

  // split url patterns for the god's sake
  var hostRules = [
    /gx\.si/,
    /adwat\.ch/,
    /(fly2url|urlwiz|xafox)\.com/,
    /(zpoz|ultry)\.net/,
    /(wwy|myam)\.me/,
    /ssl\.gs/,
    /link\.tl/,
    /xip\.ir/,
    /hit\.us/,
    /shortit\.in/,
    /(adbla|tl7)\.us/,
    /www\.adjet\.eu/,
    /srk\.gs/,
    /cun\.bz/,
    /miniurl\.tk/,
  ];
  hostRules = combineRegExp(hostRules);

  $.register({
    rule: {
      host: hostRules,
      path: /^\/.+/,
    },
    ready: run,
  });

  $.register({
    rule: {
      host: /^adtr\.im|ysear\.ch$/,
      path: /^\/.+/,
    },
    ready: function () {
      var a = $.$('div.fly_head a.close');
      var f = $.$('iframe.fly_frame');
      // the iframe may be an ad link
      // so also check the close button
      if (a && f) {
        $.openLink(f.src);
      } else {
        run();
      }
    },
  });

    $.register({
    rule: {
      host: /^ad5\.eu$/,
      path: /^\/[^.]+$/,
    },
    ready: function() {
      $.removeNodes('iframe');
      var s = searchScript(true);

      // Find the form
      var m = s.script.match(/(<form name="form1"method="post".*(?!<\\form>)<\/form>)/);

      if (!m) {return;}

      m = m[1];

      // Set the correct timezone
      var tz = -(new Date().getTimezoneOffset()/60);
      m = m.replace("'+timezone+'",tz);

      // Wrap the form into a useless div
      var d = document.createElement('div');
      d.setAttribute('id','NoPicAdsFTW');
      d.setAttribute('style', 'display:none;');

      // Feed with the right form
      d.innerHTML = m;
      document.body.appendChild(d);

      // Redirect to next page
      $('#NoPicAdsFTW > form[name=form1]').submit();
    },
  });

  $.register({
    rule: {
      host: /^tr5\.in$/,
      path: /^\/.+/,
    },
    ready: function () {
      // Malformed JSON
      run(true);
    },
  });

})();

// ex: ts=2 sts=2 sw=2 et
// sublime: tab_size 2; translate_tabs_to_spaces true; detect_indentation false; use_tab_stops true;
// kate: space-indent on; indent-width 2;
<commit_msg>Tiny fix.<commit_after>(function () {
  'use strict';

  // bc.vc, shortcut, dirty hack
  $.register({
    rule: {
      host: /^bc\.vc$/,
      query: /^.+(https?:\/\/.+)/,
    },
    start: function (m) {
      $.openLink(m.query[1]);
    },
  });

  // bc.vc, shortcut
  // FIXME may cut hash or query string
  $.register({
    rule: {
      host: /^bc\.vc$/,
      path: /^.+(https?:\/\/.+)$/,
    },
    start: function (m) {
      $.openLink(m.path[1]);
    },
  });

  function decompress (script, unzip) {
    if (!unzip) {
      return script;
    }
    var matches = script.match(/eval(.*)/);
    matches = matches[1];
    script = eval(matches);
    return script;
  }

  function searchScript (unzip) {
    var content = $.$$('script').find(function (script) {
      if (script.innerHTML.indexOf('make_log') < 0) {
        return _.nop;
      }
      return script.innerHTML;
    });
    if (content) {
      return {
        direct: false,
        script: decompress(content.payload, unzip),
      };
    }
    content = $.$$('script').find(function (script) {
      if (script.innerHTML.indexOf('click_log') < 0) {
        return _.nop;
      }
      return script.innerHTML;
    });
    if (content) {
      return {
        direct: true,
        script: decompress(content.payload, unzip),
      };
    }
    throw _.NoPicAdsError('script changed');
  }

  function knockServer (script, dirtyFix) {
    var matches = script.match(/\$.post\('([^']*)'[^{]+(\{opt:'make_log'[^}]+\}\}),/i);
    var make_url = matches[1];
    var make_opts = eval('(' + matches[2] + ')');

    var i = setInterval(function () {
      $.post(make_url, make_opts, function (text) {
        if (dirtyFix) {
          // dirty fix for tr5.in
          text = text.match(/\{.+\}/)[0];
        }
        var jj = JSON.parse(text);
        if (jj.message) {
          clearInterval(i);
          $.openLink(jj.message.url);
        }
      });
    }, 1000);
  }

  function knockServer2 (script) {
    // somehow I must use jQuery AJAX
    var post = unsafeWindow.$.post;
    // mock a fake AJAX function
    unsafeWindow.$.post = function (a, b, c) {
      if (typeof c === 'function') {
        setTimeout(function () {
          var data = {
            error: false,
            message: {
              url: '#',
            },
          };
          c(JSON.stringify(data));
        }, 1000);
      }
    };

    var matches = script.match(/\$.post\('([^']*)'[^{]+(\{opt:'make_log'[^}]+\}\}),/i);
    var make_url = matches[1];
    var make_opts = eval('(' + matches[2] + ')');

    function makeLog () {
        make_opts.opt = 'make_log';
        post(make_url, make_opts, function (text) {
          var data = JSON.parse(text);
          _.info('make_log', data);
          if (!data.message) {
            checksLog();
            return;
          }

          $.openLink(data.message.url);
        });
    }

    function checkLog () {
      make_opts.opt = 'check_log';
      post(make_url, make_opts, function (text) {
        var data = JSON.parse(text);
        _.info('check_log', data);
        if (!data.message) {
          checkLog();
          return;
        }

        makeLog();
      });
    }

    function checksLog () {
      make_opts.opt = 'checks_log';
      post(make_url, make_opts, function () {
        _.info('checks_log');
        checkLog();
      });
    }

    checksLog();
  }

  // bc.vc
  $.register({
    rule: {
      host: /^bc\.vc$/,
      path: /^\/.+/,
    },
    ready: function () {
      $.removeNodes('iframe');

      var result = searchScript(false);
      if (!result.direct) {
        knockServer2(result.script);
      } else {
        result = result.script.match(/top\.location\.href = '([^']+)'/);
        if (!result) {
          throw new _.NoPicAdsError('script changed');
        }
        result = result[1];
        $.openLink(result);
      }
    },
  });

  function run (dirtyFix) {
    // prevent redirection by iframe
    $.removeNodes('iframe');

    var result = searchScript(true);
    if (!result.direct) {
      knockServer(result.script,dirtyFix);
    } else {
      result = result.script.match(/top\.location\.href='([^']+)'/);
      if (!result) {
        throw new _.NoPicAdsError('script changed');
      }
      result = result[1];
      $.openLink(result);
    }
  }

  // adcrun.ch
  $.register({
    rule: {
      host: /^adcrun\.ch$/,
      path: /\/\w+/
    },
    ready: function() {
      // Try to bypass the survey
      $.removeNodes('.user_content');

      var rSurveyLink = /http\.open\("GET", "api_ajax\.php\?sid=\d*&ip=[^&]*&longurl=([^"]+)" \+ first_time, (?:true|false)\);/;
      var l = $.searchScripts(rSurveyLink);
      // Redirect to the target link if we found it
      if(l) {
        $.openLink(l[1]);
        return;
      }

      // Otherwise it's most likely a simple bc.vc-like link
      // Malformed JSON
      run(true);
    }
  });

  function combineRegExp (res) {
    var re = res.map(function (re) {
      return re.source;
    }).join('|');
    re = new RegExp('^' + re + '$');
    return re;
  }

  // split url patterns for the god's sake
  var hostRules = [
    /gx\.si/,
    /adwat\.ch/,
    /(fly2url|urlwiz|xafox)\.com/,
    /(zpoz|ultry)\.net/,
    /(wwy|myam)\.me/,
    /ssl\.gs/,
    /link\.tl/,
    /xip\.ir/,
    /hit\.us/,
    /shortit\.in/,
    /(adbla|tl7)\.us/,
    /www\.adjet\.eu/,
    /srk\.gs/,
    /cun\.bz/,
    /miniurl\.tk/,
  ];
  hostRules = combineRegExp(hostRules);

  $.register({
    rule: {
      host: hostRules,
      path: /^\/.+/,
    },
    ready: run,
  });

  $.register({
    rule: {
      host: /^adtr\.im|ysear\.ch$/,
      path: /^\/.+/,
    },
    ready: function () {
      var a = $.$('div.fly_head a.close');
      var f = $.$('iframe.fly_frame');
      // the iframe may be an ad link
      // so also check the close button
      if (a && f) {
        $.openLink(f.src);
      } else {
        run();
      }
    },
  });

  $.register({
    rule: {
      host: /^ad5\.eu$/,
      path: /^\/[^.]+$/,
    },
    ready: function() {
      $.removeNodes('iframe');
      var s = searchScript(true);

      // Find the form
      var m = s.script.match(/(<form name="form1"method="post".*(?!<\\form>)<\/form>)/);

      if (!m) {return;}

      m = m[1];

      // Set the correct timezone
      var tz = -(new Date().getTimezoneOffset()/60);
      m = m.replace("'+timezone+'",tz);

      // Wrap the form into a useless div
      var d = document.createElement('div');
      d.setAttribute('id','NoPicAdsFTW');
      d.setAttribute('style', 'display:none;');

      // Feed with the right form
      d.innerHTML = m;
      document.body.appendChild(d);

      // Redirect to next page
      $('#NoPicAdsFTW > form[name=form1]').submit();
    },
  });

  $.register({
    rule: {
      host: /^tr5\.in$/,
      path: /^\/.+/,
    },
    ready: function () {
      // Malformed JSON
      run(true);
    },
  });

})();

// ex: ts=2 sts=2 sw=2 et
// sublime: tab_size 2; translate_tabs_to_spaces true; detect_indentation false; use_tab_stops true;
// kate: space-indent on; indent-width 2;
<|endoftext|>


<commit_before>/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    StatusBar,
    ScrollView,
    TouchableOpacity
} from 'react-native';

import Orientation from 'react-native-orientation-locker';

export default class Intro extends Component {
    static navigationOptions = {
        header: null
    };

    constructor(props: any) {
        super(props);
        Orientation.lockToPortrait();
    }

    selectionPressed = (className) => {
        this.props.navigation.navigate('Memes', {
            className: className,
        });
    };

    render() {
        return (
            <View style={styles.container}>
                <StatusBar hidden={true} />
                <View style={styles.introTextContainer}>
                    <Text style={styles.introText}>After you take...</Text>
                </View>
                <ScrollView style={styles.optionContainer}>
                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('11')
                        }}>
                            <Text style={styles.menuText}>Comp 11</Text>
                        </TouchableOpacity>
                    </View>

                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('15')
                        }}>
                            <Text style={styles.menuText}>Comp 15</Text>
                        </TouchableOpacity>
                    </View>

                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('20')
                        }}>
                            <Text style={styles.menuText}>Comp 20</Text>
                        </TouchableOpacity>
                    </View>

                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('40')
                        }}>
                            <Text style={styles.menuText}>Comp 40</Text>
                        </TouchableOpacity>
                    </View>

                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('105')
                        }}>
                            <Text style={styles.menuText}>Comp 105</Text>
                        </TouchableOpacity>
                    </View>

                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('160')
                        }}>
                            <Text style={styles.menuText}>Comp 160</Text>
                        </TouchableOpacity>
                    </View>

                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('170')
                        }}>
                            <Text style={styles.menuText}>Comp 170</Text>
                        </TouchableOpacity>
                    </View>
                </ScrollView>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#F5FCFF',
    },
    introTextContainer: {
        flex: 0.15,
        alignItems: 'center',
        justifyContent: 'center'
    },
    introText: {
        fontSize: 20,
    },
    optionContainer: {
        flex: 0.85
    },
    menuText: {
        textAlign: 'center',
        fontSize: 20,
    },
    littleMenuContainer: {
        margin: 10,
        backgroundColor: 'rgb(244, 176, 66)'
    },
});

<commit_msg>solved android devices with different default color problem<commit_after>/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    StatusBar,
    ScrollView,
    TouchableOpacity
} from 'react-native';

import Orientation from 'react-native-orientation-locker';

export default class Intro extends Component {
    static navigationOptions = {
        header: null
    };

    constructor(props: any) {
        super(props);
        Orientation.lockToPortrait();
    }

    selectionPressed = (className) => {
        this.props.navigation.navigate('Memes', {
            className: className,
        });
    };

    render() {
        return (
            <View style={styles.container}>
                <StatusBar hidden={true} />
                <View style={styles.introTextContainer}>
                    <Text style={styles.introText}>After you take...</Text>
                </View>
                <ScrollView style={styles.optionContainer}>
                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('11')
                        }}>
                            <Text style={styles.menuText}>Comp 11</Text>
                        </TouchableOpacity>
                    </View>

                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('15')
                        }}>
                            <Text style={styles.menuText}>Comp 15</Text>
                        </TouchableOpacity>
                    </View>

                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('20')
                        }}>
                            <Text style={styles.menuText}>Comp 20</Text>
                        </TouchableOpacity>
                    </View>

                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('40')
                        }}>
                            <Text style={styles.menuText}>Comp 40</Text>
                        </TouchableOpacity>
                    </View>

                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('105')
                        }}>
                            <Text style={styles.menuText}>Comp 105</Text>
                        </TouchableOpacity>
                    </View>

                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('160')
                        }}>
                            <Text style={styles.menuText}>Comp 160</Text>
                        </TouchableOpacity>
                    </View>

                    <View style={styles.littleMenuContainer}>
                        <TouchableOpacity onPress={() => {
                            this.selectionPressed('170')
                        }}>
                            <Text style={styles.menuText}>Comp 170</Text>
                        </TouchableOpacity>
                    </View>
                </ScrollView>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#F5FCFF',
    },
    introTextContainer: {
        flex: 0.15,
        alignItems: 'center',
        justifyContent: 'center'
    },
    introText: {
        fontSize: 20,
        color: 'black'
    },
    optionContainer: {
        flex: 0.85
    },
    menuText: {
        textAlign: 'center',
        fontSize: 20,
        color: 'black'
    },
    littleMenuContainer: {
        margin: 10,
        backgroundColor: 'rgb(244, 176, 66)'
    },
});

<|endoftext|>


<commit_before>/**
 * @file CheckboxGroup component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import cloneDeep from 'lodash/cloneDeep';
import isArray from 'lodash/isArray';
import classNames from 'classnames';

import Checkbox from '../Checkbox';
import Theme from '../Theme';

import Position from '../_statics/Position';

import Util from '../_vendors/Util';

class CheckboxGroup extends Component {

    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            value: props.value
        };

    }

    changeHandler = item => {

        let value = cloneDeep(this.state.value);

        if (!value || !isArray(value)) {
            value = [item];
        } else {

            const index = value.findIndex(v => v.value === item.value);

            if (index > -1) {
                value.splice(index, 1);
            } else {
                value.push(item);
            }

        }

        this.setState({
            value
        }, () => {
            const {disabled, onChange} = this.props;
            !disabled && onChange && onChange(value);
        });

    };

    componentWillReceiveProps(nextProps) {
        if (nextProps.value && nextProps.value !== this.state.value) {
            this.setState({
                value: nextProps.value
            });
        }
    }

    render() {

        const {data} = this.props;

        if (!data) {
            return null;
        }

        const {
                className, style, theme, name, disabled, idProp, uncheckedIconCls, checkedIconCls, indeterminateIconCls,
                onCheck, onUncheck
            } = this.props,
            {value} = this.state,

            groupClassName = classNames('checkbox-group', {
                [className]: className
            });

        return (

                {
                    data && data.map((item, index) =>
                        <Checkbox key={idProp in item ? item[idProp] : index}
                                  className={item.className ? item.className : ''}
                                  style={item.style}
                                  theme={item.theme || theme}
                                  uncheckedIconCls={item.uncheckedIconCls || uncheckedIconCls}
                                  checkedIconCls={item.checkedIconCls || checkedIconCls}
                                  indeterminateIconCls={item.indeterminateIconCls || indeterminateIconCls}
                                  name={name}
                                  label={item.label}
                                  value={item.value}
                                  disabled={disabled || item.disabled}
                                  checked={value && value.findIndex(v => v.value === item.value) > -1}
                                  tip={item.tip}
                                  tipPosition={item.tipPosition}
                                  onChange={() => this.changeHandler(item)}
                                  onCheck={e => onCheck && onCheck(item, e)}
                                  onUncheck={e => onUncheck && onUncheck(item, e)}/>
                    )
                }

        );

    }
}

CheckboxGroup.propTypes = {

    className: PropTypes.string,
    style: PropTypes.object,

    /**
     * The CheckboxGroup theme.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    name: PropTypes.string,

    data: PropTypes.arrayOf(PropTypes.shape({

        className: PropTypes.string,
        style: PropTypes.object,
        theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

        label: PropTypes.any,
        value: PropTypes.any,

        disabled: PropTypes.bool,

        tip: PropTypes.any,
        tipPosition: PropTypes.oneOf(Util.enumerateValue(Position))

    })).isRequired,
    value: PropTypes.array,
    disabled: PropTypes.bool,

    idProp: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),

    uncheckedIconCls: PropTypes.string,
    checkedIconCls: PropTypes.string,
    indeterminateIconCls: PropTypes.string,

    onChange: PropTypes.func,

    onCheck: PropTypes.func,

    onUncheck: PropTypes.func

};

CheckboxGroup.defaultProps = {

    theme: Theme.DEFAULT,

    disabled: false,
    idProp: 'id'

};

export default CheckboxGroup;<commit_msg>update CheckboxGroup<commit_after>/**
 * @file CheckboxGroup component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import cloneDeep from 'lodash/cloneDeep';
import isArray from 'lodash/isArray';
import classNames from 'classnames';

import Checkbox from '../Checkbox';
import Theme from '../Theme';

import Position from '../_statics/Position';

import Util from '../_vendors/Util';

class CheckboxGroup extends Component {

    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            value: props.value
        };

    }

    changeHandler = item => {

        let value = cloneDeep(this.state.value);

        if (!value || !isArray(value)) {
            value = [item];
        } else {

            const index = value.findIndex(v => v.value === item.value);

            if (index > -1) {
                value.splice(index, 1);
            } else {
                value.push(item);
            }

        }

        this.setState({
            value
        }, () => {
            const {disabled, onChange} = this.props;
            !disabled && onChange && onChange(value);
        });

    };

    componentWillReceiveProps(nextProps) {
        if (nextProps.value && nextProps.value !== this.state.value) {
            this.setState({
                value: nextProps.value
            });
        }
    }

    render() {

        const {data} = this.props;

        if (!data) {
            return null;
        }

        const {
                className, style, theme, name, disabled, idProp, uncheckedIconCls, checkedIconCls, indeterminateIconCls,
                onCheck, onUncheck
            } = this.props,
            {value} = this.state,

            groupClassName = classNames('checkbox-group', {
                [className]: className
            });

        return (

                {
                    data && data.map((item, index) =>
                        <Checkbox key={item && idProp in item ? item[idProp] : index}
                                  className={item.className ? item.className : ''}
                                  style={item.style}
                                  theme={item.theme || theme}
                                  uncheckedIconCls={item.uncheckedIconCls || uncheckedIconCls}
                                  checkedIconCls={item.checkedIconCls || checkedIconCls}
                                  indeterminateIconCls={item.indeterminateIconCls || indeterminateIconCls}
                                  name={name}
                                  label={item.label}
                                  value={item.value}
                                  disabled={disabled || item.disabled}
                                  checked={value && value.findIndex(v => v.value === item.value) > -1}
                                  tip={item.tip}
                                  tipPosition={item.tipPosition}
                                  onChange={() => this.changeHandler(item)}
                                  onCheck={e => onCheck && onCheck(item, e)}
                                  onUncheck={e => onUncheck && onUncheck(item, e)}/>
                    )
                }

        );

    }
}

CheckboxGroup.propTypes = {

    className: PropTypes.string,
    style: PropTypes.object,

    /**
     * The CheckboxGroup theme.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    name: PropTypes.string,

    data: PropTypes.arrayOf(PropTypes.shape({

        className: PropTypes.string,
        style: PropTypes.object,
        theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

        label: PropTypes.any,
        value: PropTypes.any,

        disabled: PropTypes.bool,

        tip: PropTypes.any,
        tipPosition: PropTypes.oneOf(Util.enumerateValue(Position))

    })).isRequired,
    value: PropTypes.array,
    disabled: PropTypes.bool,

    idProp: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),

    uncheckedIconCls: PropTypes.string,
    checkedIconCls: PropTypes.string,
    indeterminateIconCls: PropTypes.string,

    onChange: PropTypes.func,

    onCheck: PropTypes.func,

    onUncheck: PropTypes.func

};

CheckboxGroup.defaultProps = {

    theme: Theme.DEFAULT,

    disabled: false,
    idProp: 'id'

};

export default CheckboxGroup;<|endoftext|>


<commit_before>// Parse some local storage objects!
// onNetwork now comes from an LSO
parseLocalStorage();

var danforthProxyURL = '.libproxy.wustl.edu';
var beckerProxyURL = '.beckerproxy.wustl.edu';

// Once per session, append "?holding=wustlmlib" to PubMed.
var rewrotePubMedThisSession = false;

// For debugging & options page
var networkTag = '';

// Set a 15 minute timeout interval to redetect the network status.
// Per poissoncdf(), this is probably friendly. Hell, MSVPN is way more active.
var refreshTimeoutHandle;
var refreshTimeoutMilliseconds = 900000;

// Are we at WashU? SLCH? WUSM? Off campus!?!
// TODO: At start, guess state from last session. If we're restoring lots of closed tabs on campus at restart, we might incorrectly assume off-campus.
function detectNetworkState() {
    // This will pass an HTTP_ORIGIN flag with the Chrome Extension ID so Becker
    // can contact me or block the extension if it causes any problems.
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "https://becker.wustl.edu/sites/all/themes/bml_theme2/js/network-indicator-script3.php", true);
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            if (xhr.status == 200) {
                var resp = JSON.parse(xhr.responseText.substring(1, xhr.responseText.length - 1));
                if (resp.networkTag == "WUSTL" || resp.networkTag == "WUSM") {
                    onNetwork = true;
                    localStorage.onNetwork = true; // I don't quite trust the LSO API yet, which is why we have this duplication.
                    beckerProxyURL = '.beckerproxy.wustl.edu';
                    networkTag = resp.networkTag;
                } else if (resp.networkTag == "SLCH" || resp.networkTag == "BJH" || resp.networkTag == "BJC") {
                    onNetwork = true; // TODO: Switch to false after excluding the short list of open journals at Becker.
                    localStorage.onNetwork = true;
                    beckerProxyURL = '.beckerproxy2a.wucon.wustl.edu';
                    networkTag = resp.networkTag;
                } else if (resp.networkTag == "OFF") {
                    onNetwork = false;
                    localStorage.onNetwork = false;
                    beckerProxyURL = '.beckerproxy.wustl.edu';
                    networkTag = resp.networkTag;
                } else {
                    // Something weird happened. We don't update onNetwork, since this could be
                    // a timed redetection that failed, so we'll preserve the previous state.
                    console.warn('Error: Unknown response from Becker:' + resp.networkTag);
                    writeToRemoteLog('Becker-Unknown-Response_' + resp.networkTag);
                    setTimeout(function(){ detectNetworkState(); }, 15000);
                }
                console.info('Detection event: onNetwork: ' + onNetwork + ', networkTag: ' + resp.networkTag);
                // TODO: Better way to detect network state changes? Is there an API for this?
                clearTimeout(refreshTimeoutHandle);
                refreshTimeoutHandle = setTimeout(function(){ detectNetworkState(); }, refreshTimeoutMilliseconds);
            } else {
                // Becker XHR returned status != 200. Maybe a network error?
                console.warn('Error from Becker: Retrying in 15s.');
                setTimeout(function(){ detectNetworkState(); }, 15000);
            }
        }
    };
    xhr.send();
}
detectNetworkState();

// Listener for the options page
chrome.extension.onRequest.addListener(
    function(request, sender, sendResponse) {
        if (request.operation == 'getstate') {
            sendResponse({networkTag: networkTag});
        } else if (request.operation == 'redetect') {
            // TODO: Rate limit this?
            console.info('Manual network redetection.');
            detectNetworkState();
            sendResponse({networkTag: networkTag});
        } else {
            console.warn('Unknown background listener request.');
            sendResponse({});
        }
    });

// The user wants auto-redirection and is off-network,
// so we perform URL parsing and (maybe) redirection
function checkURLforRedirection(tabId, parsedURL) {
    if (intersect_journals.hasOwnProperty(parsedURL.host)) {
        if (optPreferDanforth || (optEnableDanforth && !optEnableBecker)) {
            doRedirectToProxy(tabId, parsedURL, danforthProxyURL);
            return true;
        } else {
            doRedirectToProxy(tabId, parsedURL, beckerProxyURL);
            return true;
        }
    }

    // We're still here, so the journal is not in intersect_journals.
    if (optEnableDanforth) {
        if (danforth_journals.hasOwnProperty(parsedURL.host)) {
            doRedirectToProxy(tabId, parsedURL, danforthProxyURL);
            return true;
        }
    }
    if (optEnableBecker) {
        if (becker_journals.hasOwnProperty(parsedURL.host)) {
            doRedirectToProxy(tabId, parsedURL, beckerProxyURL);
            return true;
        }
    }
    return false;
}

// Redirect the page using the auto-determined proxy URL
// Modularize so we only parse the URL once (parsing is expensive!)
function doRedirectToProxy(tabId, parsed, proxyURL, appendString) {
    if (!appendString) {
        appendString = '';
    }
    var proxifiedURL = parsed.protocol + "://" + parsed.host + proxyURL + parsed.relative + appendString;
    try {
        chrome.tabs.update(tabId, {url: proxifiedURL});
    } catch(e) {
        // Pre-rendered magic tab that we can't update.
    }
}

// Send log data if there's a missed URL (someone had to manually click)
// or there's a Becker network detection error.
function writeToRemoteLog(theData) {
    var uploader = new XMLHttpRequest();
    var logString = "url:" + encodeURIComponent(theData) + ",auto:" + optAutoRedirect +
        ",onNet:" + onNetwork + ",tag:" + networkTag;
    uploader.open("GET", "https://update.epoxate.com/becker-extension/log.py?" + logString, true);
    uploader.send();
}

var rewroteAmazonThisSession = false;
// Listen for pending URL loads
// WARNING: Pre-rendered tabs from URL prediction (or link hints) cause oddities with onBeforeNavigate,
//          like inaccessible tabs. Be careful here!
function checkNavObject(frameId, tabId, url) {
    // Only listen in the main frame at frameId=0
    // Should we check for redirect loops, like with web.mit.edu?
    // frameId will match "0" for webNavigation object, and "Undefined" for tabs
    if (optAutoRedirect && frameId === 0) {
        var parsedURL = parseUri(url);
        if (optEnableBecker && parsedURL.host == "www.ncbi.nlm.nih.gov") {
            // Redirect PubMed journals to add ?holding flag.
            // This is useful whether we are on or off network.
            // TODO: Redirect for PMC articles, too?
            if (!rewrotePubMedThisSession) {
                if (parsedURL.relative == "/pubmed/" || parsedURL.relative == "/pubmed"
                    || parsedURL.relative == "/pmc/" || parsedURL.relative.match(/^\/pubmed\/\d{6,}$/)) {
                    // Redirect with no proxy url, but an appendString
                    doRedirectToProxy(tabId, parsedURL, "", "?holding=wustlmlib");
                    rewrotePubMedThisSession = true;
                    setTimeout(function(){ rewrotePubMedThisSession = false; }, 18000000);
                }
            }
        } else if (parsedURL.host == "www.amazon.com" && !rewroteAmazonThisSession) {
            // Rewrite url to append libproxy-20
            if ((parsedURL.relative.match(/dp\/0/) || parsedURL.relative.match(/\/B0/)) && !(parsedURL.relative.indexOf("tag=") >= 0)) {
                if (parsedURL.relative.indexOf("?") >= 0) {
                    doRedirectToProxy(tabId, parsedURL, "", "&tag=libproxy-20");
                } else {
                    doRedirectToProxy(tabId, parsedURL, "", "?tag=libproxy-20");
                }
                rewroteAmazonThisSession = true;
                setTimeout(function(){ rewroteAmazonThisSession = false; }, 36000000);
            }
        } else if (!onNetwork) {
            checkURLforRedirection(tabId, parsedURL);
        }
    }
};

chrome.webNavigation.onBeforeNavigate.addListener(function(navObject) {
    checkNavObject(navObject.frameId, navObject.tabId, navObject.url); } );
chrome.tabs.onCreated.addListener(function(navObject) {
    checkNavObject(0, navObject.id, navObject.url); } );
// Deal with pre-rendered tabs.
chrome.webNavigation.onTabReplaced.addListener(function(navObject) {
    chrome.tabs.get(navObject.tabId, function(tab) {
        checkNavObject(0, navObject.id, tab.url);
    }); });

// Alert users if they're trying to unnecessarily proxify things.
function showUserHint(warning) {
    var notice = webkitNotifications.createNotification(
        '../images/wucrest48.png',
        '',
        warning);
    notice.addEventListener('click', function() {notice.close();}); // Hide on click
    notice.show();
    // Auto-hide after five secs
    setTimeout(function(){ notice.close(); }, 5000);
}

// Listen to clicking on our button.
chrome.browserAction.onClicked.addListener(function (tab) {
    // Parse and redirect.
    var parsedURL = parseUri(tab.url);

    // We check for HTTP/HTTPS only (no chrome://), and that "proxy.wustl.edu" isn't at the end of the
    // string, otherwise we're probably already at [becker|lib]proxy.wustl.edu.
    if (parsedURL.protocol == 'http' || parsedURL.protocol == 'https') {
        if (parsedURL.host.substring(parsedURL.host.length - 15) != 'proxy.wustl.edu') {
            if (hint_urls.hasOwnProperty(parsedURL.host)) {
                // Warn users if they're doing something unnecessary.
                // In this case, do NOT redirect these URLs.
                showUserHint(hint_urls[parsedURL.host]);
            } else {
                // Warn on-network redirects, but don't block them
                if (onNetwork) {
                    showUserHint('You\'re on a BJC/WashU network, so you probably don\'t need a proxy.');
                }
                // TODO: Handle Danforth differently, since they don't seem to like SSL as much.
                // TODO: Consider dropping HTTPS support? Is this useful?
                if (parsedURL.protocol == 'https') {
                    parsedURL.host = parsedURL.host.replace(/\./g, '-');
                }
                if (optPreferDanforth || (!optEnableBecker && optEnableDanforth)) {
                    doRedirectToProxy(tab.id, parsedURL, danforthProxyURL);
                } else {
                    doRedirectToProxy(tab.id, parsedURL, beckerProxyURL);
                }
            }
        } else {
            // User clicked even though we're already proxified
            showUserHint('Looks like you\'re already using the proxy. No need to click again!');
        }
    } else {
        showUserHint('This extension only works on websites starting with http:// or https://');
    }

    // If we've been manually clicked (and auto-triggering is enabled), the journal
    // is missing from our list. Let's send the journal URL to the cloud logger.
    // We log non-http/https here in case we're missing something (ftp sites for journal data?)
    if (!tab.incognito && !optUsageOptOut) {
        writeToRemoteLog(parsedURL.host);
    }
});
<commit_msg>Drop old code, change NCBI timeout to 3 hours.<commit_after>// Parse some local storage objects!
// onNetwork now comes from an LSO
parseLocalStorage();

var danforthProxyURL = '.libproxy.wustl.edu';
var beckerProxyURL = '.beckerproxy.wustl.edu';

// Once per session, append "?holding=wustlmlib" to PubMed.
var rewrotePubMedThisSession = false;

// For debugging & options page
var networkTag = '';

// Set a 15 minute timeout interval to redetect the network status.
// Per poissoncdf(), this is probably friendly. Hell, MSVPN is way more active.
var refreshTimeoutHandle;
var refreshTimeoutMilliseconds = 900000;

// Are we at WashU? SLCH? WUSM? Off campus!?!
// TODO: At start, guess state from last session. If we're restoring lots of closed tabs on campus at restart, we might incorrectly assume off-campus.
function detectNetworkState() {
    // This will pass an HTTP_ORIGIN flag with the Chrome Extension ID so Becker
    // can contact me or block the extension if it causes any problems.
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "https://becker.wustl.edu/sites/all/themes/bml_theme2/js/network-indicator-script3.php", true);
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            if (xhr.status == 200) {
                var resp = JSON.parse(xhr.responseText.substring(1, xhr.responseText.length - 1));
                if (resp.networkTag == "WUSTL" || resp.networkTag == "WUSM") {
                    onNetwork = true;
                    localStorage.onNetwork = true; // I don't quite trust the LSO API yet, which is why we have this duplication.
                    beckerProxyURL = '.beckerproxy.wustl.edu';
                    networkTag = resp.networkTag;
                } else if (resp.networkTag == "SLCH" || resp.networkTag == "BJH" || resp.networkTag == "BJC") {
                    onNetwork = true; // TODO: Switch to false after excluding the short list of open journals at Becker.
                    localStorage.onNetwork = true;
                    beckerProxyURL = '.beckerproxy2a.wucon.wustl.edu';
                    networkTag = resp.networkTag;
                } else if (resp.networkTag == "OFF") {
                    onNetwork = false;
                    localStorage.onNetwork = false;
                    beckerProxyURL = '.beckerproxy.wustl.edu';
                    networkTag = resp.networkTag;
                } else {
                    // Something weird happened. We don't update onNetwork, since this could be
                    // a timed redetection that failed, so we'll preserve the previous state.
                    console.warn('Error: Unknown response from Becker:' + resp.networkTag);
                    writeToRemoteLog('Becker-Unknown-Response_' + resp.networkTag);
                    setTimeout(function(){ detectNetworkState(); }, 15000);
                }
                console.info('Detection event: onNetwork: ' + onNetwork + ', networkTag: ' + resp.networkTag);
                // TODO: Better way to detect network state changes? Is there an API for this?
                clearTimeout(refreshTimeoutHandle);
                refreshTimeoutHandle = setTimeout(function(){ detectNetworkState(); }, refreshTimeoutMilliseconds);
            } else {
                // Becker XHR returned status != 200. Maybe a network error?
                console.warn('Error from Becker: Retrying in 15s.');
                setTimeout(function(){ detectNetworkState(); }, 15000);
            }
        }
    };
    xhr.send();
}
detectNetworkState();

// Listener for the options page
chrome.extension.onRequest.addListener(
    function(request, sender, sendResponse) {
        if (request.operation == 'getstate') {
            sendResponse({networkTag: networkTag});
        } else if (request.operation == 'redetect') {
            console.info('Manual network redetection.');
            detectNetworkState();
            sendResponse({networkTag: networkTag});
        } else {
            console.warn('Unknown background listener request.');
            sendResponse({});
        }
    });

// The user wants auto-redirection and is off-network,
// so we perform URL parsing and (maybe) redirection
function checkURLforRedirection(tabId, parsedURL) {
    if (intersect_journals.hasOwnProperty(parsedURL.host)) {
        if (optPreferDanforth || (optEnableDanforth && !optEnableBecker)) {
            doRedirectToProxy(tabId, parsedURL, danforthProxyURL);
            return true;
        } else {
            doRedirectToProxy(tabId, parsedURL, beckerProxyURL);
            return true;
        }
    }

    // We're still here, so the journal is not in intersect_journals.
    if (optEnableDanforth) {
        if (danforth_journals.hasOwnProperty(parsedURL.host)) {
            doRedirectToProxy(tabId, parsedURL, danforthProxyURL);
            return true;
        }
    }
    if (optEnableBecker) {
        if (becker_journals.hasOwnProperty(parsedURL.host)) {
            doRedirectToProxy(tabId, parsedURL, beckerProxyURL);
            return true;
        }
    }
    return false;
}

// Redirect the page using the auto-determined proxy URL
// Modularize so we only parse the URL once (parsing is expensive!)
function doRedirectToProxy(tabId, parsed, proxyURL, appendString) {
    if (!appendString) {
        appendString = '';
    }
    var proxifiedURL = parsed.protocol + "://" + parsed.host + proxyURL + parsed.relative + appendString;
    try {
        chrome.tabs.update(tabId, {url: proxifiedURL});
    } catch(e) {
        // Pre-rendered magic tab that we can't update.
    }
}

// Send log data if there's a missed URL (someone had to manually click)
// or there's a Becker network detection error.
function writeToRemoteLog(theData) {
    var uploader = new XMLHttpRequest();
    var logString = "url:" + encodeURIComponent(theData) + ",auto:" + optAutoRedirect +
        ",onNet:" + onNetwork + ",tag:" + networkTag;
    uploader.open("GET", "https://update.epoxate.com/becker-extension/log.py?" + logString, true);
    uploader.send();
}

// Listen for pending URL loads
// WARNING: Pre-rendered tabs from URL prediction (or link hints) cause oddities with onBeforeNavigate,
//          like inaccessible tabs. Be careful here!
function checkNavObject(frameId, tabId, url) {
    // Only listen in the main frame at frameId=0
    // Should we check for redirect loops, like with web.mit.edu?
    // frameId will match "0" for webNavigation object, and "Undefined" for tabs
    if (optAutoRedirect && frameId === 0) {
        var parsedURL = parseUri(url);
        if (optEnableBecker && parsedURL.host == "www.ncbi.nlm.nih.gov") {
            // Redirect PubMed journals to add ?holding flag.
            // This is useful whether we are on or off network.
            // TODO: Redirect for PMC articles, too?
            if (!rewrotePubMedThisSession) {
                if (parsedURL.relative == "/pubmed/" || parsedURL.relative == "/pubmed"
                    || parsedURL.relative == "/pmc/" || parsedURL.relative.match(/^\/pubmed\/\d{6,}$/)) {
                    // Redirect with no proxy url, but an appendString
                    doRedirectToProxy(tabId, parsedURL, "", "?holding=wustlmlib");
                    rewrotePubMedThisSession = true;
                    setTimeout(function(){ rewrotePubMedThisSession = false; }, 10800000);
                }
            }
        }  else if (!onNetwork) {
            checkURLforRedirection(tabId, parsedURL);
        }
    }
};

chrome.webNavigation.onBeforeNavigate.addListener(function(navObject) {
    checkNavObject(navObject.frameId, navObject.tabId, navObject.url); } );
chrome.tabs.onCreated.addListener(function(navObject) {
    checkNavObject(0, navObject.id, navObject.url); } );
// Deal with pre-rendered tabs.
chrome.webNavigation.onTabReplaced.addListener(function(navObject) {
    chrome.tabs.get(navObject.tabId, function(tab) {
        checkNavObject(0, navObject.id, tab.url);
    }); });

// Alert users if they're trying to unnecessarily proxify things.
function showUserHint(warning) {
    var notice = webkitNotifications.createNotification(
        '../images/wucrest48.png',
        '',
        warning);
    notice.addEventListener('click', function() {notice.close();}); // Hide on click
    notice.show();
    // Auto-hide after five secs
    setTimeout(function(){ notice.close(); }, 5000);
}

// Listen to clicking on our button.
chrome.browserAction.onClicked.addListener(function (tab) {
    // Parse and redirect.
    var parsedURL = parseUri(tab.url);

    // We check for HTTP/HTTPS only (no chrome://), and that "proxy.wustl.edu" isn't at the end of the
    // string, otherwise we're probably already at [becker|lib]proxy.wustl.edu.
    if (parsedURL.protocol == 'http' || parsedURL.protocol == 'https') {
        if (parsedURL.host.substring(parsedURL.host.length - 15) != 'proxy.wustl.edu') {
            if (hint_urls.hasOwnProperty(parsedURL.host)) {
                // Warn users if they're doing something unnecessary.
                // In this case, do NOT redirect these URLs.
                showUserHint(hint_urls[parsedURL.host]);
            } else {
                // Warn on-network redirects, but don't block them
                if (onNetwork) {
                    showUserHint('You\'re on a BJC/WashU network, so you probably don\'t need a proxy.');
                }
                // TODO: Handle Danforth differently, since they don't seem to like SSL as much.
                // TODO: Consider dropping HTTPS support? Is this useful?
                if (parsedURL.protocol == 'https') {
                    parsedURL.host = parsedURL.host.replace(/\./g, '-');
                }
                if (optPreferDanforth || (!optEnableBecker && optEnableDanforth)) {
                    doRedirectToProxy(tab.id, parsedURL, danforthProxyURL);
                } else {
                    doRedirectToProxy(tab.id, parsedURL, beckerProxyURL);
                }
            }
        } else {
            // User clicked even though we're already proxified
            showUserHint('Looks like you\'re already using the proxy. No need to click again!');
        }
    } else {
        showUserHint('This extension only works on websites starting with http:// or https://');
    }

    // If we've been manually clicked (and auto-triggering is enabled), the journal
    // is missing from our list. Let's send the journal URL to the cloud logger.
    // We log non-http/https here in case we're missing something (ftp sites for journal data?)
    if (!tab.incognito && !optUsageOptOut) {
        writeToRemoteLog(parsedURL.host);
    }
});
<|endoftext|>


<commit_before>(function (Employee, undefined) {
    var module = angular.module('employeeApp', []);
    module.controller('employeeCtrl', function ($scope, $http) {
        $scope.EmployeeApiUri = "/api/Employees/";
        $scope.EmployeeList = [];
        $scope.Employee = { EmployeeID: '', FirstName: '', LastName: '', Phone: '', Email: '', IsAdd: false };

        $scope.loadEmployeeList = function loadEmployeeList() {
            $http.get($scope.EmployeeApiUri).then(function (response) {
                $scope.EmployeeList = response.data;
            }, function (e) {
                alert(e.data.Message);
            });
        }

        $scope.addEmployee = function () {
            if ($scope.Employee.IsAdd) {
                $http.post($scope.EmployeeApiUri, $scope.Employee).then(function (response) {
                    if (response.data) {
                        $scope.Employee = { EmployeeID: '', FirstName: '', LastName: '', Phone: '', Email: '' };
                        $scope.loadEmployeeList();
                        $scope.closeAddForm();
                    } else {
                        //save fail
                    }
                }, function (e) {
                    alert(e.data.Message);
                });
            } else {
                $http.put($scope.EmployeeApiUri + $scope.Employee.EmployeeID, $scope.Employee).then(function (response) {
                    if (response.data) {
                        $scope.Employee = { EmployeeID: '', FirstName: '', LastName: '', Phone: '', Email: '' };
                        $scope.loadEmployeeList();
                        $scope.closeAddForm();
                    } else {
                        //save fail
                    }
                }, function (e) {
                    alert(e.data.Message);
                });
            }
        };

        // Delete one customer based on id.
        $scope.delEmployee = function (id) {
            if (confirm("Are you sure you want to delete this customer?")) {
                // todo code for deletion
                $http.delete($scope.EmployeeApiUri + id).then(function (response) {
                    if (response.data) {
                        alert("Deleted successfully.");
                        // Refresh list
                        $scope.loadEmployeeList();
                    } else {
                        //save fail
                    }
                }, function (e) {
                    alert(e.data.Message);
                });
            }
        };

        $scope.openEditForm = function (emp) {
            $scope.Employee.IsAdd = false;
            $scope.Employee.EmployeeID = emp.EmployeeID.trim();
            $scope.Employee.FirstName = emp.FirstName.trim();
            $scope.Employee.LastName = emp.LastName.trim();
            $scope.Employee.Phone = emp.Phone.trim();
            $scope.Employee.Email = emp.Email.trim();
            $('#formModal').modal({
                keyboard: true,
            });
        };

        $scope.openAddForm = function () {
            $scope.Employee = { EmployeeID: '', FirstName: '', LastName: '', Phone: '', Email: '', IsAdd: true };
            $('#formModal').modal({
                keyboard: true,
            });
        };

        $scope.closeAddForm = function () {
            $('#formModal').modal('hide');
        };

        $scope.loadEmployeeList();
    });
}(window.Employee = window.Employee || {}));<commit_msg>code refactor<commit_after>(function (Employee, undefined) {
    var employeeApp = angular.module('employeeApp', []);

    employeeApp.factory('employeeService', ['$http', function ($http) {
        var employeeApiUri = "/api/Employees/";
        var employeeService = {};
        employeeService.getEmployees = function () {
            return $http.get(employeeApiUri);
        };
        employeeService.addEmployee = function (d) {
            return $http.post(employeeApiUri, d);
        };
        employeeService.updateEmployee = function (d) {
            return $http.put(employeeApiUri + d.EmployeeID, d);
        };
        employeeService.deleteEmployee = function (id) {
            return $http.delete(employeeApiUri + id);
        };
        return employeeService;
    }]);

    employeeApp.controller('employeeCtrl', function ($scope, employeeService) {
        $scope.EmployeeList = [];
        $scope.Employee = { EmployeeID: '', FirstName: '', LastName: '', Phone: '', Email: '', IsAdd: false };

        $scope.loadEmployeeList = function loadEmployeeList() {
            employeeService.getEmployees().then(function (response) {
                $scope.EmployeeList = response.data;
            }, function (e) {
                alert(e.data.Message);
            });
        }

        $scope.addEmployee = function () {
            if ($scope.Employee.IsAdd) {
                employeeService.addEmployee($scope.Employee).then(function (response) {
                    if (response.data) {
                        $scope.Employee = { EmployeeID: '', FirstName: '', LastName: '', Phone: '', Email: '' };
                        $scope.loadEmployeeList();
                        $scope.closeAddForm();
                    } else {
                        //save fail
                    }
                }, function (e) {
                    alert(e.data.Message);
                });
            } else {
                employeeService.updateEmployee($scope.Employee).then(function (response) {
                    if (response.data) {
                        $scope.Employee = { EmployeeID: '', FirstName: '', LastName: '', Phone: '', Email: '' };
                        $scope.loadEmployeeList();
                        $scope.closeAddForm();
                    } else {
                        //save fail
                    }
                }, function (e) {
                    alert(e.data.Message);
                });
            }
        };

        // Delete one customer based on id.
        $scope.delEmployee = function (id) {
            if (confirm("Are you sure you want to delete this customer?")) {
                // todo code for deletion
                employeeService.deleteEmployee(id).then(function (response) {
                    if (response.data) {
                        alert("Deleted successfully.");
                        // Refresh list
                        $scope.loadEmployeeList();
                    } else {
                        //save fail
                    }
                }, function (e) {
                    alert(e.data.Message);
                });
            }
        };

        $scope.openEditForm = function (emp) {
            $scope.Employee.IsAdd = false;
            $scope.Employee.EmployeeID = emp.EmployeeID.trim();
            $scope.Employee.FirstName = emp.FirstName.trim();
            $scope.Employee.LastName = emp.LastName.trim();
            $scope.Employee.Phone = emp.Phone.trim();
            $scope.Employee.Email = emp.Email.trim();
            $('#formModal').modal({
                keyboard: true,
            });
        };

        $scope.openAddForm = function () {
            $scope.Employee = { EmployeeID: '', FirstName: '', LastName: '', Phone: '', Email: '', IsAdd: true };
            $('#formModal').modal({
                keyboard: true,
            });
        };

        $scope.closeAddForm = function () {
            $('#formModal').modal('hide');
        };

        $scope.loadEmployeeList();
    });
}(window.Employee = window.Employee || {}));<|endoftext|>