<commit_before>import Ember from 'ember';

/**
 * An Ember service for doing things to files.
 * Essentially a wrapper for the Waterbutler API.
 * http://waterbutler.readthedocs.io/
 *
 * @class file-manager
 * @extends Ember.Service
 */

export default Ember.Service.extend({
    session: Ember.inject.service(),
    store: Ember.inject.service(),

    /**
     * Get a URL to download the given file.
     *
     * @method getDownloadUrl
     * @param {file} file A `file` model
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the URL.
     * @param {Object} [options.query.version] `file-version` ID
     * @return {String} Download URL
     */
    getDownloadUrl(file, options = {}) {
        let url = file.get('links.download');

        if (file.isFolder) {
            options.query.zip = '';
        }
        let queryString = Ember.$.param(options.query);
        return `${url}?${queryString}`;
    },

    /**
     * Download the contents of the given file.
     *
     * @method getContents
     * @param {file} file A `file` model with `isFolder == false`.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves to the file contents or rejects
     * with an error message.
     */
    getContents(file, options = {}) {
        let url = file.get('links.download');
        return this._waterbutlerRequest('GET', url, options);
    },

    /**
     * Upload a new version of an existing file.
     *
     * @method updateContents
     * @param {file} file A `file` model with `isFolder == false`.
     * @param {Object} contents A native `File` object or another appropriate
     * payload for uploading.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves to the updated `file` model or
     * rejects with an error message.
     */
    updateContents(file, contents, options = {}) {
        let url = file.get('links').upload;
        if (!options.query) {
            options.query = {};
        }
        options.query.kind = 'file';
        options.data = contents;

        let p = this._waterbutlerRequest('PUT', url, options);
        return p.then(() => this._reloadModel(file));
    },

    /**
     * Check out a file, so only the current user can modify it.
     *
     * @method checkOut
     * @param {file} file `file` model with `isFolder == false`.
     * @return {Promise} Promise that resolves on success or rejects with an
     * error message.
     */
    checkOut(file) {
        return Ember.run(() => {
            let userID = this.get('session.data.authenticated.id');
            file.set('checkout', userID);
            return file.save().catch((error) => {
                file.rollbackAttributes();
                throw error;
            });
        });
    },

    /**
     * Check in a file, so anyone with permission can modify it.
     *
     * @method checkOut
     * @param {file} file `file` model with `isFolder == false`.
     * @return {Promise} Promise that resolves on success or rejects with an
     * error message.
     */
    checkIn(file) {
        return Ember.run(() => {
            file.set('checkout', null);
            return file.save().catch((error) => {
                file.rollbackAttributes();
                throw error;
            });
        });
    },

    /**
     * Create a new folder
     *
     * @method addSubfolder
     * @param {file} folder Location of the new folder, a `file` model with
     * `isFolder == true`.
     * @param {String} name Name of the folder to create.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves to the new folder's model or
     * rejects with an error message.
     */
    addSubfolder(folder, name, options = {}) {
        let url = folder.get('links').new_folder;
        if (!options.query) {
            options.query = {};
        }
        options.query.name = name;
        options.query.kind = 'folder';

        // HACK: This is the only WB link that already has a query string
        let queryStart = url.search(/\?kind=folder$/);
        if (queryStart > -1) {
            url = url.slice(0, queryStart);
        }
        let p = this._waterbutlerRequest('PUT', url, options);
        return p.then(() => this._getNewFileInfo(folder, name));
    },

    /**
     * Upload a file
     *
     * @method uploadFile
     * @param {file} folder Location of the new file, a `file` model with
     * `isFolder == true`.
     * @param {String} name Name of the new file.
     * @param {Object} contents A native `File` object or another appropriate
     * payload for uploading.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves to the new file's model or
     * rejects with an error message.
     */
    uploadFile(folder, name, contents, options = {}) {
        let url = folder.get('links').upload;
        options.data = contents;
        if (!options.query) {
            options.query = {};
        }
        options.query.name = name;
        options.query.kind = 'file';

        let p = this._waterbutlerRequest('PUT', url, options);
        return p.then(() => this._getNewFileInfo(folder, name));
    },

    /**
     * Rename a file or folder
     *
     * @method rename
     * @param {file} file `file` model to rename.
     * @param {String} newName New name for the file.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves to the updated `file` model or
     * rejects with an error message.
     */
    rename(file, newName, options = {}) {
        let url = file.get('links').move;
        options.data = JSON.stringify({ action: 'rename', rename: newName });

        let p = this._waterbutlerRequest('POST', url, options);
        return p.then(() => this._reloadModel(file));
    },

    /**
     * Move (or copy) a file or folder
     *
     * @method move
     * @param {file} file `file` model to move.
     * @param {file} targetFolder Where to move the file, a `file` model with
     * `isFolder == true`.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @param {String} [options.data.rename] If specified, also rename the file
     * to the given name.
     * @param {String} [options.data.resource] Optional node ID. If specified,
     * move the file to that node.
     * @param {String} [options.data.provider] Optional provider name. If
     * specified, move the file to that provider.
     * @param {String} [options.data.action='move'] Either 'move' or 'copy'.
     * @param {String} [options.data.conflict='replace'] Specifies what to do if
     * a file of the same name already exists in the target folder. If 'keep',
     * rename this file to avoid conflict. If replace, the existing file is
     * destroyed.
     * @return {Promise} Promise that resolves to the the updated (or newly
     * created) `file` model or rejects with an error message.
     */
    move(file, targetFolder, options = {}) {
        let url = file.get('links').move;
        let defaultData = {
            action: 'move',
            path: targetFolder.get('path')
        };
        Ember.$.extend(defaultData, options.data);
        options.data = JSON.stringify(defaultData);

        let p = this._waterbutlerRequest('POST', url, options);
        return p.then((wbResponse) => {
            let name = wbResponse.data.attributes.name;
            return this._getNewFileInfo(targetFolder, name);
        });
    },

    /**
     * Copy a file or folder.
     * Convenience method for `move` with `options.copy == true`.
     *
     * @method copy
     * @param {file} file `file` model to copy.
     * @param {file} targetFolder Where to copy the file, a `file` model with
     * `isFolder == true`.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @param {String} [options.data.rename] If specified, also rename the file
     * to the given name.
     * @param {String} [options.data.resource] Optional node ID. If specified,
     * move the file to that node.
     * @param {String} [options.data.provider] Optional provider name. If
     * specified, move the file to that provider.
     * @param {String} [options.data.conflict='replace'] Specifies what to do if
     * a file of the same name already exists in the target folder. If 'keep',
     * rename this file to avoid conflict. If replace, the existing file is
     * destroyed.
     * @return {Promise} Promise that resolves to the the new `file` model or
     * rejects with an error message.
     */
    copy(file, targetFolder, options={}) {
        if (!options.data) {
            options.data = {};
        }
        options.data.action = 'copy';
        return this.move(file, targetFolder, options);
    },

    /**
     * Delete a file or folder
     *
     * @method deleteFile
     * @param {file} file `file` model to delete.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves on success or rejects with an
     * error message.
     */
    deleteFile(file, options = {}) {
        let url = file.get('links').delete;
        let p = this._waterbutlerRequest('DELETE', url, options);
        return p.then(() => file.get('parentFolder').then((parent) => {
                if (parent) {
                    return this._reloadModel(parent.get('files'));
                } else {
                    this.get('store').unloadRecord(file);
                    return true;
                }
            })
        );
    },

    /**
     * Check whether the given url corresponds to a model that is currently
     * reloading after a file operation.
     *
     * Used by `mixin:file-cache-bypass` to avoid a race condition where the
     * cache might return stale, inaccurate data.
     *
     * @method isReloadingUrl
     * @param {String} url
     * @return {Boolean} `true` if `url` corresponds to a pending reload on a
     * model immediately after a Waterbutler action, otherwise `false`.
     */
    isReloadingUrl(url) {
        return !!this._reloadingUrls[url];
    },

    /**
     * Hash set of URLs for `model.reload()` calls that are still pending.
     *
     * @property _reloadingUrls
     * @private
     */
    _reloadingUrls: {},

    /**
     * Force-reload a model from the API.
     *
     * @method _reloadModel
     * @private
     * @param {Object} model `file` model or a `files` relationship
     * @return {Promise} Promise that resolves to the reloaded model or
     * rejects with an error message.
     */
    _reloadModel(model) {
        // If it's a file model, it has its own URL in `links.info`.
        let reloadUrl = model.get('links.info');
        if (!reloadUrl) {
            // If it's not a file model, it must be a relationship.
            // HACK: Looking at Ember's privates.
            reloadUrl = model.get('content.relationship.link');
        }
        if (reloadUrl) {
            this._reloadingUrls[reloadUrl] = true;
        }

        return model.reload().then((freshModel) => {
            if (reloadUrl) {
                delete this._reloadingUrls[reloadUrl];
            }
            return freshModel;
        }).catch((error) => {
            if (reloadUrl) {
                delete this._reloadingUrls[reloadUrl];
            }
            throw error;
        });
    },

    /**
     * Make a Waterbutler request
     *
     * @method _waterbutlerRequest
     * @private
     * @param {String} method HTTP method for the request.
     * @param {String} url Waterbutler URL.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves to the data returned from the
     * server on success, or rejects with an error message.
     */
    _waterbutlerRequest(method, url, options = {}) {
        if (options.query) {
            let queryString = Ember.$.param(options.query);
            url = `${url}?${queryString}`;
        }

        let headers = {};
        this.get('session').authorize('authorizer:osf-token', (headerName, content) => {
            headers[headerName] = content;
        });

        return new Ember.RSVP.Promise((resolve, reject) => {
            let p = Ember.$.ajax(url, {
                method,
                headers,
                data: options.data,
                processData: false
            });
            p.done((data) => resolve(data));
            p.fail((_, __, error) => reject(error));
        });
    },

    /**
     * Get the `file` model for a newly created file.
     *
     * @method _getNewFileInfo
     * @private
     * @param {file} parentFolder Model for the new file's parent folder.
     * @param {String} name Name of the new file.
     * @return {Promise} Promise that resolves to the new file's model or
     * rejects with an error message.
     */
    _getNewFileInfo(parentFolder, name) {
        let p = this._reloadModel(parentFolder.get('files'));
        return p.then((files) => files.findBy('name', name));
    }
});
<commit_msg>Fix error on downloading file without params<commit_after>import Ember from 'ember';

/**
 * An Ember service for doing things to files.
 * Essentially a wrapper for the Waterbutler API.
 * http://waterbutler.readthedocs.io/
 *
 * @class file-manager
 * @extends Ember.Service
 */

export default Ember.Service.extend({
    session: Ember.inject.service(),
    store: Ember.inject.service(),

    /**
     * Get a URL to download the given file.
     *
     * @method getDownloadUrl
     * @param {file} file A `file` model
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the URL.
     * @param {Object} [options.query.version] `file-version` ID
     * @return {String} Download URL
     */
    getDownloadUrl(file, options = {}) {
        let url = file.get('links.download');

        if (!options.query) {
            options.query = {};
        }
        if (file.get('isFolder')) {
            options.query.zip = '';
        }
        let queryString = Ember.$.param(options.query);
        if (queryString.length) {
            return `${url}?${queryString}`;
        } else {
            return url;
        }
    },

    /**
     * Download the contents of the given file.
     *
     * @method getContents
     * @param {file} file A `file` model with `isFolder == false`.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves to the file contents or rejects
     * with an error message.
     */
    getContents(file, options = {}) {
        let url = file.get('links.download');
        return this._waterbutlerRequest('GET', url, options);
    },

    /**
     * Upload a new version of an existing file.
     *
     * @method updateContents
     * @param {file} file A `file` model with `isFolder == false`.
     * @param {Object} contents A native `File` object or another appropriate
     * payload for uploading.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves to the updated `file` model or
     * rejects with an error message.
     */
    updateContents(file, contents, options = {}) {
        let url = file.get('links').upload;
        if (!options.query) {
            options.query = {};
        }
        options.query.kind = 'file';
        options.data = contents;

        let p = this._waterbutlerRequest('PUT', url, options);
        return p.then(() => this._reloadModel(file));
    },

    /**
     * Check out a file, so only the current user can modify it.
     *
     * @method checkOut
     * @param {file} file `file` model with `isFolder == false`.
     * @return {Promise} Promise that resolves on success or rejects with an
     * error message.
     */
    checkOut(file) {
        return Ember.run(() => {
            let userID = this.get('session.data.authenticated.id');
            file.set('checkout', userID);
            return file.save().catch((error) => {
                file.rollbackAttributes();
                throw error;
            });
        });
    },

    /**
     * Check in a file, so anyone with permission can modify it.
     *
     * @method checkOut
     * @param {file} file `file` model with `isFolder == false`.
     * @return {Promise} Promise that resolves on success or rejects with an
     * error message.
     */
    checkIn(file) {
        return Ember.run(() => {
            file.set('checkout', null);
            return file.save().catch((error) => {
                file.rollbackAttributes();
                throw error;
            });
        });
    },

    /**
     * Create a new folder
     *
     * @method addSubfolder
     * @param {file} folder Location of the new folder, a `file` model with
     * `isFolder == true`.
     * @param {String} name Name of the folder to create.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves to the new folder's model or
     * rejects with an error message.
     */
    addSubfolder(folder, name, options = {}) {
        let url = folder.get('links').new_folder;
        if (!options.query) {
            options.query = {};
        }
        options.query.name = name;
        options.query.kind = 'folder';

        // HACK: This is the only WB link that already has a query string
        let queryStart = url.search(/\?kind=folder$/);
        if (queryStart > -1) {
            url = url.slice(0, queryStart);
        }
        let p = this._waterbutlerRequest('PUT', url, options);
        return p.then(() => this._getNewFileInfo(folder, name));
    },

    /**
     * Upload a file
     *
     * @method uploadFile
     * @param {file} folder Location of the new file, a `file` model with
     * `isFolder == true`.
     * @param {String} name Name of the new file.
     * @param {Object} contents A native `File` object or another appropriate
     * payload for uploading.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves to the new file's model or
     * rejects with an error message.
     */
    uploadFile(folder, name, contents, options = {}) {
        let url = folder.get('links').upload;
        options.data = contents;
        if (!options.query) {
            options.query = {};
        }
        options.query.name = name;
        options.query.kind = 'file';

        let p = this._waterbutlerRequest('PUT', url, options);
        return p.then(() => this._getNewFileInfo(folder, name));
    },

    /**
     * Rename a file or folder
     *
     * @method rename
     * @param {file} file `file` model to rename.
     * @param {String} newName New name for the file.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves to the updated `file` model or
     * rejects with an error message.
     */
    rename(file, newName, options = {}) {
        let url = file.get('links').move;
        options.data = JSON.stringify({ action: 'rename', rename: newName });

        let p = this._waterbutlerRequest('POST', url, options);
        return p.then(() => this._reloadModel(file));
    },

    /**
     * Move (or copy) a file or folder
     *
     * @method move
     * @param {file} file `file` model to move.
     * @param {file} targetFolder Where to move the file, a `file` model with
     * `isFolder == true`.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @param {String} [options.data.rename] If specified, also rename the file
     * to the given name.
     * @param {String} [options.data.resource] Optional node ID. If specified,
     * move the file to that node.
     * @param {String} [options.data.provider] Optional provider name. If
     * specified, move the file to that provider.
     * @param {String} [options.data.action='move'] Either 'move' or 'copy'.
     * @param {String} [options.data.conflict='replace'] Specifies what to do if
     * a file of the same name already exists in the target folder. If 'keep',
     * rename this file to avoid conflict. If replace, the existing file is
     * destroyed.
     * @return {Promise} Promise that resolves to the the updated (or newly
     * created) `file` model or rejects with an error message.
     */
    move(file, targetFolder, options = {}) {
        let url = file.get('links').move;
        let defaultData = {
            action: 'move',
            path: targetFolder.get('path')
        };
        Ember.$.extend(defaultData, options.data);
        options.data = JSON.stringify(defaultData);

        let p = this._waterbutlerRequest('POST', url, options);
        return p.then((wbResponse) => {
            let name = wbResponse.data.attributes.name;
            return this._getNewFileInfo(targetFolder, name);
        });
    },

    /**
     * Copy a file or folder.
     * Convenience method for `move` with `options.copy == true`.
     *
     * @method copy
     * @param {file} file `file` model to copy.
     * @param {file} targetFolder Where to copy the file, a `file` model with
     * `isFolder == true`.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @param {String} [options.data.rename] If specified, also rename the file
     * to the given name.
     * @param {String} [options.data.resource] Optional node ID. If specified,
     * move the file to that node.
     * @param {String} [options.data.provider] Optional provider name. If
     * specified, move the file to that provider.
     * @param {String} [options.data.conflict='replace'] Specifies what to do if
     * a file of the same name already exists in the target folder. If 'keep',
     * rename this file to avoid conflict. If replace, the existing file is
     * destroyed.
     * @return {Promise} Promise that resolves to the the new `file` model or
     * rejects with an error message.
     */
    copy(file, targetFolder, options={}) {
        if (!options.data) {
            options.data = {};
        }
        options.data.action = 'copy';
        return this.move(file, targetFolder, options);
    },

    /**
     * Delete a file or folder
     *
     * @method deleteFile
     * @param {file} file `file` model to delete.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves on success or rejects with an
     * error message.
     */
    deleteFile(file, options = {}) {
        let url = file.get('links').delete;
        let p = this._waterbutlerRequest('DELETE', url, options);
        return p.then(() => file.get('parentFolder').then((parent) => {
                if (parent) {
                    return this._reloadModel(parent.get('files'));
                } else {
                    this.get('store').unloadRecord(file);
                    return true;
                }
            })
        );
    },

    /**
     * Check whether the given url corresponds to a model that is currently
     * reloading after a file operation.
     *
     * Used by `mixin:file-cache-bypass` to avoid a race condition where the
     * cache might return stale, inaccurate data.
     *
     * @method isReloadingUrl
     * @param {String} url
     * @return {Boolean} `true` if `url` corresponds to a pending reload on a
     * model immediately after a Waterbutler action, otherwise `false`.
     */
    isReloadingUrl(url) {
        return !!this._reloadingUrls[url];
    },

    /**
     * Hash set of URLs for `model.reload()` calls that are still pending.
     *
     * @property _reloadingUrls
     * @private
     */
    _reloadingUrls: {},

    /**
     * Force-reload a model from the API.
     *
     * @method _reloadModel
     * @private
     * @param {Object} model `file` model or a `files` relationship
     * @return {Promise} Promise that resolves to the reloaded model or
     * rejects with an error message.
     */
    _reloadModel(model) {
        // If it's a file model, it has its own URL in `links.info`.
        let reloadUrl = model.get('links.info');
        if (!reloadUrl) {
            // If it's not a file model, it must be a relationship.
            // HACK: Looking at Ember's privates.
            reloadUrl = model.get('content.relationship.link');
        }
        if (reloadUrl) {
            this._reloadingUrls[reloadUrl] = true;
        }

        return model.reload().then((freshModel) => {
            if (reloadUrl) {
                delete this._reloadingUrls[reloadUrl];
            }
            return freshModel;
        }).catch((error) => {
            if (reloadUrl) {
                delete this._reloadingUrls[reloadUrl];
            }
            throw error;
        });
    },

    /**
     * Make a Waterbutler request
     *
     * @method _waterbutlerRequest
     * @private
     * @param {String} method HTTP method for the request.
     * @param {String} url Waterbutler URL.
     * @param {Object} [options] Options hash
     * @param {Object} [options.query] Key-value hash of query parameters to
     * add to the request URL.
     * @param {Object} [options.data] Payload to be sent.
     * @return {Promise} Promise that resolves to the data returned from the
     * server on success, or rejects with an error message.
     */
    _waterbutlerRequest(method, url, options = {}) {
        if (options.query) {
            let queryString = Ember.$.param(options.query);
            url = `${url}?${queryString}`;
        }

        let headers = {};
        this.get('session').authorize('authorizer:osf-token', (headerName, content) => {
            headers[headerName] = content;
        });

        return new Ember.RSVP.Promise((resolve, reject) => {
            let p = Ember.$.ajax(url, {
                method,
                headers,
                data: options.data,
                processData: false
            });
            p.done((data) => resolve(data));
            p.fail((_, __, error) => reject(error));
        });
    },

    /**
     * Get the `file` model for a newly created file.
     *
     * @method _getNewFileInfo
     * @private
     * @param {file} parentFolder Model for the new file's parent folder.
     * @param {String} name Name of the new file.
     * @return {Promise} Promise that resolves to the new file's model or
     * rejects with an error message.
     */
    _getNewFileInfo(parentFolder, name) {
        let p = this._reloadModel(parentFolder.get('files'));
        return p.then((files) => files.findBy('name', name));
    }
});
<|endoftext|>


<commit_before>var _ = require("underscore"),
    utils = require("./utils"),
    models = require("./server-models"),
    logger = require("./logging").getLogger();

var ensureSuperuser = utils.ensureSuperuser

module.exports = {
    route: function(app, db, options) {
        //---------------------------------------------------------//
        //                        PERMALINKS                            //
        //---------------------------------------------------------//

        app.get("/h/", function(req, res) {
            res.render('permalink-intro.ejs', {user: req.user});
        });

        app.get("/h/admin/:code/:key", function(req, res) {
            var session = db.permalinkSessions.find(function(s) {
                return s.get("shortCode")===req.params.code;
            });

            if(_.isUndefined(session)) {
                logger.warn("Request for /h/admin/:code/:key with unknown session code: " + req.params.code);
                res.status(404);
                res.send();
                return;
            }

            if(req.params.key===session.get("creationKey")) {
                // now render the normal permalink view, but with the admin interface showing.
                res.render('permalink.ejs', {showAdmin: true, users:db.users, session:session });
            } else {
                logger.warn("Attempt to load /h/admin/:code/:key with invalid key for this session: " + req.params.key);
                res.status(403);
                res._errorReason = "invalid creationKey";
                res.send();
                return;
            }

        });

        app.post("/h/admin/:code", function(req, res) {
            if(!("creationKey" in req.body)) {
                res.status(400);
                res.send();
                return;
            }

            // otherwise, if creationKey is present, check it against the specified session.
            var session = db.permalinkSessions.find(function(s) {
                return s.get("shortCode")==req.params.code;
            });

            if(_.isUndefined(session)) {
                logger.warn("sess:unknown (set title/desc)" + JSON.stringify(req.body));
                res.status(404);
                res.send();
                return;
            }

            if(req.body.creationKey===session.get("creationKey")) {
                session.set("title", req.body.title);
                session.set("description", req.body.description);
                session.save();

                res.redirect("/h/" + req.params.code);
                logger.analytics("permalinks", {
                    action: "update",
                    title: req.body.title,
                    description: req.body.description,
                    shortCode: session.get("shortCode"),
                    req: req,
                    session: session
                });
            } else {
                logger.warn("POST to /h/admin/:code with invaid creationKey.");
                res.status(403);
                res._errorReason = "invalid creationKey",
                res.send();
                return;
            }
        });

        app.get("/h/:code", function(req, res) {
            // okay, this is the permalink part of the site. if this key exists already,
            // then render an appropriate template immediately. if we don't have a
            // session that matches this code, then create one and then render
            // the page.

            // validate shortcodes here.
            var re = /^[A-Za-z0-9-_.+]*$/;
            if(!re.test(req.params.code)) {
                logger.warn("GET /h/" + req.params.code + " is not a valid hangout permalink");
                res.send(400, "Invalid short code.");
                res._errorReason = "invalid shortCode";
                return;
            }

            var session = db.permalinkSessions.findWhere({shortCode: req.params.code});
            if(!session) {
                session = new models.ServerSession();
                session.set("isPermalinkSession", true);

                // use the specified code for the new session.
                session.set("shortCode", req.params.code);
                db.permalinkSessions.add(session);
                session.generateCreationKey();
                session.set("first-load", true);
                session.save();

                // now redirect them to the admin page for this session.
                res.redirect("/h/admin/" + req.params.code + "/" + session.get("creationKey"));
                logger.analytics("permalinks", {
                    action: "create",
                    shortCode: req.params.code,
                    session: session,
                    req: req,
                    res: res
                });
                return;
            }

            // the problem with this is that we don't actually have all users in the
            // hangout in our list of users. That only includes people who have logged
            // in here. Not sure what to do about this. We'll either have to force
            // people to google log in when they land on the page, OR we're
            // going to have to have the participant information sent from the
            // hangout app instead of just the ids involved.

            res.render('permalink.ejs', {showAdmin: false, users:db.users, session:session});

            // after rendering, drop the first-load
            if(session.get("first-load")) {
                session.set("first-load", false);
                session.save();
            }
        });

        // this endpoint manually stops a shortcode session.
        // we seem to occasionally get sessions that are broken and stay open when
        // they're supposed to be closed. this lets us fix that issue.
        // this checks global admin status, not per-shortcode admin status.
        // we could at some point add this in to the permalink admin page, but I don't
        // think people actually go back to that.
        app.post("/h/:code/stop", ensureSuperuser, function(req, res) {
            var session = db.permalinkSessions.find(function(s) {
                return s.get("shortCode")==req.params.code;
            });
            if(!session) {
                logger.warn("sess:unknown (stop)" + JSON.stringify(req.body));
                res.send(404, "Not found");
                return;
            }
            session.onHangoutStopped()
            res.redirect("/admin");
        });
    }
}
<commit_msg>putting back authentication for permalink routes<commit_after>var _ = require("underscore"),
    utils = require("./utils"),
    models = require("./server-models"),
    logger = require("./logging").getLogger();

var ensureSuperuser = utils.ensureSuperuser

module.exports = {
    route: function(app, db, options) {
        //---------------------------------------------------------//
        //                        PERMALINKS                            //
        //---------------------------------------------------------//

        app.get("/h/", utils.ensureAuthenticated, function(req, res) {
            res.render('permalink-intro.ejs', {user: req.user});
        });

        app.get("/h/admin/:code/:key", utils.ensureAuthenticated, function(req, res) {
            var session = db.permalinkSessions.find(function(s) {
                return s.get("shortCode")===req.params.code;
            });

            if(_.isUndefined(session)) {
                logger.warn("Request for /h/admin/:code/:key with unknown session code: " + req.params.code);
                res.status(404);
                res.send();
                return;
            }

            if(req.params.key===session.get("creationKey")) {
                // now render the normal permalink view, but with the admin interface showing.
                res.render('permalink.ejs', {showAdmin: true, users:db.users, session:session });
            } else {
                logger.warn("Attempt to load /h/admin/:code/:key with invalid key for this session: " + req.params.key);
                res.status(403);
                res._errorReason = "invalid creationKey";
                res.send();
                return;
            }

        });

        app.post("/h/admin/:code", utils.ensureAuthenticated, function(req, res) {
            if(!("creationKey" in req.body)) {
                res.status(400);
                res.send();
                return;
            }

            // otherwise, if creationKey is present, check it against the specified session.
            var session = db.permalinkSessions.find(function(s) {
                return s.get("shortCode")==req.params.code;
            });

            if(_.isUndefined(session)) {
                logger.warn("sess:unknown (set title/desc)" + JSON.stringify(req.body));
                res.status(404);
                res.send();
                return;
            }

            if(req.body.creationKey===session.get("creationKey")) {
                session.set("title", req.body.title);
                session.set("description", req.body.description);
                session.save();

                res.redirect("/h/" + req.params.code);
                logger.analytics("permalinks", {
                    action: "update",
                    title: req.body.title,
                    description: req.body.description,
                    shortCode: session.get("shortCode"),
                    req: req,
                    session: session
                });
            } else {
                logger.warn("POST to /h/admin/:code with invaid creationKey.");
                res.status(403);
                res._errorReason = "invalid creationKey",
                res.send();
                return;
            }
        });

        app.get("/h/:code", utils.ensureAuthenticated, function(req, res) {
            // okay, this is the permalink part of the site. if this key exists already,
            // then render an appropriate template immediately. if we don't have a
            // session that matches this code, then create one and then render
            // the page.

            // validate shortcodes here.
            var re = /^[A-Za-z0-9-_.+]*$/;
            if(!re.test(req.params.code)) {
                logger.warn("GET /h/" + req.params.code + " is not a valid hangout permalink");
                res.send(400, "Invalid short code.");
                res._errorReason = "invalid shortCode";
                return;
            }

            var session = db.permalinkSessions.findWhere({shortCode: req.params.code});
            if(!session) {
                session = new models.ServerSession();
                session.set("isPermalinkSession", true);

                // use the specified code for the new session.
                session.set("shortCode", req.params.code);
                db.permalinkSessions.add(session);
                session.generateCreationKey();
                session.set("first-load", true);
                session.save();

                // now redirect them to the admin page for this session.
                res.redirect("/h/admin/" + req.params.code + "/" + session.get("creationKey"));
                logger.analytics("permalinks", {
                    action: "create",
                    shortCode: req.params.code,
                    session: session,
                    req: req,
                    res: res
                });
                return;
            }

            // the problem with this is that we don't actually have all users in the
            // hangout in our list of users. That only includes people who have logged
            // in here. Not sure what to do about this. We'll either have to force
            // people to google log in when they land on the page, OR we're
            // going to have to have the participant information sent from the
            // hangout app instead of just the ids involved.

            res.render('permalink.ejs', {showAdmin: false, users:db.users, session:session});

            // after rendering, drop the first-load
            if(session.get("first-load")) {
                session.set("first-load", false);
                session.save();
            }
        });

        // this endpoint manually stops a shortcode session.
        // we seem to occasionally get sessions that are broken and stay open when
        // they're supposed to be closed. this lets us fix that issue.
        // this checks global admin status, not per-shortcode admin status.
        // we could at some point add this in to the permalink admin page, but I don't
        // think people actually go back to that.
        app.post("/h/:code/stop", utils.ensureAuthenticated, ensureSuperuser, function(req, res) {
            var session = db.permalinkSessions.find(function(s) {
                return s.get("shortCode")==req.params.code;
            });
            if(!session) {
                logger.warn("sess:unknown (stop)" + JSON.stringify(req.body));
                res.send(404, "Not found");
                return;
            }
            session.onHangoutStopped()
            res.redirect("/admin");
        });
    }
}
<|endoftext|>


<commit_before>var MONGODB_URL = process.env.MONGODB_URL || null;

if (MONGODB_URL) {
  module.exports = {
    db: {
      name: 'drinksDB',
      connector: 'loopback-connector-mongodb',
      url: MONGODB_URL
    }
  };
}
<commit_msg>Update datasources.local.js<commit_after>var mongoUri = process.env.MONGOLAB_URI ||
  process.env.MONGOHQ_URL ||
  'mongodb://localhost/mydb';

module.exports = {
  mongodb: {
    defaultForType: "mongodb",
    connector: "loopback-connector-mongodb",
    url: mongoUri
  }
};
<|endoftext|>


<commit_before>
/**
 * @private
 * CognitoStorage is used to wrap the Cognito SDK's use of local storage.
 * Rather than directly writing to localStorage, this is captured in memory and then
 * persisted in ember-simple-auth's "authenticated" data.
 * Effectively, this is a wrapper to ember-simple-auth's authenticated data designed
 * to look like window.localStorage
 * It's also based on https://github.com/aws/amazon-cognito-identity-js/blob/master/src/StorageHelper.js
 * but converted to be a true non-static object.
 */
export default class CognitoStorage {
  constructor(data = {}) {
    this.data = data;
  }

  getData() {
    return this.data;
  }

  /**
   * @public
   * This is used to set a specific item in storage
   * @param {string} key - the key for the item
   * @param {object} value - the value
   * @returns {string} value that was set
   */
  setItem(key, value) {
    this.data[key] = value;
    return this.data[key];
  }

  /**
   * @public
   * This is used to get a specific key from storage
   * @param {string} key - the key for the item
   * This is used to clear the storage
   * @returns {string} the data item
   */
  getItem(key) {
    return Object.prototype.hasOwnProperty.call(this.data, key) ? this.data[key] : undefined;
  }

  /**
   * @public
   * This is used to remove an item from storage
   * @param {string} key - the key being set
   * @returns {string} value - value that was deleted
   */
  removeItem(key) {
    let value = this.data[key];
    delete this.data[key];
    return value;
  }

  /**
   * @public
   * This is used to clear the storage
   * @returns {string} nothing
   */
  clear() {
    this.data = {};
    return this.data;
  }
}
<commit_msg>Deprecate cognito storage<commit_after>import { deprecate } from '@ember/application/deprecations';

/**
 * @private
 * CognitoStorage is used to wrap the Cognito SDK's use of local storage.
 * Rather than directly writing to localStorage, this is captured in memory and then
 * persisted in ember-simple-auth's "authenticated" data.
 * Effectively, this is a wrapper to ember-simple-auth's authenticated data designed
 * to look like window.localStorage
 * It's also based on https://github.com/aws/amazon-cognito-identity-js/blob/master/src/StorageHelper.js
 * but converted to be a true non-static object.
 */
export default class CognitoStorage {
  constructor(data = {}) {
    deprecate(
      'The CognitoStorage object has been deprecated.',
      false,
      { id: 'ember-cognito-storage', until: '1.0' }
    );
    this.data = data;
  }

  getData() {
    return this.data;
  }

  /**
   * @public
   * This is used to set a specific item in storage
   * @param {string} key - the key for the item
   * @param {object} value - the value
   * @returns {string} value that was set
   */
  setItem(key, value) {
    this.data[key] = value;
    return this.data[key];
  }

  /**
   * @public
   * This is used to get a specific key from storage
   * @param {string} key - the key for the item
   * This is used to clear the storage
   * @returns {string} the data item
   */
  getItem(key) {
    return Object.prototype.hasOwnProperty.call(this.data, key) ? this.data[key] : undefined;
  }

  /**
   * @public
   * This is used to remove an item from storage
   * @param {string} key - the key being set
   * @returns {string} value - value that was deleted
   */
  removeItem(key) {
    let value = this.data[key];
    delete this.data[key];
    return value;
  }

  /**
   * @public
   * This is used to clear the storage
   * @returns {string} nothing
   */
  clear() {
    this.data = {};
    return this.data;
  }
}
<|endoftext|>


<commit_before>/**
 *
 * @param {String|HTMLElement} c context querySelect or HTML Element to become the context
 * @returns {F|*}
 * @constructor
 */
F = function (c) {
	var _ = this				// this context
		, x						// the context selected by querySelector
		, l						// length of x
		, i						// placeholder counter1
		, j						// placeholder counter2
		, a						// placeholder array
		, g;					// placeholder generic

	// Global vars
	F.d = d = document;
	F.L = L = "length";
	F.Q = Q = "querySelectorAll";
	F.H = "innerHTML";

	if (_.__proto__.constructor != F) return new F(c);

	/**
	 * Iterate over context, call v for each item
	 * Note: Iteration is inverse
	 * @param {Function} v
	 * @returns {F}
	 */
	_.each = function (v) {
		for (i = l; i--;) v(F(_[i]), i);
		return _
	};

	/**
	 * Reset keys inside F object and recount length
	 */
	_.y = function () {
		for (i = 99; i--;)x[i] ? _[i] = x[i] : delete _[i];	// clean up objects indicies and assign new indicies
		_[L] = l = x[L];								// set .length and internal placeholder l
		_.x = x;
		return _
	};

	/**
	 * Find selector v in context
	 * @param {String} v
	 * @returns {F}
	 */
	_.find = function (v) {
		if(v && !v.big){
			x=[v];
			return _.y()
		}

		try {
			a = v.split(" ");
			v = "";
			for (i in a)
				v += " " + (g = a[i].split(":"))[0] + (!isNaN(j = g[1]) ? ":nth-of-type(" + (+j + 1) + ")" : j ? ":" + j : "");

			if (!x)l = 1, x = [d];
			for (a = [], i = l; i--;)a[i] = x[i][Q](v);

			x = [];
			for (i = 0; i < a[L]; i++)
				if ((g = a[i])[L] && !g.options)
					for (j = 0; j < g[L]; j++)
						x.push(g[j]);
				else x.push(g);
			if (x.toString().match(/No/))x = [];
			return _.y()
		} catch (e) {
		}
	};

	/**
	 * Get element at index v from context
	 * @param {Number} v index
	 * @returns {F}
	 */
	_.get = function (v) {
		x = [x[v]];
		return _.y()
	};

	/**
	 * Get previous sibling element from first element in context
	 * @returns {F}
	 */
	_.prev = function() {
		x = [x[0].previousElementSibling];
		return _.y()
	};

	/**
	 * Get next sibling element from first element in context
	 * @returns {F}
	 */
	_.next = function() {
		x = [x[0].nextElementSibling];
		return _.y()
	};

	/**
	 * Load a number of scripts asynchronously
	 * @param {Array} a contains urls
	 * @param {Function} c callback
	 * @param [s] placeholder number script loaded
	 * @param [r] placeholder xhr-request
	 * @param [i] placeholder
	 */
	_.require = function (a, c, s, r, i) {
		s=0;i=a[F.L];
		for(;(r= d.createElement('script')).src=a.shift(); d.head.appendChild(r))r.onload=function(){s++;c&&s==i&&c(_)}

		return _
	};

	if (c && c[0] == '<') {
		(g = d.createElement('div'))[F.H] = c;
		x = g.childNodes;

		return _.y()
	}

	return _.find(c)
};

/**
 * Extend framewreck with a module
 * @param {Object} m Module
 */
F.ext = function (m) {
	for (i in m)
		F.prototype[i] = m[i]
};<commit_msg>moved some placeholders<commit_after>/**
 *
 * @param {String|HTMLElement} c context querySelect or HTML Element to become the context
 * @returns {F|*}
 * @constructor
 */
F = function (c) {
	var _ = this				// this context
		, x						// the context selected by querySelector
		, l						// length of x
		, i						// placeholder counter1
		, j						// placeholder counter2
		, a						// placeholder array
		, g						// placeholder generic
		, e						// placeholder
		, d;					// placeholder document

	// Global vars
	F.d = d = document;
	F.L = L = "length";
	F.Q = "querySelectorAll";
	F.H = "innerHTML";
	e = "createElement";

	if (_.__proto__.constructor != F) return new F(c);

	/**
	 * Iterate over context, call v for each item
	 * Note: Iteration is inverse
	 * @param {Function} v
	 * @returns {F}
	 */
	_.each = function (v) {
		for (i = l; i--;) v(F(_[i]), i);
		return _
	};

	/**
	 * Reset keys inside F object and recount length
	 */
	_.y = function () {
		for (i = 99; i--;)x[i] ? _[i] = x[i] : delete _[i];	// clean up objects indicies and assign new indicies
		_[L] = l = x[L];								// set .length and internal placeholder l
		_.x = x;
		return _
	};

	/**
	 * Find selector v in context
	 * @param {String} v
	 * @returns {F}
	 */
	_.find = function (v) {
		if(v && !v.big){
			x=[v];
			return _.y()
		}

		try {
			a = v.split(" ");
			v = "";
			for (i in a)
				v += " " + (g = a[i].split(":"))[0] + (!isNaN(j = g[1]) ? ":nth-of-type(" + (+j + 1) + ")" : j ? ":" + j : "");

			if (!x)l = 1, x = [d];
			for (a = [], i = l; i--;)a[i] = x[i][F.Q](v);

			x = [];
			for (i = 0; i < a[L]; i++)
				if ((g = a[i])[L] && !g.options)
					for (j = 0; j < g[L]; j++)
						x.push(g[j]);
				else x.push(g);
			if (x.toString().match(/No/))x = [];
			return _.y()
		} catch (e) {
		}
	};

	/**
	 * Get element at index v from context
	 * @param {Number} v index
	 * @returns {F}
	 */
	_.get = function (v) {
		x = [x[v]];
		return _.y()
	};

	/**
	 * Get previous sibling element from first element in context
	 * @returns {F}
	 */
	_.prev = function() {
		x = [x[0].previousElementSibling];
		return _.y()
	};

	/**
	 * Get next sibling element from first element in context
	 * @returns {F}
	 */
	_.next = function() {
		x = [x[0].nextElementSibling];
		return _.y()
	};

	/**
	 * Load a number of scripts asynchronously
	 * @param {Array} a contains urls
	 * @param {Function} c callback
	 * @param [s] placeholder number script loaded
	 * @param [r] placeholder xhr-request
	 * @param [i] placeholder
	 */
	_.require = function (a, c, s, r, i) {
		s=0;i=a[F.L];
		for(;(r= d[e]('script')).src=a.shift(); d.head.appendChild(r))r.onload=function(){s++;c&&s==i&&c(_)}

		return _
	};

	if (c && c[0] == '<') {
		(g = d[e]('div'))[F.H] = c;
		x = g.childNodes;

		return _.y()
	}

	return _.find(c)
};

/**
 * Extend framewreck with a module
 * @param {Object} m Module
 */
F.ext = function (m) {
	for (i in m)
		F.prototype[i] = m[i]
};<|endoftext|>


<commit_before>/**
 *  Copyright 2012 Rackspace
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

var util = require('util');
var net = require('net');
var tls = require('tls');
var Agent = require('http').Agent;
var SSLAgent = require('https').Agent;

/**
 * A special global instance which re-uses the same long-lived connection.
 */
function PersistentAgent(options) {
  var self = this;

  this.options = options || {};
  this.requests = {};
  this.sockets = {};
  this.freeSockets = {};
  this.maxSockets = 1;
  this.minSockets = 1;

  this.on('free', function(socket, host, port) {
    var name = host + ':' + port;

    if (self.requests[name] && self.requests[name].length) {
      // Pending requests
      self.requests[name].shift().onSocket(socket);
    }
    else {
      // No pending requests, re-use the socket
      if (!self.freeSockets[name]) {
        self.freeSockets[name] = [];
      }

      self.freeSockets[name].push(socket);
    }
  });
}

util.inherits(PersistentAgent, Agent);

PersistentAgent.prototype.createConnection = net.createConnection;
PersistentAgent.prototype.addRequestNoreuse = Agent.prototype.addRequest;

PersistentAgent.prototype.addRequest = function(req, host, port) {
  var name = host + ':' + port,
      self = this;

  if ((this.freeSockets[name] && this.freeSockets[name].length > 0) && !req.useChunkedEncodingByDefault) {
    // Re-use a socket
    var idleSocket = this.freeSockets[name].pop();

    if (idleSocket._onIdleError) {
      idleSocket.removeListener('error', idleSocket._onIdleError);
      delete idleSocket._onIdleError;
    }

    req._reusedSocket = true;
    req.onSocket(idleSocket);
  } else {

    if (Object.keys(this.sockets).length === 0 || (this.freeSockets[name] && this.freeSockets[name].length === 0)) {
      // No available sockets, this a probably a first request, create a new
      // socket.
      this.addRequestNoreuse(req, host, port);
      return;
    }

    // No free socket available, wait and try again
    setTimeout(function() {
      self.addRequest(req, host, port);
    }, 50);
  }
};

PersistentAgent.prototype.removeSocket = function(s, name, host, port) {
  var index;

  if (this.sockets[name]) {
    index = this.sockets[name].indexOf(s);

    if (index !== -1) {
      this.sockets[name].splice(index, 1);
    }
  }

  if (this.sockets[name] && this.sockets[name].length === 0) {
    delete this.sockets[name];
    delete this.requests[name];
  }

  if (this.freeSockets[name]) {
    index = this.freeSockets[name].indexOf(s);

    if (index !== -1) {
      this.freeSockets[name].splice(index, 1);

      if (this.freeSockets[name].length === 0) {
        delete this.freeSockets[name];
      }
    }
  }

  if (this.requests[name] && this.requests[name].length) {
    // If we have pending requests and a socket gets closed a new one
    // needs to be created to take over in the pool for the one that closed.
    this.createSocket(name, host, port).emit('free');
  }
};

function PersistentAgentSSL (options) {
  PersistentAgent.call(this, options);
}

util.inherits(PersistentAgentSSL, PersistentAgent);

PersistentAgentSSL.prototype.createConnection = createConnectionSSL;
PersistentAgentSSL.prototype.addRequestNoreuse = SSLAgent.prototype.addRequest;

function createConnectionSSL(port, host, options) {
  options.port = port;
  options.host = host;
  return tls.connect(options);
}

exports.PersistentAgent = PersistentAgent;
exports.PersistentAgentSSL = PersistentAgentSSL;
<commit_msg>Fix the tls.connect so it works with 0.6.<commit_after>/**
 *  Copyright 2012 Rackspace
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

var util = require('util');
var net = require('net');
var tls = require('tls');
var Agent = require('http').Agent;
var SSLAgent = require('https').Agent;

/**
 * A special global instance which re-uses the same long-lived connection.
 */
function PersistentAgent(options) {
  var self = this;

  this.options = options || {};
  this.requests = {};
  this.sockets = {};
  this.freeSockets = {};
  this.maxSockets = 1;
  this.minSockets = 1;

  this.on('free', function(socket, host, port) {
    var name = host + ':' + port;

    if (self.requests[name] && self.requests[name].length) {
      // Pending requests
      self.requests[name].shift().onSocket(socket);
    }
    else {
      // No pending requests, re-use the socket
      if (!self.freeSockets[name]) {
        self.freeSockets[name] = [];
      }

      self.freeSockets[name].push(socket);
    }
  });
}

util.inherits(PersistentAgent, Agent);

PersistentAgent.prototype.createConnection = net.createConnection;
PersistentAgent.prototype.addRequestNoreuse = Agent.prototype.addRequest;

PersistentAgent.prototype.addRequest = function(req, host, port) {
  var name = host + ':' + port,
      self = this;

  if ((this.freeSockets[name] && this.freeSockets[name].length > 0) && !req.useChunkedEncodingByDefault) {
    // Re-use a socket
    var idleSocket = this.freeSockets[name].pop();

    if (idleSocket._onIdleError) {
      idleSocket.removeListener('error', idleSocket._onIdleError);
      delete idleSocket._onIdleError;
    }

    req._reusedSocket = true;
    req.onSocket(idleSocket);
  } else {

    if (Object.keys(this.sockets).length === 0 || (this.freeSockets[name] && this.freeSockets[name].length === 0)) {
      // No available sockets, this a probably a first request, create a new
      // socket.
      this.addRequestNoreuse(req, host, port);
      return;
    }

    // No free socket available, wait and try again
    setTimeout(function() {
      self.addRequest(req, host, port);
    }, 50);
  }
};

PersistentAgent.prototype.removeSocket = function(s, name, host, port) {
  var index;

  if (this.sockets[name]) {
    index = this.sockets[name].indexOf(s);

    if (index !== -1) {
      this.sockets[name].splice(index, 1);
    }
  }

  if (this.sockets[name] && this.sockets[name].length === 0) {
    delete this.sockets[name];
    delete this.requests[name];
  }

  if (this.freeSockets[name]) {
    index = this.freeSockets[name].indexOf(s);

    if (index !== -1) {
      this.freeSockets[name].splice(index, 1);

      if (this.freeSockets[name].length === 0) {
        delete this.freeSockets[name];
      }
    }
  }

  if (this.requests[name] && this.requests[name].length) {
    // If we have pending requests and a socket gets closed a new one
    // needs to be created to take over in the pool for the one that closed.
    this.createSocket(name, host, port).emit('free');
  }
};

function PersistentAgentSSL (options) {
  PersistentAgent.call(this, options);
}

util.inherits(PersistentAgentSSL, PersistentAgent);

PersistentAgentSSL.prototype.createConnection = createConnectionSSL;
PersistentAgentSSL.prototype.addRequestNoreuse = SSLAgent.prototype.addRequest;

function createConnectionSSL(port, host, options) {
  options.port = port;
  options.host = host;
  return tls.connect(port, host, options);
}

exports.PersistentAgent = PersistentAgent;
exports.PersistentAgentSSL = PersistentAgentSSL;
<|endoftext|>


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

    /**
    *@ngdoc directive
    *@name openlmis-core.directive:openlmisSelect
    *@restrict E
    *@description directive for simple implementing <ui-select> element.
    *
    */

    angular.module("openlmis-core").directive('openlmisSelect', select);

    select.$inject = ['$parse'];
    function select($parse) {
        return {
            scope: {
                ngModel: '=ngModel',
                items: '=items',
                placeholder: '@?placeholder',
                fieldDisplayed: '@fieldDisplayed',
                orderBy: '@?',
                trackBy: '@?',
                defaultValue: '@?defaultValue'
            },
            restrict: 'E',
            replace: 'true',
            templateUrl: 'common/select.html',
            controller: 'SelectController',
        };
    }
})();<commit_msg>OLMIS-1167: Simplified select directive<commit_after>
(function(){
    "use strict";

    /**
    *@ngdoc directive
    *@name openlmis-core.directive:select
    *@restrict E
    *@description directive for simple implementing <ui-select> element.
    *
    */

    angular.module("openlmis-core").directive('select', select);

    select.$inject = ['$parse'];
    function select($parse) {
        return {
            restrict: 'E',
            replace: false,
            require: "ngModel",
            priority: 10, // Sets this link function to run AFTER ngSelect
            link: function(scope, element, attrs, ngModelCtrl){

                var numOptions = element.children().length;

                if(numOptions <= 1){
                    element.attr("disabled", true);
                }
                if(numOptions == 1){
                    var option = angular.element(element.children()[0]);
                    ngModelCtrl.$setViewValue(option.value);
                }
            }
        };
    }
})();<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';

import c3 from 'c3';

export default class C3Component extends Component {
  constructor() {
    super();
    this.state = {
      chartObject: {},
      chartBoundTo: '',
      lastEvent: 0,
    };
  }

  componentDidMount() {
    this.onMount();
  }

  componentWillReceiveProps(newProps) {
    const {c3obj, eventData} = newProps;
    const {c3fct, c3arg} = c3obj;
    const {chartObject, lastEvent} = this.state;
    if (c3fct !== 'generate') {
      chartObject[c3fct](c3arg);
    }

    // Redux Events
    if (newProps.eventData.timeStamp > lastEvent) {
      this.setState({lastEvent: eventData.timeStamp});
      this.incomingEvent(newProps.eventData, ['default']);
    }
  }

  shouldComponentUpdate(newProps) {
    return (newProps.eventData.timeStamp <= this.state.lastEvent);
  }

  onMount() {
    const {c3obj, setEvent, highlightEmit} = this.props;
    const {c3fct, c3arg} = c3obj;
    const chartObject = c3[c3fct](c3arg);
    const chartBoundTo = (c3arg.bindto).substr(1);
    this.setState({chartObject, chartBoundTo});
    chartObject.setEvent = setEvent;
    chartObject.highlightEmit = highlightEmit;
  }

  incomingEvent(obj) {
    const {data, event, eventGroup} = obj;
    const {highlightListen, highlight} = this.props;
    const {chartObject} = this.state;
    // check if you have an overlap between highlightEmit and highlightListen
    const listenGroups = highlightListen;
    const intersection = eventGroup.filter((n) => {
      return listenGroups.indexOf(n) !== -1;
    });

    if (intersection.length && highlight) {
      if (event === 'mouseover') {
        chartObject.focus(data.id);
      } else if (event === 'mouseout') {
        chartObject.revert();
      }
    }
  }

  render() {
    const {c3obj} = this.props;
    const {bindto} = c3obj.c3arg;
    let {chartBoundTo} = this.state;
    if (bindto) {
      chartBoundTo = bindto.substr(1);
    }

    return (
);
  }
}

C3Component.defaultProps = {
  highlight: true,
  highlightEmit: ['default'],
  highlightListen: ['default'],
};

C3Component.propTypes = {
  c3obj: PropTypes.object,
  eventData: PropTypes.object.isRequired,
  highlight: PropTypes.bool,
  highlightEmit: PropTypes.array,
  highlightListen: PropTypes.array,
  setEvent: PropTypes.func.isRequired,
};
<commit_msg>Fix timestamp in c3 module.<commit_after>import React, {Component, PropTypes} from 'react';

import c3 from 'c3';

export default class C3Component extends Component {
  constructor() {
    super();
    this.state = {
      chartObject: {},
      chartBoundTo: '',
      lastEvent: 0,
    };
  }

  componentDidMount() {
    this.onMount();
  }

  componentWillReceiveProps(newProps) {
    const {c3obj, eventData} = newProps;
    const {c3fct, c3arg} = c3obj;
    const {chartObject, lastEvent} = this.state;
    if (c3fct !== 'generate') {
      chartObject[c3fct](c3arg);
    }

    // Redux Events
    if (newProps.eventData.timeStamp > lastEvent) {
      this.incomingEvent(newProps.eventData, ['default']);
    }

    this.setState({lastEvent: eventData.timeStamp});
  }

  shouldComponentUpdate(newProps) {
    return (newProps.eventData.timeStamp <= this.state.lastEvent);
  }

  onMount() {
    const {c3obj, setEvent, highlightEmit} = this.props;
    const {c3fct, c3arg} = c3obj;
    const chartObject = c3[c3fct](c3arg);
    const chartBoundTo = (c3arg.bindto).substr(1);
    this.setState({chartObject, chartBoundTo});
    chartObject.setEvent = setEvent;
    chartObject.highlightEmit = highlightEmit;
  }

  incomingEvent(obj) {
    const {data, event, eventGroup} = obj;
    const {highlightListen, highlight} = this.props;
    const {chartObject} = this.state;
    // check if you have an overlap between highlightEmit and highlightListen
    const listenGroups = highlightListen;
    const intersection = eventGroup.filter((n) => {
      return listenGroups.indexOf(n) !== -1;
    });

    if (intersection.length && highlight) {
      if (event === 'mouseover') {
        chartObject.focus(data.id);
      } else if (event === 'mouseout') {
        chartObject.revert();
      }
    }
  }

  render() {
    const {c3obj} = this.props;
    const {bindto} = c3obj.c3arg;
    let {chartBoundTo} = this.state;
    if (bindto) {
      chartBoundTo = bindto.substr(1);
    }

    return (
);
  }
}

C3Component.defaultProps = {
  highlight: true,
  highlightEmit: ['default'],
  highlightListen: ['default'],
};

C3Component.propTypes = {
  c3obj: PropTypes.object,
  eventData: PropTypes.object.isRequired,
  highlight: PropTypes.bool,
  highlightEmit: PropTypes.array,
  highlightListen: PropTypes.array,
  setEvent: PropTypes.func.isRequired,
};
<|endoftext|>


<commit_before>/**
 * These interactions are provided for prototyping use ONLY.
 *
 * Browser support, accessibility and security are not concerns in this prototype.
 * This is not aimed at being keyboard accessible or screen-reader friendly.
 * For these reasons, the JavaScript below is NOT for production use.
 */

const warningStyles = `
  color: #fff;
  background-color: #c23934;
  display: block;
  text-align: center;
  padding: 8px 32px;
  font: 100 16px/28px sans-serif;
  background-image: linear-gradient(45deg,rgba(0,0,0,.025) 25%,transparent 25%,transparent 50%,rgba(0,0,0,.025) 50%,rgba(0,0,0,.025) 75%,transparent 75%,transparent);
  background-size: 64px 64px;
`;
console.log('%c%s', warningStyles, 'Please do not use the provided JavaScript in production!');
console.log('Code in app.js is not built for accessibility, performance, or cross-browser compatibility.');

/**
 * Open / Close an element
 * @param {Node} element
 */
const toggle = (element) => element.classList.toggle('slds-is-open');

/**
 * Open / Close an element's parent
 * @param {Node} element
 */
const toggleParent = (element) => toggle(element.parentNode);

/**
 * Dropdown menus
 * https://www.lightningdesignsystem.com/components/menus/
 */
const dropdownButtons = document.querySelectorAll('.slds-dropdown-trigger--click > .slds-button');

Array.from(dropdownButtons)
  .forEach((button) =>
    button.addEventListener('click', (event) => toggleParent(event.target), false)
  );

/**
 * Tabs
 * https://www.lightningdesignsystem.com/components/tabs/
 */

const tabs = (variant) => document.querySelectorAll(`.slds-tabs--${variant} [role=tablist] [role=tab]`);
const tabActiveReset = (tab) => Array.from(tab.parentNode.parentNode.querySelectorAll('li'))
  .forEach((element) => element.classList.remove('slds-active'));
const tabActiveSet = (tab) => tab.parentNode.classList.add('slds-active');
const tabPanelsReset = (tab) => Array.from(tab.parentNode.parentNode.parentNode.querySelectorAll('[role="tabpanel"]'))
  .forEach((tabpanel) => {
    tabpanel.classList.remove('slds-show');
    tabpanel.classList.remove('slds-hide');
    tabpanel.classList.add('slds-hide');
  });
const tabPanelShow = (tab) => {
  const tabpanel = document.getElementById(tab.getAttribute('aria-controls'));
  tabpanel.classList.remove('slds-show');
  tabpanel.classList.remove('slds-hide');
  tabpanel.classList.add('slds-show');
};

const defaultTabs = tabs('default');
const scopedTabs = tabs('scoped');

const assignTabEvents = (event) => {
  tabActiveReset(event.target);
  tabActiveSet(event.target);
  tabPanelsReset(event.target);
  tabPanelShow(event.target);
}

Array.from(defaultTabs).forEach((tab) => {
  tab.addEventListener('click', assignTabEvents, false);
});
Array.from(scopedTabs).forEach((tab) => {
  tab.addEventListener('click', assignTabEvents, false);
});
<commit_msg>Fix a bug where the events would target children<commit_after>/**
 * These interactions are provided for prototyping use ONLY.
 *
 * Browser support, accessibility and security are not concerns in this prototype.
 * This is not aimed at being keyboard accessible or screen-reader friendly.
 * For these reasons, the JavaScript below is NOT for production use.
 */

const warningStyles = `
  color: #fff;
  background-color: #c23934;
  display: block;
  text-align: center;
  padding: 8px 32px;
  font: 100 16px/28px sans-serif;
  background-image: linear-gradient(45deg,rgba(0,0,0,.025) 25%,transparent 25%,transparent 50%,rgba(0,0,0,.025) 50%,rgba(0,0,0,.025) 75%,transparent 75%,transparent);
  background-size: 64px 64px;
`;
console.log('%c%s', warningStyles, 'Please do not use the provided JavaScript in production!');
console.log('Code in app.js is not built for accessibility, performance, or cross-browser compatibility.');

/**
 * Open / Close an element
 * @param {Node} element
 */
const toggle = (element) => element.classList.toggle('slds-is-open');

/**
 * Open / Close an element's parent
 * @param {Node} element
 */
const toggleParent = (element) => toggle(element.parentNode);

/**
 * Dropdown menus
 * https://www.lightningdesignsystem.com/components/menus/
 */
const dropdownButtons = document.querySelectorAll('.slds-dropdown-trigger--click > .slds-button');

Array.from(dropdownButtons)
  .forEach((button) =>
    button.addEventListener('click', (event) => toggleParent(event.currentTarget), false)
  );

/**
 * Tabs
 * https://www.lightningdesignsystem.com/components/tabs/
 */

const tabs = (variant) => document.querySelectorAll(`.slds-tabs--${variant} [role=tablist] [role=tab]`);
const tabActiveReset = (tab) => Array.from(tab.parentNode.parentNode.querySelectorAll('li'))
  .forEach((element) => element.classList.remove('slds-active'));
const tabActiveSet = (tab) => tab.parentNode.classList.add('slds-active');
const tabPanelsReset = (tab) => Array.from(tab.parentNode.parentNode.parentNode.querySelectorAll('[role="tabpanel"]'))
  .forEach((tabpanel) => {
    tabpanel.classList.remove('slds-show');
    tabpanel.classList.remove('slds-hide');
    tabpanel.classList.add('slds-hide');
  });
const tabPanelShow = (tab) => {
  const tabpanel = document.getElementById(tab.getAttribute('aria-controls'));
  tabpanel.classList.remove('slds-show');
  tabpanel.classList.remove('slds-hide');
  tabpanel.classList.add('slds-show');
};

const defaultTabs = tabs('default');
const scopedTabs = tabs('scoped');

const assignTabEvents = (event) => {
  tabActiveReset(event.currentTarget);
  tabActiveSet(event.currentTarget);
  tabPanelsReset(event.currentTarget);
  tabPanelShow(event.currentTarget);
}

Array.from(defaultTabs).forEach((tab) => {
  tab.addEventListener('click', assignTabEvents, false);
});
Array.from(scopedTabs).forEach((tab) => {
  tab.addEventListener('click', assignTabEvents, false);
});
<|endoftext|>


<commit_before>"use strict"
// Copyright 2017 TRI R.A. WIBOWO

function show_error(res, error)
{
    console.log(error.stack)

    res.send(error.stack)
}

function show_result(res, result)
{
    console.log(result)
    res.send(result)
}

function webhook_endpoints(app, db)
{
    // 1. Webhook for Telegram
    function telegram_callback(req, res)
    {
        console.log(req.body.message.text)
        res.send(req.body.message.text)
    }

    app.post(`/telegram`, telegram_callback)

    // 2. Webhook for LINE
    function line_callback(req, res)
    {
        console.log(req.body)
        res.send(req.body)
    }

    // https://c6fd29f5.ngrok.io/bots/line/notifications
    app.post('/bots/line/notifications', line_callback)
}

module.exports = webhook_endpoints
<commit_msg>Implementing LINE Messaging API<commit_after>"use strict"
// Copyright 2017 TRI R.A. WIBOWO

function show_error(res, error)
{
    console.log(error.stack)

    res.send(error.stack)
}

function show_result(res, result)
{
    console.log(result)
    res.send(result)
}

function webhook_endpoints(app, db)
{
    // 1. Webhook for Telegram
    function telegram_callback(req, res)
    {
        console.log(req.body.message.text)
        res.send(req.body.message.text)
    }

    app.post(`/telegram`, telegram_callback)

    // 2. Webhook for LINE
    function line_callback(req, res)
    {
        console.log(req.body.events[0].source)
        res.send(req.body)
    }

    // https://c6fd29f5.ngrok.io/bots/line/notifications
    app.post('/bots/line/notifications', line_callback)
}

module.exports = webhook_endpoints
<|endoftext|>


<commit_before>
const SPLITTER_COLOR = [255, 20, 147];
const SPLITTER_WIDTH = 4;
const TABSTRIP_COLOR = [240, 240, 240];
const TABSTRIP_TAB_WIDTH = 100;
const TABSTRIP_TAB_HEIGHT = 20;
const TABSTRIP_TAB_LINE_WIDTH = 4;

export default class Component {
    static get Splitter(){
        return Splitter;
    }
    static get TabStrip(){
        return TabStrip;
    }
}

class Emitter {
    constructor(){
        this.listeners = {};
    }
    emit(eventName, arg){
        if(this.listeners.hasOwnProperty(eventName) === true){
            this.listeners[eventName](arg);
        }
    }
    on(eventName, listener){
        this.listeners[eventName] = listener;
    }
    off(eventName){
        this.listeners[eventName] = null;
    }
}

class Splitter extends Emitter {
    constructor(parentDOM, horizontal = true){
        super();
        this.parentDOM = parentDOM;
        this.horizontal = horizontal;
        this.wrap   = document.createElement('div');
        this.first  = document.createElement('div');
        this.split  = document.createElement('div');
        this.second = document.createElement('div');
        this.isDown = false;
        this.prev = [0, 0];

        // styling
        appendStyle(this.wrap, {
            width: '100%',
            height: '100%',
            display: 'flex',
            flexDirection: this.horizontal === true ? 'column' : 'row',
            overflow: 'hidden',
        });
        appendStyle(this.split, {
            backgroundColor: `rgb(${SPLITTER_COLOR.join(',')})`,
            userSelect: 'none',
        });

        // appending
        this.wrap.appendChild(this.first);
        this.wrap.appendChild(this.split);
        this.wrap.appendChild(this.second);
        this.parentDOM.appendChild(this.wrap);

        // event setting
        const DOWN = (evt) => {
            this.isDown = true;
            // let bound = this.parentDOM.getBoundingClientRect();
            // this.prev = [evt.clientX - bound.left, evt.clientY - bound.top];
            window.addEventListener('mousemove', MOVE, false);
            window.addEventListener('mouseup', UP, false);
        };
        const MOVE = (evt) => {
            if(this.isDown !== true){return;}
            let bound = this.parentDOM.getBoundingClientRect();
            let x = (evt.clientX - bound.left) / bound.width;
            let y = (evt.clientY - bound.top) / bound.height;
            if(this.horizontal === true){
                this.update(y);
            }else{
                this.update(x);
            }
            this.emit('change', {x, y});
        };
        const UP = (evt) => {
            this.isDown = false;
            window.removeEventListener('mousemove', MOVE);
            window.removeEventListener('mouseup', UP);
        };
        this.split.addEventListener('mousedown', DOWN, false);

        this.update();
    }
    update(ratio = 0.5){
        let first = `calc(${ratio * 100}% - ${SPLITTER_WIDTH / 2}px)`;
        let second = `calc(${100 - ratio * 100}% - ${SPLITTER_WIDTH / 2}px)`;
        if(this.horizontal === true){
            appendStyle(this.first, {
                width: '100%',
                height: first,
            });
            appendStyle(this.split, {
                borderTop: `1px solid rgba(0, 0, 0, 0.3)`,
                borderBottom: `1px solid rgba(0, 0, 0, 0.3)`,
                width: '100%',
                minHeight: `${SPLITTER_WIDTH}px`,
                maxHeight: `${SPLITTER_WIDTH}px`,
                cursor: 'row-resize',
            });
            appendStyle(this.second, {
                width: '100%',
                height: second,
            });
        }else{
            appendStyle(this.first, {
                width: first,
                height: '100%',
            });
            appendStyle(this.split, {
                borderLeft: `1px solid rgba(0, 0, 0, 0.3)`,
                borderRight: `1px solid rgba(0, 0, 0, 0.3)`,
                minWidth: `${SPLITTER_WIDTH}px`,
                maxWidth: `${SPLITTER_WIDTH}px`,
                height: '100%',
                cursor: 'col-resize',
            });
            appendStyle(this.second, {
                width: second,
                height: '100%',
            });
        }
    }
}

class TabStrip extends Emitter {
    constructor(parentDOM, tabs, selectedIndex){
        super();
        this.parentDOM = parentDOM;
        this.count = Math.max(1, tabs.length);
        this.index = Math.min(this.count - 1, selectedIndex);
        this.tabs = [];
        this.inners = [];
        this.wrap = document.createElement('div');
        this.tabBlock = document.createElement('div');
        this.innerBlock = document.createElement('div');

        // styling
        appendStyle(this.wrap, {
            width: '100%',
            height: '100%',
            display: 'flex',
            flexDirection: 'column',
        });
        appendStyle(this.tabBlock, {
            lineHeight: `${TABSTRIP_TAB_HEIGHT}px`,
            width: '100%',
            minHeight: `${TABSTRIP_TAB_HEIGHT + TABSTRIP_TAB_LINE_WIDTH}px`,
            maxHeight: `${TABSTRIP_TAB_HEIGHT + TABSTRIP_TAB_LINE_WIDTH}px`,
            display: 'flex',
            flexDirection: 'row',
        });
        appendStyle(this.innerBlock, {
            width: '100%',
            height: '100%',
        });

        // appending
        this.parentDOM.appendChild(this.wrap);
        this.wrap.appendChild(this.tabBlock);
        this.wrap.appendChild(this.innerBlock);
        tabs.forEach((v, index) => {
            let item = new Tab(this.tabBlock, index, v, this.index === index);
            item.on('click', (idx) => {
                this.tabs.forEach((tab, i) => {
                    tab.update(idx === i);
                });
            });
            this.tabs.push(item);
            let inner = document.createElement('div');
            appendStyle(inner, {
                width: '100%',
                height: '100%',
                overflow: 'hidden',
            });
            inner.setAttribute('id', `page${index}_${v}`);
            inner.textContent = index;
            this.innerBlock.appendChild(inner);
            this.inners.push(inner);
        });

        // event setting
    }
    getPage(index){
        return this.inners[index];
    }
}

class Tab extends Emitter {
    static get ACTIVE(){return `${TABSTRIP_TAB_LINE_WIDTH}px solid rgb(${TABSTRIP_COLOR.join(',')})`;}
    static get DEACTIVE(){return `${TABSTRIP_TAB_LINE_WIDTH}px solid rgba(${TABSTRIP_COLOR.join(',')},0.3)`;}
    static get ACTIVE_COLOR(){return `rgb(${TABSTRIP_COLOR.join(',')})`;}
    static get DEACTIVE_COLOR(){return `rgba(${TABSTRIP_COLOR.join(',')},0.3)`;}
    constructor(parentDOM, index, title, isActive = false){
        super();
        this.parentDOM = parentDOM;
        this.index = index;
        this.wrap = document.createElement('div');
        this.wrap.textContent = title;
        this.active = isActive;
        // styling
        appendStyle(this.wrap, {
            borderBottom: this.active === true ? Tab.ACTIVE : Tab.DEACTIVE,
            color: this.active === true ? Tab.ACTIVE_COLOR : Tab.DEACTIVE_COLOR,
            lineHeight: `${TABSTRIP_TAB_HEIGHT}px`,
            minWidth: `${TABSTRIP_TAB_WIDTH}px`,
            maxWidth: `${TABSTRIP_TAB_WIDTH}px`,
            whiteSpace: 'nowrap',
            textOverflow: 'ellipsis',
            overflow: 'hidden',
        });
        // appending
        this.parentDOM.appendChild(this.wrap);
        // event setting
        this.wrap.addEventListener('click', () => {
            this.emit('click', index);
        }, false);
        this.wrap.addEventListener('mouseenter', () => {
            appendStyle(this.wrap, {
                borderBottom: Tab.ACTIVE,
            });
        }, false);
        this.wrap.addEventListener('mouseleave', () => {
            this.update();
        }, false);
    }
    setActive(isActive){
        if(isActive == null){return;}
        this.active = isActive;
    }
    update(isActive){
        this.setActive(isActive);
        appendStyle(this.wrap, {
            color: this.active === true ? Tab.ACTIVE_COLOR : Tab.DEACTIVE_COLOR,
            borderBottom: this.active === true ? Tab.ACTIVE : Tab.DEACTIVE,
        });
    }
}

function appendStyle(target, style){
    for(let s in style){
        target.style[s] = style[s];
    }
}

<commit_msg>update tabstrip component<commit_after>
const SPLITTER_COLOR = [255, 20, 147];
const SPLITTER_WIDTH = 4;
const TABSTRIP_COLOR = [240, 240, 240];
const TABSTRIP_TAB_WIDTH = 100;
const TABSTRIP_TAB_HEIGHT = 20;
const TABSTRIP_TAB_LINE_WIDTH = 4;

export default class Component {
    static get Splitter(){
        return Splitter;
    }
    static get TabStrip(){
        return TabStrip;
    }
}

class Emitter {
    constructor(){
        this.listeners = {};
    }
    emit(eventName, arg){
        if(this.listeners.hasOwnProperty(eventName) === true){
            this.listeners[eventName](arg);
        }
    }
    on(eventName, listener){
        this.listeners[eventName] = listener;
    }
    off(eventName){
        this.listeners[eventName] = null;
    }
}

class Splitter extends Emitter {
    constructor(parentDOM, horizontal = true){
        super();
        this.parentDOM = parentDOM;
        this.horizontal = horizontal;
        this.wrap   = document.createElement('div');
        this.first  = document.createElement('div');
        this.split  = document.createElement('div');
        this.second = document.createElement('div');
        this.isDown = false;
        this.prev = [0, 0];

        // styling
        appendStyle(this.wrap, {
            width: '100%',
            height: '100%',
            display: 'flex',
            flexDirection: this.horizontal === true ? 'column' : 'row',
            overflow: 'hidden',
        });
        appendStyle(this.split, {
            backgroundColor: `rgb(${SPLITTER_COLOR.join(',')})`,
            userSelect: 'none',
        });

        // appending
        this.wrap.appendChild(this.first);
        this.wrap.appendChild(this.split);
        this.wrap.appendChild(this.second);
        this.parentDOM.appendChild(this.wrap);

        // event setting
        const DOWN = (evt) => {
            this.isDown = true;
            // let bound = this.parentDOM.getBoundingClientRect();
            // this.prev = [evt.clientX - bound.left, evt.clientY - bound.top];
            window.addEventListener('mousemove', MOVE, false);
            window.addEventListener('mouseup', UP, false);
        };
        const MOVE = (evt) => {
            if(this.isDown !== true){return;}
            let bound = this.parentDOM.getBoundingClientRect();
            let x = (evt.clientX - bound.left) / bound.width;
            let y = (evt.clientY - bound.top) / bound.height;
            if(this.horizontal === true){
                this.update(y);
            }else{
                this.update(x);
            }
            this.emit('change', {x, y});
        };
        const UP = (evt) => {
            this.isDown = false;
            window.removeEventListener('mousemove', MOVE);
            window.removeEventListener('mouseup', UP);
        };
        this.split.addEventListener('mousedown', DOWN, false);

        this.update();
    }
    update(ratio = 0.5){
        let first = `calc(${ratio * 100}% - ${SPLITTER_WIDTH / 2}px)`;
        let second = `calc(${100 - ratio * 100}% - ${SPLITTER_WIDTH / 2}px)`;
        if(this.horizontal === true){
            appendStyle(this.first, {
                width: '100%',
                height: first,
            });
            appendStyle(this.split, {
                borderTop: `1px solid rgba(0, 0, 0, 0.3)`,
                borderBottom: `1px solid rgba(0, 0, 0, 0.3)`,
                width: '100%',
                minHeight: `${SPLITTER_WIDTH}px`,
                maxHeight: `${SPLITTER_WIDTH}px`,
                cursor: 'row-resize',
            });
            appendStyle(this.second, {
                width: '100%',
                height: second,
            });
        }else{
            appendStyle(this.first, {
                width: first,
                height: '100%',
            });
            appendStyle(this.split, {
                borderLeft: `1px solid rgba(0, 0, 0, 0.3)`,
                borderRight: `1px solid rgba(0, 0, 0, 0.3)`,
                minWidth: `${SPLITTER_WIDTH}px`,
                maxWidth: `${SPLITTER_WIDTH}px`,
                height: '100%',
                cursor: 'col-resize',
            });
            appendStyle(this.second, {
                width: second,
                height: '100%',
            });
        }
    }
}

class Tab extends Emitter {
    static get ACTIVE(){return `${TABSTRIP_TAB_LINE_WIDTH}px solid rgb(${TABSTRIP_COLOR.join(',')})`;}
    static get DEACTIVE(){return `${TABSTRIP_TAB_LINE_WIDTH}px solid rgba(${TABSTRIP_COLOR.join(',')},0.3)`;}
    static get ACTIVE_COLOR(){return `rgb(${TABSTRIP_COLOR.join(',')})`;}
    static get DEACTIVE_COLOR(){return `rgba(${TABSTRIP_COLOR.join(',')},0.3)`;}
    constructor(parentDOM, index, title, isActive = false){
        super();
        this.parentDOM = parentDOM;
        this.index = index;
        this.wrap = document.createElement('div');
        this.wrap.textContent = title;
        this.active = isActive;
        // styling
        appendStyle(this.wrap, {
            borderBottom: this.active === true ? Tab.ACTIVE : Tab.DEACTIVE,
            color: this.active === true ? Tab.ACTIVE_COLOR : Tab.DEACTIVE_COLOR,
            lineHeight: `${TABSTRIP_TAB_HEIGHT}px`,
            minWidth: `${TABSTRIP_TAB_WIDTH}px`,
            maxWidth: `${TABSTRIP_TAB_WIDTH}px`,
            whiteSpace: 'nowrap',
            textOverflow: 'ellipsis',
            overflow: 'hidden',
            userSelect: 'none',
            cursor: 'default',
        });
        // appending
        this.parentDOM.appendChild(this.wrap);
        // event setting
        this.wrap.addEventListener('click', () => {
            this.emit('click', index);
        }, false);
        this.wrap.addEventListener('mouseenter', () => {
            appendStyle(this.wrap, {
                borderBottom: Tab.ACTIVE,
            });
        }, false);
        this.wrap.addEventListener('mouseleave', () => {
            this.update();
        }, false);
    }
    setActive(isActive){
        if(isActive == null){return;}
        this.active = isActive;
    }
    update(isActive){
        this.setActive(isActive);
        appendStyle(this.wrap, {
            color: this.active === true ? Tab.ACTIVE_COLOR : Tab.DEACTIVE_COLOR,
            borderBottom: this.active === true ? Tab.ACTIVE : Tab.DEACTIVE,
        });
    }
}

class Block extends Emitter {
    constructor(parentDOM, index, isActive = false){
        super();
        this.parentDOM = parentDOM;
        this.index = index;
        this.wrap = document.createElement('div');
        this.wrap.setAttribute('id', `page_${index}`);
        this.active = isActive;
        // styling
        appendStyle(this.wrap, {
            width: '100%',
            height: '100%',
            display: this.active === true ? 'block' : 'none',
            overflow: 'hidden',
        });
        // appending
        this.parentDOM.appendChild(this.wrap);
    }
    setActive(isActive){
        if(isActive == null){return;}
        this.active = isActive;
    }
    update(isActive){
        this.setActive(isActive);
        appendStyle(this.wrap, {
            display: this.active === true ? 'block' : 'none',
        });
    }
}

class TabStrip extends Emitter {
    constructor(parentDOM, tabs, selectedIndex){
        super();
        this.parentDOM = parentDOM;
        this.count = Math.max(1, tabs.length);
        this.index = Math.min(this.count - 1, selectedIndex);
        this.tabs = [];
        this.inners = [];
        this.wrap = document.createElement('div');
        this.tabBlock = document.createElement('div');
        this.innerBlock = document.createElement('div');

        // styling
        appendStyle(this.wrap, {
            width: '100%',
            height: '100%',
            display: 'flex',
            flexDirection: 'column',
        });
        appendStyle(this.tabBlock, {
            lineHeight: `${TABSTRIP_TAB_HEIGHT}px`,
            width: '100%',
            minHeight: `${TABSTRIP_TAB_HEIGHT + TABSTRIP_TAB_LINE_WIDTH}px`,
            maxHeight: `${TABSTRIP_TAB_HEIGHT + TABSTRIP_TAB_LINE_WIDTH}px`,
            display: 'flex',
            flexDirection: 'row',
        });
        appendStyle(this.innerBlock, {
            width: '100%',
            height: `calc(100% - ${TABSTRIP_TAB_HEIGHT + TABSTRIP_TAB_LINE_WIDTH}px)`,
        });

        // appending
        this.parentDOM.appendChild(this.wrap);
        this.wrap.appendChild(this.tabBlock);
        this.wrap.appendChild(this.innerBlock);
        tabs.forEach((v, index) => {
            let item = new Tab(this.tabBlock, index, v, this.index === index);
            item.on('click', (idx) => {
                this.tabs.forEach((tab, i) => {
                    this.tabs[i].update(idx === i);
                    this.inners[i].update(idx === i);
                });
            });
            this.tabs.push(item);
            let block = new Block(this.innerBlock, index, this.index === index);
            this.inners.push(block);
        });

        // event setting
    }
    getPage(index){
        return this.inners[index].wrap;
    }
}

function appendStyle(target, style){
    for(let s in style){
        target.style[s] = style[s];
    }
}

<|endoftext|>