<commit_before>'use strict';

const _ = require('lodash');
const has = require('has');
const { Status } = require('flora-cluster');

const Cast = require('./cast');
const Profiler = require('./profiler');

/**
 * Execute all DataSources.
 *
 * Executes all subFilters, the main request and the subRequests, and may be
 * recursively called by the subRequests.
 *
 * @param {Api} api
 * @param {flora.Request} request
 * @param {Object} dst DataSourceTree
 * @return {Promise<Object>}
 * @private
 */
async function executeDst(api, request, dst) {
    let globalResults;

    const cast = new Cast(api);

    // Execute subFilters
    let subFilters = [];
    if (dst.subFilters) {
        subFilters = dst.subFilters.map((subFilter) => {
            subFilter._profiler = dst._profiler;
            return executeDst(api, request, subFilter);
        });
    }
    const allSubFilterResults = await Promise.all(subFilters);

    // Adjust the main request's filter to include the results from the subFilters
    /*
     * allSubFilterResults:
     * Results from all subFilters.
     * - Outer array: foreach subFilter
     * - Inner array: list of results per subFilter request.
     *   As there are no subRequests in subFilters, the length is 1
     */
    if (dst.request.filter) {
        // Build filter array for the main request
        const parentValues = {};
        /*
        * parentValues:
        * case 1: combined key:
        *   indexed by concatenated filter field:
        *   Example:
        *     {
        *       'instrumentId-exchangeId': [
        *         [133962, 12],
        *         [134000, 12]
        *       ]
        *     }
        * case 2: single key:
        *    Example:
        *     {
        *       'instrumentId': [133962, 134000]
        *     }
        */

        allSubFilterResults.forEach((subFilterResults, index) => {
            const subFilterResult = subFilterResults[0]; // only main result from each subFilter
            parentValues[index] = [];

            const pvs = [];
            for (let i = 0; i < subFilterResult.data.length; i++) {
                if (subFilterResult.parentKey.length === 1) {
                    if (has(subFilterResult.data[i], subFilterResult.childKey[0])) {
                        pvs.push(subFilterResult.data[i][subFilterResult.childKey[0]]);
                    }
                } else {
                    let partEmpty = false;
                    const part = subFilterResult.childKey.map((childKeyPart) => {
                        if (!has(subFilterResult.data[i], childKeyPart)) {
                            partEmpty = true;
                            return null;
                        }
                        return subFilterResult.data[i][childKeyPart];
                    });
                    if (!partEmpty) pvs.push(part);
                }
            }
            parentValues[index].push(pvs);
        });

        // Prevent duplicates for all parentValues properties

        Object.keys(parentValues).forEach((parentKeyIdx) => {
            parentValues[parentKeyIdx] = parentValues[parentKeyIdx].map(
                parentValue => _.uniqWith(parentValue, _.isEqual)
            );
        });

        // Replace "valueFromSubFilter" properties for the main resource
        // by the values from the parentValues object
        const requestFilter = [];
        dst.request.filter.forEach((orFilter) => {
            const orFilterNew = [];
            orFilter.forEach((andFilter) => {
                if (dst._isEmpty) return;

                if (!has(andFilter, 'valueFromSubFilter')) {
                    orFilterNew.push(andFilter);
                    return;
                }

                if (!parentValues[andFilter.valueFromSubFilter]) {
                    throw new Error(`Missing subFilter for attribute "${andFilter.attribute}"`);
                }

                parentValues[andFilter.valueFromSubFilter].forEach((pv) => {
                    if (dst._isEmpty) return;

                    const andfilterNew = Object.assign({}, andFilter);

                    if (pv.length === 0) {
                        andfilterNew.empty = true;
                    } else if (!Array.isArray(andFilter.attribute)) {
                        andfilterNew.value = pv;
                    } else {
                        andfilterNew.value = [];
                        for (let j = 0; j < andFilter.attribute.length; j++) {
                            andfilterNew.value[j] = pv[j];
                        }
                    }

                    orFilterNew.push(andfilterNew);
                });
            });
            if (orFilterNew.length > 0) requestFilter.push(orFilterNew);
        });

        // Remove AND filters with no results. If there are no OR filters left,
        // there are not results at all, so skip any datasource queries from here
        // TODO: move this to the loop above to avoid this additional run
        const newRequestFilter = requestFilter.filter(
            orFilter => !orFilter.some(andFilter => andFilter.empty)
        );
        if (newRequestFilter.length === 0) dst._isEmpty = true;

        dst.request.filter = newRequestFilter;
    }

    // Cast filter values to their storedType
    if (!dst._isEmpty && dst.request.filter && dst.attributeOptions) {
        dst.request.filter.forEach((filter) => {
            if (!filter) return;

            filter.forEach((filterPart) => {
                if (Array.isArray(filterPart.attribute)) {
                    filterPart.attribute.forEach((attrPart, index) => {
                        const storedType = (dst.attributeOptions[attrPart])
                            ? dst.attributeOptions[attrPart].storedType
                            : null;
                        if (!storedType) return;
                        if (Array.isArray(filterPart.value[index])) {
                            filterPart.value[index] = filterPart.value[index].map(
                                valuePart => cast.cast(valuePart, storedType)
                            );
                        } else {
                            filterPart.value[index] = cast.cast(
                                filterPart.value[index],
                                storedType
                            );
                        }
                    });
                } else {
                    const storedType = (dst.attributeOptions[filterPart.attribute])
                        ? dst.attributeOptions[filterPart.attribute].storedType
                        : null;
                    if (!storedType) return;
                    if (Array.isArray(filterPart.value)) {
                        filterPart.value = filterPart.value.map(
                            valuePart => cast.cast(valuePart, storedType)
                        );
                    } else {
                        filterPart.value = cast.cast(filterPart.value, storedType);
                    }
                }
            });
        });
    }

    // Extension: "preExecute" (resource)
    if (dst.resourceName) {
        const resource = api.getResource(dst.resourceName);
        if (resource && resource.extensions && resource.extensions.preExecute) {
            api.log.trace('handle: "preExecute" extension (%s)', dst.resourceName);
            await resource.extensions.preExecute({
                name: dst.dataSourceName,
                request: dst.request,
                dataSourceTree: dst,
                floraRequest: request
            });
        }
    }

    // Execute the main request
    const mainResults = {
        data: [],
        totalCount: 0
    };
    [
        'attributePath', 'dataSourceName', 'childKey', 'parentKey',
        'multiValuedParentKey', 'uniqueChildKey'
    ].forEach((key) => {
        if (has(dst, key)) mainResults[key] = dst[key];
    });

    if (!dst._isEmpty) {
        const ds = api.dataSources[dst.request.type];
        if (!ds) throw new Error(`Unknown DataSource type "${dst.request.type}"`);

        // TODO: Move requestName generation into request-resolver:
        let requestName = 'unnamedRequest';
        if (dst.attributePath) {
            requestName = (dst.attributePath.length > 0 ? dst.attributePath.join('.') : '{root}')
                + ':' + dst.dataSourceName;
        }

        const status = request._status ? request._status.addChild('dataSourceRequests') : new Status();
        dst.request._status = status;
        status.set('requestName', requestName);
        status.set('type', dst.request.type);

        if (request._explain) dst.request._explain = {};

        const profiler = dst._profiler.child(requestName);
        dst.request._profiler = profiler;

        try {
            const rows = await ds.process(dst.request);
            profiler.end();
            status.close();

            if (dst.request._explain) {
                if (Array.isArray(rows.data)) {
                    dst.request._explain.countRows = rows.data.length;
                }
                if (!dst.request._explain.duration) {
                    dst.request._explain.duration = profiler.getDuration();
                }
            }

            mainResults.data = rows.data;
            mainResults.totalCount = rows.totalCount;
        } catch (err) {
            profiler.end();
            status.close();

            if (dst.request._explain) {
                dst.request._explain.error = '' + err;
                if (!dst.request._explain.duration) {
                    dst.request._explain.duration = profiler.getDuration();
                }
            }

            err.info = err.info || {};
            err.info.dataSource = requestName;
            throw err;
        }
    }

    // Transform the main results, do type casting
    /*
     * mainResults:
     * {
     *   "data": [
     *      {id: 1, firstname: "Alice"},
     *      {id: 3, firstname: "Bob"}, ...
     *   ]
     *   "totalCount": null // or the real count
     * }
     */
    if (dst.attributeOptions && typeof dst.attributeOptions === 'object') {
        mainResults.data.forEach((result) => {
            if (typeof result !== 'object') return;
            Object.keys(result).forEach((key) => {
                if (!dst.attributeOptions[key] || !dst.attributeOptions[key].type) return;
                result[key] = cast.cast(result[key], dst.attributeOptions[key]);
            });
        });
    }

    // Extension: "postExecute" (resource)
    if (dst.resourceName) {
        const resource = api.getResource(dst.resourceName);
        if (resource && resource.extensions && resource.extensions.postExecute) {
            api.log.trace('handle: "postExecute" extension (%s)', dst.resourceName);
            await resource.extensions.postExecute({
                name: dst.dataSourceName,
                request: dst.request,
                floraRequest: request,
                rawResults: mainResults
            });
        }
    }

    // Add the main results to our global results array
    /*
     * globalResults
     * This is whats is being returned by executeDst:
     * [
     *    {data: ...}, // results of the main request
     *    {data: ...}, // results of the first subRequest
     *    {data: ...}, // results of the second subRequest
     *    ...
     * ]
     */
    globalResults = [mainResults];

    // Execute the subRequests
    if (dst.subRequests && mainResults && mainResults.data.length > 0) {
        // Prepare the subRequests that may be dependent from the main results.
        // "valueFromParentKey" properties are replaced by the appropriate values.
        const subRequests = dst.subRequests.map((subRequest) => {
            let parentValues = [];

            const flatten = (subRequest.parentKey.length === 1
                && dst.attributeOptions
                && dst.attributeOptions[subRequest.parentKey[0]]
                && dst.attributeOptions[subRequest.parentKey[0]].multiValued);

            mainResults.data.forEach((mainResult) => {
                let isNull = true;
                let value;
                if (subRequest.parentKey.length === 1) {
                    value = mainResult[subRequest.parentKey[0]];
                    if (value !== null && typeof value !== 'undefined') isNull = false;
                } else {
                    value = subRequest.parentKey.map((m) => {
                        if (mainResult[m] !== null && typeof mainResult[m] !== 'undefined') isNull = false;
                        return mainResult[m];
                    });
                }
                if (isNull) return;

                if (flatten) {
                    if (Array.isArray(value)) Array.prototype.push.apply(parentValues, value);
                } else {
                    parentValues.push(value);
                }
            });

            if (parentValues.length === 0) subRequest._isEmpty = true;
            parentValues = (subRequest.parentKey.length === 1)
                ? _.uniq(parentValues)
                : _.uniqWith(parentValues, _.isEqual);

            subRequest.request.filter.forEach((filters) => {
                filters.forEach((filter) => {
                    if (!filter.valueFromParentKey) return;
                    filter.value = parentValues;
                });
            });

            subRequest._profiler = dst._profiler;
            subRequest.request._parentResults = mainResults;
            return executeDst(api, request, subRequest);
        });

        const subResults = await Promise.all(subRequests);
        subResults.forEach((subResult) => {
            globalResults = globalResults.concat(subResult);
        });
    }

    // Add the subRequest results to the global results array
    delete dst._isEmpty; // clean up

    return globalResults;
}

/**
 * Actually execute a resolved request and call the DataSources.
 *
 * @param {Api} api
 * @param {flora.Request} request
 * @param {Object} dst DataSourceTree
 * @return {Promise<Object>}
 */
module.exports = function execute(api, request, dst) {
    dst._profiler = dst._profiler || new Profiler();
    return executeDst(api, request, dst);
};
<commit_msg>Fix performance issues by optimizing uniq values in datasource-executor<commit_after>'use strict';

const has = require('has');
const { Status } = require('flora-cluster');

const Cast = require('./cast');
const Profiler = require('./profiler');

/**
 * @param {Array} array Array of scalar values
 * @returns {Array}
 */
function uniq(array) {
    return [...new Set(array)];
}

/**
 * @param {Array} array Array of Arrays with scalar values
 * @returns {Array}
 */
function uniqArrays(array) {
    const map = new Map();
    const separator = '-';

    return array.filter((key) => {
        const jKey = Array.isArray(key) ? key.join(separator) : key;
        if (map.has(jKey)) return false;
        map.set(jKey, true);
        return true;
    });
}

/**
 * Execute all DataSources.
 *
 * Executes all subFilters, the main request and the subRequests, and may be
 * recursively called by the subRequests.
 *
 * @param {Api} api
 * @param {flora.Request} request
 * @param {Object} dst DataSourceTree
 * @return {Promise<Object>}
 * @private
 */
async function executeDst(api, request, dst) {
    let globalResults;

    const cast = new Cast(api);

    // Execute subFilters
    let subFilters = [];
    if (dst.subFilters) {
        subFilters = dst.subFilters.map((subFilter) => {
            subFilter._profiler = dst._profiler;
            return executeDst(api, request, subFilter);
        });
    }
    const allSubFilterResults = await Promise.all(subFilters);

    // Adjust the main request's filter to include the results from the subFilters
    /*
     * allSubFilterResults:
     * Results from all subFilters.
     * - Outer array: foreach subFilter
     * - Inner array: list of results per subFilter request.
     *   As there are no subRequests in subFilters, the length is 1
     */
    if (dst.request.filter) {
        // Build filter array for the main request
        const parentValues = {};
        /*
        * parentValues:
        * case 1: combined key:
        *   indexed by concatenated filter field:
        *   Example:
        *     {
        *       'instrumentId-exchangeId': [
        *         [133962, 12],
        *         [134000, 12]
        *       ]
        *     }
        * case 2: single key:
        *    Example:
        *     {
        *       'instrumentId': [133962, 134000]
        *     }
        */

        allSubFilterResults.forEach((subFilterResults, index) => {
            const subFilterResult = subFilterResults[0]; // only main result from each subFilter
            parentValues[index] = [];

            const pvs = [];
            for (let i = 0; i < subFilterResult.data.length; i++) {
                if (subFilterResult.parentKey.length === 1) {
                    if (has(subFilterResult.data[i], subFilterResult.childKey[0])) {
                        pvs.push(subFilterResult.data[i][subFilterResult.childKey[0]]);
                    }
                } else {
                    let partEmpty = false;
                    const part = subFilterResult.childKey.map((childKeyPart) => {
                        if (!has(subFilterResult.data[i], childKeyPart)) {
                            partEmpty = true;
                            return null;
                        }
                        return subFilterResult.data[i][childKeyPart];
                    });
                    if (!partEmpty) pvs.push(part);
                }
            }
            parentValues[index].push(pvs);
        });

        // Prevent duplicates for all parentValues properties

        Object.keys(parentValues).forEach((parentKeyIdx) => {
            parentValues[parentKeyIdx] = parentValues[parentKeyIdx].map(
                parentValue => uniqArrays(parentValue)
            );
        });

        // Replace "valueFromSubFilter" properties for the main resource
        // by the values from the parentValues object
        const requestFilter = [];
        dst.request.filter.forEach((orFilter) => {
            const orFilterNew = [];
            orFilter.forEach((andFilter) => {
                if (dst._isEmpty) return;

                if (!has(andFilter, 'valueFromSubFilter')) {
                    orFilterNew.push(andFilter);
                    return;
                }

                if (!parentValues[andFilter.valueFromSubFilter]) {
                    throw new Error(`Missing subFilter for attribute "${andFilter.attribute}"`);
                }

                parentValues[andFilter.valueFromSubFilter].forEach((pv) => {
                    if (dst._isEmpty) return;

                    const andfilterNew = Object.assign({}, andFilter);

                    if (pv.length === 0) {
                        andfilterNew.empty = true;
                    } else if (!Array.isArray(andFilter.attribute)) {
                        andfilterNew.value = pv;
                    } else {
                        andfilterNew.value = [];
                        for (let j = 0; j < andFilter.attribute.length; j++) {
                            andfilterNew.value[j] = pv[j];
                        }
                    }

                    orFilterNew.push(andfilterNew);
                });
            });
            if (orFilterNew.length > 0) requestFilter.push(orFilterNew);
        });

        // Remove AND filters with no results. If there are no OR filters left,
        // there are not results at all, so skip any datasource queries from here
        // TODO: move this to the loop above to avoid this additional run
        const newRequestFilter = requestFilter.filter(
            orFilter => !orFilter.some(andFilter => andFilter.empty)
        );
        if (newRequestFilter.length === 0) dst._isEmpty = true;

        dst.request.filter = newRequestFilter;
    }

    // Cast filter values to their storedType
    if (!dst._isEmpty && dst.request.filter && dst.attributeOptions) {
        dst.request.filter.forEach((filter) => {
            if (!filter) return;

            filter.forEach((filterPart) => {
                if (Array.isArray(filterPart.attribute)) {
                    filterPart.attribute.forEach((attrPart, index) => {
                        const storedType = (dst.attributeOptions[attrPart])
                            ? dst.attributeOptions[attrPart].storedType
                            : null;
                        if (!storedType) return;
                        if (Array.isArray(filterPart.value[index])) {
                            filterPart.value[index] = filterPart.value[index].map(
                                valuePart => cast.cast(valuePart, storedType)
                            );
                        } else {
                            filterPart.value[index] = cast.cast(
                                filterPart.value[index],
                                storedType
                            );
                        }
                    });
                } else {
                    const storedType = (dst.attributeOptions[filterPart.attribute])
                        ? dst.attributeOptions[filterPart.attribute].storedType
                        : null;
                    if (!storedType) return;
                    if (Array.isArray(filterPart.value)) {
                        filterPart.value = filterPart.value.map(
                            valuePart => cast.cast(valuePart, storedType)
                        );
                    } else {
                        filterPart.value = cast.cast(filterPart.value, storedType);
                    }
                }
            });
        });
    }

    // Extension: "preExecute" (resource)
    if (dst.resourceName) {
        const resource = api.getResource(dst.resourceName);
        if (resource && resource.extensions && resource.extensions.preExecute) {
            api.log.trace('handle: "preExecute" extension (%s)', dst.resourceName);
            await resource.extensions.preExecute({
                name: dst.dataSourceName,
                request: dst.request,
                dataSourceTree: dst,
                floraRequest: request
            });
        }
    }

    // Execute the main request
    const mainResults = {
        data: [],
        totalCount: 0
    };
    [
        'attributePath', 'dataSourceName', 'childKey', 'parentKey',
        'multiValuedParentKey', 'uniqueChildKey'
    ].forEach((key) => {
        if (has(dst, key)) mainResults[key] = dst[key];
    });

    if (!dst._isEmpty) {
        const ds = api.dataSources[dst.request.type];
        if (!ds) throw new Error(`Unknown DataSource type "${dst.request.type}"`);

        // TODO: Move requestName generation into request-resolver:
        let requestName = 'unnamedRequest';
        if (dst.attributePath) {
            requestName = (dst.attributePath.length > 0 ? dst.attributePath.join('.') : '{root}')
                + ':' + dst.dataSourceName;
        }

        const status = request._status ? request._status.addChild('dataSourceRequests') : new Status();
        dst.request._status = status;
        status.set('requestName', requestName);
        status.set('type', dst.request.type);

        if (request._explain) dst.request._explain = {};

        const profiler = dst._profiler.child(requestName);
        dst.request._profiler = profiler;

        try {
            const rows = await ds.process(dst.request);
            profiler.end();
            status.close();

            if (dst.request._explain) {
                if (Array.isArray(rows.data)) {
                    dst.request._explain.countRows = rows.data.length;
                }
                if (!dst.request._explain.duration) {
                    dst.request._explain.duration = profiler.getDuration();
                }
            }

            mainResults.data = rows.data;
            mainResults.totalCount = rows.totalCount;
        } catch (err) {
            profiler.end();
            status.close();

            if (dst.request._explain) {
                dst.request._explain.error = '' + err;
                if (!dst.request._explain.duration) {
                    dst.request._explain.duration = profiler.getDuration();
                }
            }

            err.info = err.info || {};
            err.info.dataSource = requestName;
            throw err;
        }
    }

    // Transform the main results, do type casting
    /*
     * mainResults:
     * {
     *   "data": [
     *      {id: 1, firstname: "Alice"},
     *      {id: 3, firstname: "Bob"}, ...
     *   ]
     *   "totalCount": null // or the real count
     * }
     */
    if (dst.attributeOptions && typeof dst.attributeOptions === 'object') {
        mainResults.data.forEach((result) => {
            if (typeof result !== 'object') return;
            Object.keys(result).forEach((key) => {
                if (!dst.attributeOptions[key] || !dst.attributeOptions[key].type) return;
                result[key] = cast.cast(result[key], dst.attributeOptions[key]);
            });
        });
    }

    // Extension: "postExecute" (resource)
    if (dst.resourceName) {
        const resource = api.getResource(dst.resourceName);
        if (resource && resource.extensions && resource.extensions.postExecute) {
            api.log.trace('handle: "postExecute" extension (%s)', dst.resourceName);
            await resource.extensions.postExecute({
                name: dst.dataSourceName,
                request: dst.request,
                floraRequest: request,
                rawResults: mainResults
            });
        }
    }

    // Add the main results to our global results array
    /*
     * globalResults
     * This is whats is being returned by executeDst:
     * [
     *    {data: ...}, // results of the main request
     *    {data: ...}, // results of the first subRequest
     *    {data: ...}, // results of the second subRequest
     *    ...
     * ]
     */
    globalResults = [mainResults];

    // Execute the subRequests
    if (dst.subRequests && mainResults && mainResults.data.length > 0) {
        // Prepare the subRequests that may be dependent from the main results.
        // "valueFromParentKey" properties are replaced by the appropriate values.
        const subRequests = dst.subRequests.map((subRequest) => {
            let parentValues = [];

            const flatten = (subRequest.parentKey.length === 1
                && dst.attributeOptions
                && dst.attributeOptions[subRequest.parentKey[0]]
                && dst.attributeOptions[subRequest.parentKey[0]].multiValued);

            mainResults.data.forEach((mainResult) => {
                let isNull = true;
                let value;
                if (subRequest.parentKey.length === 1) {
                    value = mainResult[subRequest.parentKey[0]];
                    if (value !== null && typeof value !== 'undefined') isNull = false;
                } else {
                    value = subRequest.parentKey.map((m) => {
                        if (mainResult[m] !== null && typeof mainResult[m] !== 'undefined') isNull = false;
                        return mainResult[m];
                    });
                }
                if (isNull) return;

                if (flatten) {
                    if (Array.isArray(value)) Array.prototype.push.apply(parentValues, value);
                } else {
                    parentValues.push(value);
                }
            });

            if (parentValues.length === 0) subRequest._isEmpty = true;
            parentValues = (subRequest.parentKey.length === 1)
                ? uniq(parentValues)
                : uniqArrays(parentValues);

            subRequest.request.filter.forEach((filters) => {
                filters.forEach((filter) => {
                    if (!filter.valueFromParentKey) return;
                    filter.value = parentValues;
                });
            });

            subRequest._profiler = dst._profiler;
            subRequest.request._parentResults = mainResults;
            return executeDst(api, request, subRequest);
        });

        const subResults = await Promise.all(subRequests);
        subResults.forEach((subResult) => {
            globalResults = globalResults.concat(subResult);
        });
    }

    // Add the subRequest results to the global results array
    delete dst._isEmpty; // clean up

    return globalResults;
}

/**
 * Actually execute a resolved request and call the DataSources.
 *
 * @param {Api} api
 * @param {flora.Request} request
 * @param {Object} dst DataSourceTree
 * @return {Promise<Object>}
 */
module.exports = function execute(api, request, dst) {
    dst._profiler = dst._profiler || new Profiler();
    return executeDst(api, request, dst);
};
<|endoftext|>


<commit_before>/*global $, jQuery*/

/*
*
* Yet Another DataTables Column Filter - (yadcf)
*
* File:        jquery.dataTables.yadcf.js
* Version:     0.1
* Author:      Daniel Reznick
* Info:        https://github.com/vedmack/yadcf
*
* Copyright 2013 Daniel Reznick, all rights reserved.
*
* Dual licensed under two licenses: GPL v2 license or a BSD (3-point) license (just like DataTables itself)
*
* This source file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
*
* Parameters:
*
*
* -------------

* column_number
				Required:			true
				Type:				String
				Description:		The number of the column to which the filter will be applied

* data
				Required:			false
				Type:				Array
				Description:		When the need of predefined data for filter is needed just use an array ["value1","value2"....]

* column_data_type
				Required:			false
				Type:				String
				Default value:		text
				Possible values:	text / html
				Description:		The type of data in column , use "html" when you have some html code in the column (support parsing of multiple elements per cell)

* text_data_delimiter
				Required:			false
				Type:				String
				Description:		Delimiter that seperates text in table column, for example text_data_delimiter: ","

* html_data_type
				Required:			false
				Type:				String
				Default value:		text
				Possible values:	text / value / id
				Description:		When using "html" for column_data_type argument you can choose how exactly to parse your html element/s in column , for example use "text" for the following Some text

* filter_container_id
				Required:			false
				Type:				String
				Description:		In case that user don't want to place the filter in column header , he can pass an id of the desired container for the column filter

* filter_default_label
				Required:			false
				Type:				String
				Default value:		Select value
				Description:		The label that will appear in the select menu filter when no value is selected from the filter

* filter_reset_button_text
				Required:			false
				Type:				String
				Default value:		x
				Description:		The text that will appear inside the reset button next to the select drop down
*
*
*/
var yadcf = (function ($) {

	'use strict';

	var oTable,
		options;

	function appendSelectFilter(args) {

		var i = 0,
			$filter_selector,
			filter_selector_string,

			data,
			filter_container_id,
			column_number,
			column_data_type,
			html_data_type,
			text_data_delimiter,
			filter_default_label,
			filter_reset_button_text,

			options,
			j,
			k,
			data_length,
			col_inner_elements,
			col_inner_data,
			col_filter_array = {},
			ii;

		for (i; i < args.length; i++) {

			data = args[i].data;
			filter_container_id = args[i].filter_container_id;
			column_number = args[i].column_number;
			column_data_type = args[i].column_data_type;
			html_data_type = args[i].html_data_type;
			text_data_delimiter = args[i].text_data_delimiter;
			filter_default_label = args[i].filter_default_label;
			filter_reset_button_text = args[i].filter_reset_button_text;

			if (column_number === undefined) {
				alert("You must specify column number");
				return;
			}

			if (column_data_type === undefined) {
				column_data_type = "text";
			} else if (column_data_type === "html") {
				if (html_data_type === undefined) {
					html_data_type = "text";
				}
			}

			if (filter_default_label === undefined) {
				filter_default_label = "Select value";
			}

			if (filter_reset_button_text === undefined) {
				filter_reset_button_text = "x";
			}

			options = "<option value=\"" + "-1" + "\">" + filter_default_label + "</option>";

			if (data === undefined) {
				data = oTable._('tr');
				data_length = data.length;

				for (j = 0; j < data_length; j++) {
					if (column_data_type === "html") {
						col_inner_elements = $(data[j][column_number]);
						for (k = 0; k < col_inner_elements.length; k++) {
							switch (html_data_type) {
							case "text":
								col_inner_data = $(col_inner_elements[k]).text();
								break;
							case "value":
								col_inner_data = $(col_inner_elements[k]).val();
								break;
							case "id":
								col_inner_data = col_inner_elements[k].id;
								break;
							}
							if (!(col_filter_array.hasOwnProperty(col_inner_data))) {
								col_filter_array[col_inner_data] = col_inner_data;
								options += '<option value="' + col_inner_data + '">' + col_inner_data + '</option>';
							}
						}
					} else if (column_data_type === "text") {
						if (text_data_delimiter !== undefined) {
							col_inner_elements = data[j][column_number].split(text_data_delimiter);
							for (k = 0; k < col_inner_elements.length; k++) {
								col_inner_data = col_inner_elements[k];
								if (!(col_filter_array.hasOwnProperty(col_inner_data))) {
									col_filter_array[col_inner_data] = col_inner_data;
									options += '<option value="' + col_inner_data + '">' + col_inner_data + '</option>';
								}
							}
						} else {
							col_inner_data = data[j][column_number];
							if (!(col_filter_array.hasOwnProperty(col_inner_data))) {
								col_filter_array[col_inner_data] = col_inner_data;
								options += '<option value="' + col_inner_data + '">' + col_inner_data + '</option>';
							}
						}
					}
				}

			} else {
				for (ii = 0; ii < data.length; ii++) {
					options += '<option value="' + data[ii] + '">' + data[ii] + '</option>';
				}
			}

			if (filter_container_id === undefined) {
				filter_selector_string = oTable.selector + " thead th:eq(" + column_number + ")";
				$filter_selector = $(filter_selector_string).find(".yadcf-filter");
			} else {
				filter_selector_string = filter_container_id;
				$filter_selector = $("#" + filter_selector_string).find(".yadcf-filter");
			}

			if ($filter_selector.length === 1) {
				$filter_selector.empty();
				$filter_selector.append(options);
			} else {

				if (filter_container_id === undefined) {
					$(filter_selector_string).append("<select class=\"yadcf-filter\" " +
							"onchange=\"yadcf.doFilter('" + filter_selector_string + "', this, " + column_number + ")\" onclick='event.cancelBubble = true;event.stopPropagation();'>" + options + "</select>");
					$(filter_selector_string).find(".yadcf-filter").prev().css("display", "inline-block");

					$(filter_selector_string).find(".yadcf-filter").after("<input value=\"" + filter_reset_button_text + "\" type=\"button\" " +
							"onclick=\"event.cancelBubble = true;event.stopPropagation();yadcf.doFilter('" + filter_selector_string + "', 'clear', " + column_number + "); return false;\" class=\"yadcf-filter-reset-button\">");

				} else {

					if ($("#" + filter_container_id).length === 0) {
						alert("Filter container could not be found.");
					}

					$("<select class=\"yadcf-filter\" " +
							"onchange=\"yadcf.doFilter('" + filter_selector_string + "', this, " + column_number + ")\" onclick='event.cancelBubble = true;event.stopPropagation();'>" +
							options + "</select>").appendTo("#" + filter_container_id);

					$("#" + filter_container_id).find(".yadcf-filter").after("<input value=\"" + filter_reset_button_text + "\" type=\"button\" " +
							"onclick=\"event.cancelBubble = true;event.stopPropagation();yadcf.doFilter('" + "#" + filter_container_id + "', 'clear', " + column_number + "); return false;\" class=\"yadcf-filter-reset-button\">");

				}

			}

			//load value from document data
			if ($(document).data(filter_selector_string + "_val") !== undefined) {
				$(filter_selector_string).find(".yadcf-filter").val($(document).data(filter_selector_string + "_val"));
			}
		}
	}

	function doFilter(filter_selector_string, arg, column_number) {
		if (arg === "clear") {
			$(filter_selector_string).find(".yadcf-filter").val("-1");
			oTable.fnFilter("", column_number);
			return;
		}

		$(document).data(filter_selector_string + "_val", arg.value);

		if (arg.value !== "-1") {
			oTable.fnFilter($(arg).find('option:selected').text(), column_number);
		} else {
			oTable.fnFilter("", column_number);
		}
	}

	function getOptions() {
		return options;
	}

    $.fn.yadcf = function (options_arg) {
        oTable = this;
        options = options_arg;

        if (oTable.fnSettings().sAjaxSource === null) {
			appendSelectFilter(yadcf.getOptions());
        } else {
	        if (parseFloat($().jquery) >= 1.7) {
				$(document).on("draw", oTable, function (event) {
					appendSelectFilter(yadcf.getOptions());
	            });
	        } else {
				$(document).delegate(oTable, "draw", function () {
					appendSelectFilter(yadcf.getOptions());
				});
	        }
        }

        return oTable;

    };

    return {
		doFilter : doFilter,
		getOptions : getOptions
    };

}(jQuery));
<commit_msg>Update jquery.dataTables.yadcf.js<commit_after>/*global $, jQuery*/

/*
*
* Yet Another DataTables Column Filter - (yadcf)
*
* File:        jquery.dataTables.yadcf.js
* Version:     0.1
* Author:      Daniel Reznick
* Info:        https://github.com/vedmack/yadcf
*
* Copyright 2013 Daniel Reznick, all rights reserved.
*
* Dual licensed under two licenses: GPL v2 license or a BSD (3-point) license (just like DataTables itself)
*
* This source file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
*
* Parameters:
*
*
* -------------

* column_number
				Required:			true
				Type:				String
				Description:		The number of the column to which the filter will be applied

* data
				Required:			false
				Type:				Array
				Description:		When the need of predefined data for filter is needed just use an array ["value1","value2"....]

* column_data_type
				Required:			false
				Type:				String
				Default value:		text
				Possible values:	text / html
				Description:		The type of data in column , use "html" when you have some html code in the column (support parsing of multiple elements per cell)

* text_data_delimiter
				Required:			false
				Type:				String
				Description:		Delimiter that seperates text in table column, for example text_data_delimiter: ","

* html_data_type
				Required:			false
				Type:				String
				Default value:		text
				Possible values:	text / value / id
				Description:		When using "html" for column_data_type argument you can choose how exactly to parse your html element/s in column , for example use "text" for the following Some text

* filter_container_id
				Required:			false
				Type:				String
				Description:		In case that user don't want to place the filter in column header , he can pass an id of the desired container for the column filter

* filter_default_label
				Required:			false
				Type:				String
				Default value:		Select value
				Description:		The label that will appear in the select menu filter when no value is selected from the filter

* filter_reset_button_text
				Required:			false
				Type:				String
				Default value:		x
				Description:		The text that will appear inside the reset button next to the select drop down
*
*
*/
var yadcf = (function ($) {

	'use strict';

	var oTable,
		options;

	function appendSelectFilter(args) {

		var i = 0,
			$filter_selector,
			filter_selector_string,

			data,
			filter_container_id,
			column_number,
			column_data_type,
			html_data_type,
			text_data_delimiter,
			filter_default_label,
			filter_reset_button_text,

			options,
			j,
			k,
			data_length,
			col_inner_elements,
			col_inner_data,
			col_filter_array = {},
			ii;

		for (i; i < args.length; i++) {

			data = args[i].data;
			filter_container_id = args[i].filter_container_id;
			column_number = args[i].column_number;
			column_data_type = args[i].column_data_type;
			html_data_type = args[i].html_data_type;
			text_data_delimiter = args[i].text_data_delimiter;
			filter_default_label = args[i].filter_default_label;
			filter_reset_button_text = args[i].filter_reset_button_text;

			if (column_number === undefined) {
				alert("You must specify column number");
				return;
			}

			if (column_data_type === undefined) {
				column_data_type = "text";
			} else if (column_data_type === "html") {
				if (html_data_type === undefined) {
					html_data_type = "text";
				}
			}

			if (filter_default_label === undefined) {
				filter_default_label = "Select value";
			}

			if (filter_reset_button_text === undefined) {
				filter_reset_button_text = "x";
			}

			options = "<option value=\"" + "-1" + "\">" + filter_default_label + "</option>";

			if (data === undefined) {
				data = oTable._('tr');
				data_length = data.length;

				for (j = 0; j < data_length; j++) {
					if (column_data_type === "html") {
						col_inner_elements = $(data[j][column_number]);
						for (k = 0; k < col_inner_elements.length; k++) {
							switch (html_data_type) {
							case "text":
								col_inner_data = $(col_inner_elements[k]).text();
								break;
							case "value":
								col_inner_data = $(col_inner_elements[k]).val();
								break;
							case "id":
								col_inner_data = col_inner_elements[k].id;
								break;
							}
							if (!(col_filter_array.hasOwnProperty(col_inner_data))) {
								col_filter_array[col_inner_data] = col_inner_data;
								options += '<option value="' + col_inner_data + '">' + col_inner_data + '</option>';
							}
						}
					} else if (column_data_type === "text") {
						if (text_data_delimiter !== undefined) {
							col_inner_elements = data[j][column_number].split(text_data_delimiter);
							for (k = 0; k < col_inner_elements.length; k++) {
								col_inner_data = col_inner_elements[k];
								if (!(col_filter_array.hasOwnProperty(col_inner_data))) {
									col_filter_array[col_inner_data] = col_inner_data;
									options += '<option value="' + col_inner_data + '">' + col_inner_data + '</option>';
								}
							}
						} else {
							col_inner_data = data[j][column_number];
							if (!(col_filter_array.hasOwnProperty(col_inner_data))) {
								col_filter_array[col_inner_data] = col_inner_data;
								options += '<option value="' + col_inner_data + '">' + col_inner_data + '</option>';
							}
						}
					}
				}

			} else {
				for (ii = 0; ii < data.length; ii++) {
					options += '<option value="' + data[ii] + '">' + data[ii] + '</option>';
				}
			}

			if (filter_container_id === undefined) {
				filter_selector_string = oTable.selector + " thead th:eq(" + column_number + ")";
				$filter_selector = $(filter_selector_string).find(".yadcf-filter");
			} else {
				filter_selector_string = filter_container_id;
				$filter_selector = $("#" + filter_selector_string).find(".yadcf-filter");
			}

			if ($filter_selector.length === 1) {
				$filter_selector.empty();
				$filter_selector.append(options);
			} else {

				if (filter_container_id === undefined) {
					$(filter_selector_string).append("<select class=\"yadcf-filter\" " +
							"onchange=\"yadcf.doFilter('" + filter_selector_string + "', this, " + column_number + ")\" onclick='event.cancelBubble = true;event.stopPropagation();'>" + options + "</select>");
					$(filter_selector_string).find(".yadcf-filter").prev().css("display", "inline-block");

					$(filter_selector_string).find(".yadcf-filter").after("<input value=\"" + filter_reset_button_text + "\" type=\"button\" " +
							"onclick=\"event.cancelBubble = true;event.stopPropagation();yadcf.doFilter('" + filter_selector_string + "', 'clear', " + column_number + "); return false;\" class=\"yadcf-filter-reset-button\">");

				} else {

					if ($("#" + filter_container_id).length === 0) {
						alert("Filter container could not be found.");
					}

					$("<select class=\"yadcf-filter\" " +
							"onchange=\"yadcf.doFilter('" + filter_selector_string + "', this, " + column_number + ")\" onclick='event.cancelBubble = true;event.stopPropagation();'>" +
							options + "</select>").appendTo("#" + filter_container_id);

					$("#" + filter_container_id).find(".yadcf-filter").after("<input value=\"" + filter_reset_button_text + "\" type=\"button\" " +
							"onclick=\"event.cancelBubble = true;event.stopPropagation();yadcf.doFilter('" + "#" + filter_container_id + "', 'clear', " + column_number + "); return false;\" class=\"yadcf-filter-reset-button\">");

				}

			}

			//load value from document data
			if ($(document).data(filter_selector_string + "_val") !== undefined && $(document).data(filter_selector_string + "_val") !== "-1") {
				$(filter_selector_string).find(".yadcf-filter").val($(document).data(filter_selector_string + "_val"));
			}
		}
	}

	function doFilter(filter_selector_string, arg, column_number) {
		if (arg === "clear") {
			$(filter_selector_string).find(".yadcf-filter").val("-1");
			$(document).data(filter_selector_string + "_val", "-1");
			oTable.fnFilter("", column_number);
			$(filter_selector_string).find(".yadcf-filter-reset-button").focus();
			return;
		}

		$(document).data(filter_selector_string + "_val", arg.value);

		if (arg.value !== "-1") {
			oTable.fnFilter($(arg).find('option:selected').text(), column_number);
		} else {
			oTable.fnFilter("", column_number);
		}
	}

	function getOptions() {
		return options;
	}

    $.fn.yadcf = function (options_arg) {
        oTable = this;
        options = options_arg;

        if (oTable.fnSettings().sAjaxSource === null) {
			appendSelectFilter(yadcf.getOptions());
        } else {
	        if (parseFloat($().jquery) >= 1.7) {
				$(document).on("draw", oTable, function (event) {
					appendSelectFilter(yadcf.getOptions());
	            });
	        } else {
				$(document).delegate(oTable, "draw", function () {
					appendSelectFilter(yadcf.getOptions());
				});
	        }
        }

        return oTable;

    };

    return {
		doFilter : doFilter,
		getOptions : getOptions
    };

}(jQuery));
<|endoftext|>


<commit_before>'use strict';

// MODULES //

var ltrim = require( '@stdlib/string/left-trim' );
var rtrim = require( '@stdlib/string/right-trim' );

// MAIN //

/**
* Trim whitespace characters from beginning and end of a string.
*
* @param {string} str - input string
* @throws {TypeError} must provide a string primitive
* @returns {string} trimmed string
*
* @example
* var out = trim( '   Whitespace   ' );
* // returns 'Whitespace'
*
* @example
* var out = trim( '\t\t\tTabs\t\t\t' );
* // returns 'Tabs'
*
* @example
* var out = trim( '\n\n\nNew Lines\n\n\n' ) );
* // returns 'New Lines'
*/
function trim( str ) {
	return ltrim( rtrim( str ) );
} // end FUNCTION trim()

// EXPORTS //

module.exports = trim;
<commit_msg>Add internal implementation to increase performance<commit_after>'use strict';

// MODULES //

var isString = require( '@stdlib/assert/is-string' ).isPrimitive;
var replace = require( '@stdlib/string/replace' );

// VARIABLES //

// The following regular expression should suffice to polyfill (most?) all environments.
var RE = /^[\u0020\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+([\S\s]*?)[\u0020\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+$/;

// MAIN //

/**
* Trim whitespace characters from beginning and end of a string.
*
* @param {string} str - input string
* @throws {TypeError} must provide a string primitive
* @returns {string} trimmed string
*
* @example
* var out = trim( '   Whitespace   ' );
* // returns 'Whitespace'
*
* @example
* var out = trim( '\t\t\tTabs\t\t\t' );
* // returns 'Tabs'
*
* @example
* var out = trim( '\n\n\nNew Lines\n\n\n' ) );
* // returns 'New Lines'
*/
function trim( str ) {
	if ( !isString( str ) ) {
		throw new TypeError( 'invalid input argument. Must provide a string primitive. Value: `' + str + '`.' );
	}
	return replace( str, RE, '$1' );
} // end FUNCTION trim()

// EXPORTS //

module.exports = trim;
<|endoftext|>


<commit_before>/**
 * Created by benjamin on 22.03.2016.
 */
angular.module('imageShopAdm.size', [])

    .config(['$stateProvider', '$urlRouterProvider',  function ($stateProvider, $urlRouterProvider) {
        $stateProvider
            .state('sizes', {
                url: '/sizes',
                templateUrl: "views/adm/size/size.html",
                controller: 'SizeCtrl as ctrl',
                data: {
                    permissions: {
                        except: ['anonymous'],
                        redirectTo: 'auth'
                    }
                }
            })
    }])

    .controller('SizeCtrl', ['$state', '$http', '$rootScope', '$uibModal', '$location', function ($state, $http, $rootScope, $uibModal, $location) {
        var vm = this;

        this.getOrders = function () {
            $http.get('api/sizes').then(function (response) {
                if (response.data.error) {
                    alert("Error");
                } else {
                    vm.sizes = response.data.data;
                }
            }, function (response) {
                alert("Failure");
            });
        };

        var init = function () {
            vm.getOrders();
        };

        init();

        this.delete = function (id) {
            if (confirm('Delete Size ' + id + '?')) {
                $http.delete('api/sizes/' + id).then(function (response) {
                    vm.getOrders();
                }, function (response) {
                    console.log(response);
                });
            }
        };

        this.openCreateSizeModal = function () {
            var modalInstance = $uibModal.open({
                templateUrl: 'views/adm/size/createSizeDialog.html',
                controller: 'SizeDetailDialogController as ctrl'
            });

            modalInstance.size = {
                text: null,
                price: ''
            };

            modalInstance.result.then(function (size) {
                var config = {
                    headers: {
                        'Content-Type': 'application/json;'
                    }
                };

                $http.post('api/sizes', size, config).then(function (response) {
                    if (response.status == 201) {//Created
                        vm.getOrders();
                    }
                }, function (response) {
                    console.log(response);
                });
            });
        };

        this.openEditSizeModal = function (id) {

            $http.get('api/sizes/' + id).success(function (data) {
                var modalInstance = $uibModal.open({
                    templateUrl: 'views/adm/size/createSizeDialog.html',
                    controller: 'SizeDetailDialogController as ctrl'
                });
                data.data.price = parseFloat(data.data.price);
                modalInstance.size = data.data;

                modalInstance.result.then(function (size) {
                    var config = {
                        headers: {
                            'Content-Type': 'application/json;'
                        }
                    };

                    $http.put('api/sizes/' + id, size, config).then(function (response) {
                        if (response.status == 200) {//Updated
                            vm.getOrders();
                        }
                    }, function (response) {
                        console.log(response);
                    });
                });
            });
        };

    }]).controller('SizeDetailDialogController',
    ['$uibModalInstance', function ($uibModalInstance) {

        this.size = $uibModalInstance.size;

        this.ok = function () {
            $uibModalInstance.close(this.size);
        };

        this.cancel = function () {
            $uibModalInstance.dismiss();
        };
    }]).directive('sizeElement', function () {
        return {
            restrict: 'A',
            templateUrl: 'views/adm/size/listelement.html',
            scope: {
                size: '=size',
                controller: '=controller'
            }
        };
    });
<commit_msg>method rename<commit_after>/**
 * Created by benjamin on 22.03.2016.
 */
angular.module('imageShopAdm.size', [])

    .config(['$stateProvider', '$urlRouterProvider',  function ($stateProvider, $urlRouterProvider) {
        $stateProvider
            .state('sizes', {
                url: '/sizes',
                templateUrl: "views/adm/size/size.html",
                controller: 'SizeCtrl as ctrl',
                data: {
                    permissions: {
                        except: ['anonymous'],
                        redirectTo: 'auth'
                    }
                }
            })
    }])

    .controller('SizeCtrl', ['$state', '$http', '$rootScope', '$uibModal', '$location', function ($state, $http, $rootScope, $uibModal, $location) {
        var vm = this;

        this.getSize = function () {
            $http.get('api/sizes').then(function (response) {
                if (response.data.error) {
                    alert("Error");
                } else {
                    vm.sizes = response.data.data;
                }
            }, function (response) {
                alert("Failure");
            });
        };

        var init = function () {
            vm.getSize();
        };

        init();

        this.delete = function (id) {
            if (confirm('Delete Size ' + id + '?')) {
                $http.delete('api/sizes/' + id).then(function (response) {
                    vm.getSize();
                }, function (response) {
                    console.log(response);
                });
            }
        };

        this.openCreateSizeModal = function () {
            var modalInstance = $uibModal.open({
                templateUrl: 'views/adm/size/createSizeDialog.html',
                controller: 'SizeDetailDialogController as ctrl'
            });

            modalInstance.size = {
                text: null,
                price: ''
            };

            modalInstance.result.then(function (size) {
                var config = {
                    headers: {
                        'Content-Type': 'application/json;'
                    }
                };

                $http.post('api/sizes', size, config).then(function (response) {
                    if (response.status == 201) {//Created
                        vm.getSize();
                    }
                }, function (response) {
                    console.log(response);
                });
            });
        };

        this.openEditSizeModal = function (id) {

            $http.get('api/sizes/' + id).success(function (data) {
                var modalInstance = $uibModal.open({
                    templateUrl: 'views/adm/size/createSizeDialog.html',
                    controller: 'SizeDetailDialogController as ctrl'
                });
                data.data.price = parseFloat(data.data.price);
                modalInstance.size = data.data;

                modalInstance.result.then(function (size) {
                    var config = {
                        headers: {
                            'Content-Type': 'application/json;'
                        }
                    };

                    $http.put('api/sizes/' + id, size, config).then(function (response) {
                        if (response.status == 200) {//Updated
                            vm.getSizes();
                        }
                    }, function (response) {
                        console.log(response);
                    });
                });
            });
        };

    }]).controller('SizeDetailDialogController',
    ['$uibModalInstance', function ($uibModalInstance) {

        this.size = $uibModalInstance.size;

        this.ok = function () {
            $uibModalInstance.close(this.size);
        };

        this.cancel = function () {
            $uibModalInstance.dismiss();
        };
    }]).directive('sizeElement', function () {
        return {
            restrict: 'A',
            templateUrl: 'views/adm/size/listelement.html',
            scope: {
                size: '=size',
                controller: '=controller'
            }
        };
    });
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

// Provides control sap.m.SplitApp.
sap.ui.define(['jquery.sap.global', './SplitContainer', './library'],
	function(jQuery, SplitContainer, library) {
	"use strict";

	/**
	 * Constructor for a new SplitApp.
	 *
	 * @param {string} [sId] id for the new control, generated automatically if no id is given
	 * @param {object} [mSettings] initial settings for the new control
	 *
	 * @class
	 * SplitApp is another root element of a UI5 mobile application besides App control. It maintains two NavContainers if runs in tablet and one NavContainer in phone. The display of master NavContainer depends on the portrait/landscape of the device and the mode of SplitApp.
	 * @extends sap.m.SplitContainer
	 *
	 * @author SAP SE
	 * @version ${version}
	 *
	 * @constructor
	 * @public
	 * @alias sap.m.SplitApp
	 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
	 */
	var SplitApp = SplitContainer.extend("sap.m.SplitApp", /** @lends sap.m.SplitApp.prototype */ { metadata : {

		library : "sap.m",
		properties : {

			/**
			 * The icon to be displayed on the home screen of iOS devices after the user does "add to home screen".
			 *
			 * Note that only the first attempt to set the homeIcon will be executed, subsequent settings are ignored.
			 *
			 * This icon must be in PNG format. The property can either hold the URL of one single icon which is used for all devices (and possibly scaled, which looks not perfect), or an object holding icon URLs for the different required sizes.
			 *
			 * A desktop icon (used for bookmarks and overriding the favicon) can also be configured. This requires an object to be given and the "icon" property of this object then defines the desktop bookmark icon. For this icon, PNG is not supported by Internet Explorer. The ICO format is supported by all browsers. ICO is also preferred for this desktop icon setting because the file can contain different images for different resolutions.
			 *
			 * One example is:
			 *
			 * app.setHomeIcon({
			 * 'phone':'phone-icon.png',
			 * 'phone@2':'phone-retina.png',
			 * 'tablet':'tablet-icon.png',
			 * 'tablet@2':'tablet-retina.png',
			 * 'icon':'desktop.ico'
			 * });
			 *
			 * The respective image sizes are 57/114 px for the phone and 72/144 px for the tablet.
			 * If an object is given but one of the sizes is not given, the largest given icon will be used for this size.
			 *
			 * On Android these icons may or may not be used by the device. Apparently chances can be improved by adding glare effect and rounded corners, setting the file name so it ends with "-precomposed.png" and setting the "homeIconPrecomposed" property to "true".
			 */
			homeIcon : {type : "any", group : "Misc", defaultValue : null}
		},
		events : {

			/**
			 * This event will be fired when orientation (portrait/landscape) is changed.
			 */
			orientationChange : {
				parameters : {

					/**
					 * Returns true if the device is in landscape.
					 */
					landscape : {type : "boolean"}
				}
			}
		}
	}});

	//**************************************************************
	//* START - Life Cycle Methods
	//**************************************************************/
	SplitApp.prototype.init = function() {
		if (SplitContainer.prototype.init) {
			SplitContainer.prototype.init.apply(this, arguments);
		}
		this.addStyleClass("sapMSplitApp");
		jQuery.sap.initMobile({
			viewport: !this._debugZoomAndScroll,
			statusBar: "default",
			hideBrowser: true,
			preventScroll: !this._debugZoomAndScroll,
			rootId: this.getId()
		});
	};

	SplitApp.prototype.onBeforeRendering = function() {
		if (SplitContainer.prototype.onBeforeRendering) {
			SplitContainer.prototype.onBeforeRendering.apply(this, arguments);
		}
		jQuery.sap.initMobile({
			homeIcon: this.getHomeIcon()
		});
	};

	SplitApp.prototype.onAfterRendering = function(){
		if (SplitContainer.prototype.onAfterRendering) {
			SplitContainer.prototype.onAfterRendering.apply(this, arguments);
		}

		var ref = this.getDomRef().parentNode;
		// set all parent elements to 100% height this *should* be done by the application in CSS, but people tend to forget it...
		if (ref && !ref._sapui5_heightFixed) {
			ref._sapui5_heightFixed = true;
			while (ref && ref !== document.documentElement) {
				var $ref = jQuery(ref);
				if ($ref.attr("data-sap-ui-root-content")) { // Shell as parent does this already
					break;
				}
				if (!ref.style.height) {
					ref.style.height = "100%";
				}
				ref = ref.parentNode;
			}
		}
	};

	SplitApp.prototype.exit = function() {
		if (SplitContainer.prototype.exit) {
			SplitContainer.prototype.exit.apply(this, arguments);
		}
	};
	//**************************************************************
	//* END - Life Cycle Methods
	//**************************************************************/

	/**
	 * Fires the orientationChange event after SplitApp has reacted to the browser orientationchange event.
	 *
	 * @protected
	 */
	SplitApp.prototype._onOrientationChange = function(){
		this.fireOrientationChange({
			landscape: sap.ui.Device.orientation.landscape
		});
	};

	return SplitApp;

}, /* bExport= */ true);
<commit_msg>[INTERNAL] sap.m.SplitApp: Improve JSDOC<commit_after>/*!
 * ${copyright}
 */

// Provides control sap.m.SplitApp.
sap.ui.define(['jquery.sap.global', './SplitContainer', './library'],
	function(jQuery, SplitContainer, library) {
	"use strict";

	/**
	 * Constructor for a new SplitApp.
	 *
	 * @param {string} [sId] ID for the new control, generated automatically if no ID is given
	 * @param {object} [mSettings] Initial settings for the new control
	 *
	 * @class
	 * SplitApp is another root element of a UI5 mobile application besides App control. It maintains two NavContainers if running on tablet and one - on phone.
	 * The display of master NavContainer depends on the portrait/landscape mode of the device and the mode of SplitApp.
	 * @extends sap.m.SplitContainer
	 *
	 * @author SAP SE
	 * @version ${version}
	 *
	 * @constructor
	 * @public
	 * @alias sap.m.SplitApp
	 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
	 */
	var SplitApp = SplitContainer.extend("sap.m.SplitApp", /** @lends sap.m.SplitApp.prototype */ { metadata : {

		library : "sap.m",
		properties : {

			/**
			 * Represents the icon to be displayed on the home screen of iOS devices after the user does "add to home screen".
			 * Note that only the first attempt to set the homeIcon is executed, subsequent settings are ignored.
			 * The icon must be in PNG format. The property can either store the URL of one single icon or an object holding icon URLs for the different required sizes.
			 * Note that if single icon is used for all devices, when scaled, its quality can regress.
			 * A desktop icon (used for bookmarks and overriding the favicon) can also be configured. This requires an object to be given and the "icon" property of this object then defines the desktop bookmark icon.
			 * For this icon, PNG is not supported by Internet Explorer. The ICO format is supported by all browsers. ICO is also preferred for this desktop icon setting as the file can contain different images for different resolutions.
			 *
			 * One example is:
			 *
			 * app.setHomeIcon({
			 * 'phone':'phone-icon.png',
			 * 'phone@2':'phone-retina.png',
			 * 'tablet':'tablet-icon.png',
			 * 'tablet@2':'tablet-retina.png',
			 * 'icon':'desktop.ico'
			 * });
			 *
			 * The image size is 57/114 px for the phone and 72/144 px for the tablet.
			 * If an object is given but one of the sizes is not given, the largest given icon will be used for this size.
			 *
			 * On Android, these icons may or may not be used by the device. Chances can be improved by adding glare effect, rounded corners, setting the file name to end with "-precomposed.png", and setting the homeIconPrecomposed property to true.
			 */
			homeIcon : {type : "any", group : "Misc", defaultValue : null}
		},
		events : {

			/**
			 * Fires when orientation (portrait/landscape) is changed.
			 */
			orientationChange : {
				parameters : {

					/**
					 * Returns true if the device is in landscape mode.
					 */
					landscape : {type : "boolean"}
				}
			}
		}
	}});

	//**************************************************************
	//* START - Life Cycle Methods
	//**************************************************************/
	SplitApp.prototype.init = function() {
		if (SplitContainer.prototype.init) {
			SplitContainer.prototype.init.apply(this, arguments);
		}
		this.addStyleClass("sapMSplitApp");
		jQuery.sap.initMobile({
			viewport: !this._debugZoomAndScroll,
			statusBar: "default",
			hideBrowser: true,
			preventScroll: !this._debugZoomAndScroll,
			rootId: this.getId()
		});
	};

	SplitApp.prototype.onBeforeRendering = function() {
		if (SplitContainer.prototype.onBeforeRendering) {
			SplitContainer.prototype.onBeforeRendering.apply(this, arguments);
		}
		jQuery.sap.initMobile({
			homeIcon: this.getHomeIcon()
		});
	};

	SplitApp.prototype.onAfterRendering = function(){
		if (SplitContainer.prototype.onAfterRendering) {
			SplitContainer.prototype.onAfterRendering.apply(this, arguments);
		}

		var ref = this.getDomRef().parentNode;
		// set all parent elements to 100% height this *should* be done by the application in CSS, but people tend to forget it...
		if (ref && !ref._sapui5_heightFixed) {
			ref._sapui5_heightFixed = true;
			while (ref && ref !== document.documentElement) {
				var $ref = jQuery(ref);
				if ($ref.attr("data-sap-ui-root-content")) { // Shell as parent does this already
					break;
				}
				if (!ref.style.height) {
					ref.style.height = "100%";
				}
				ref = ref.parentNode;
			}
		}
	};

	//**************************************************************
	//* END - Life Cycle Methods
	//**************************************************************/

	/**
	 * Fires the orientationChange event after SplitApp has reacted to the browser orientationChange event.
	 *
	 * @protected
	 */
	SplitApp.prototype._onOrientationChange = function(){
		this.fireOrientationChange({
			landscape: sap.ui.Device.orientation.landscape
		});
	};

	return SplitApp;

}, /* bExport= */ true);
<|endoftext|>


<commit_before>import { copyToClipboard } from './copyToClipboard.js';
import { sendGoogleAnalyticsEvent } from './googleAnalytics.js';

$('#clip-created-modal-copy-btn').on('click', function () {
  clipCreatedCopyURL();
});

$('#clip-created-modal-add-to-playlist-btn').on('click', function () {
  toggleAddToPlaylistMenu();
});

$('#clip-created-modal-recommend-btn').on('click', function () {
  toggleRecommendMenu();
});

function clipCreatedCopyURL () {
  copyToClipboard(document.getElementById('clip-created-modal-link'));
  $('#clip-created-modal-copy-btn').html('Copied Link!');
  setTimeout(function () {
    $('#clip-created-modal').modal('hide');
    setTimeout(function () {
      $('#clip-created-modal-copy-btn').html('Copy Clip Link');
    }, 500);
  }, 800);

  sendGoogleAnalyticsEvent('Clip Created Modal', 'Copy URL');
}

function toggleAddToPlaylistMenu () {
  $('#clip-created-modal-recommend-btn').removeClass('selected');
  $('#clip-created-modal-add-to-playlist-btn').toggleClass('selected');
}

function toggleRecommendMenu () {
  $('#clip-created-modal-add-to-playlist-btn').removeClass('selected');
  $('#clip-created-modal-recommend-btn').toggleClass('selected');
}
<commit_msg>Do not hide clip created modal when copy button pressed<commit_after>import { copyToClipboard } from './copyToClipboard.js';
import { sendGoogleAnalyticsEvent } from './googleAnalytics.js';

$('#clip-created-modal-copy-btn').on('click', function () {
  clipCreatedCopyURL();
});

$('#clip-created-modal-add-to-playlist-btn').on('click', function () {
  toggleAddToPlaylistMenu();
});

$('#clip-created-modal-recommend-btn').on('click', function () {
  toggleRecommendMenu();
});

function clipCreatedCopyURL () {
  copyToClipboard(document.getElementById('clip-created-modal-link'));
  $('#clip-created-modal-copy-btn').html('Copied Link!');
  setTimeout(function () {
    $('#clip-created-modal-copy-btn').html('<i class="fa fa-copy"></i>  Copy');
    $('#clip-created-modal-copy-btn').blur();
  }, 2000);

  sendGoogleAnalyticsEvent('Clip Created Modal', 'Copy Link');
}

function toggleAddToPlaylistMenu () {
  $('#clip-created-modal-recommend-btn').removeClass('selected');
  $('#clip-created-modal-add-to-playlist-btn').toggleClass('selected');
}

function toggleRecommendMenu () {
  $('#clip-created-modal-add-to-playlist-btn').removeClass('selected');
  $('#clip-created-modal-recommend-btn').toggleClass('selected');
}
<|endoftext|>


<commit_before>/*
The MIT License (MIT)

Copyright (c) 2015 Los Andes University

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
(function (ng) {
    var mod = ng.module('mainApp', [
        //'ngCrudMock',
        'ngCrud',
        'ui.router',
        'clientModule',
        'itemModule',
        'tripModule',
        'productModule',
        'agencyModule',
        'categoryModule',
        'authModule',
        'roleModule',
        'commentaryModule',
        'questionModule'
    ]);

    mod.config(['$logProvider', function ($logProvider) {
            $logProvider.debugEnabled(true);
        }]);

    mod.config(['RestangularProvider', function (rp) {
            rp.setBaseUrl('api');
            rp.setRequestInterceptor(function (elem, operation) {
                if (operation === "remove") {
                    return null;
                }
                return elem;
            });
            rp.addResponseInterceptor(function (data, operation, what, url, response) {
                if (operation === "getList") {
                    data.totalRecords = parseInt(response.headers("X-Total-Count")) || 1;
                }
                return data;
            });
        }]);

    mod.config(['$urlRouterProvider', function ($urlRouterProvider) {
                $urlRouterProvider.otherwise('/');
        }]);

    mod.config(['authServiceProvider', function (auth) {
            auth.setValues({
                apiUrl: 'api/users/',
                successState: 'tripGallery'
            });
            auth.setRoles({
                'client': [{
                        id: 'client',
                        label: 'Client',
                        icon: 'list-alt',
                        state: 'clientList'
                    }],
                'agency': [{
                        id: 'agency',
                        label: 'Agency',
                        icon: 'list-alt',
                        state: 'agencyList'
                    }],
                'admin': [{
                        id: 'client',
                        label: 'Client',
                        icon: 'list-alt',
                        state: 'clientList'
                    }, {
                        id: 'category',
                        label: 'Category',
                        icon: 'list-alt',
                        state: 'categoryList'
                    }, {
                        id: 'agency',
                        label: 'Agency',
                        icon: 'list-alt',
                        state: 'agencyList'
                    }, {
                        id: 'product',
                        label: 'Product',
                        icon: 'list-alt',
                        state: 'productList'
                    }
                    , {
                        id: 'commentary',
                        label: 'Commentarys',
                        icon: 'list-alt',
                        state: 'commentaryList'
                    }
                   ]
            });
        }]);

    /*
     * When there's an error changing state, ui-router doesn't raise an error
     * This configuration allows to print said errors
     */
    mod.run(['$rootScope', '$log', function ($rootScope, $log) {
            $rootScope.$on('$stateChangeError', function (event, toState, toParams, fromState, fromParams, error) {
                $log.warn(error);
            });
        }]);
})(window.angular);
<commit_msg>Corrige merge master<commit_after>/*
The MIT License (MIT)

Copyright (c) 2015 Los Andes University

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
(function (ng) {
    var mod = ng.module('mainApp', [
        //'ngCrudMock',
        'ngCrud',
        'ui.router',
        'clientModule',
        'itemModule',
        'tripModule',
        'productModule',
        'agencyModule',
        'categoryModule',
        'authModule',
        'roleModule',
        'questionModule',
        'commentaryModule'
    ]);

    mod.config(['$logProvider', function ($logProvider) {
            $logProvider.debugEnabled(true);
        }]);

    mod.config(['RestangularProvider', function (rp) {
            rp.setBaseUrl('api');
            rp.setRequestInterceptor(function (elem, operation) {
                if (operation === "remove") {
                    return null;
                }
                return elem;
            });
            rp.addResponseInterceptor(function (data, operation, what, url, response) {
                if (operation === "getList") {
                    data.totalRecords = parseInt(response.headers("X-Total-Count")) || 1;
                }
                return data;
            });
        }]);

    mod.config(['$urlRouterProvider', function ($urlRouterProvider) {
                $urlRouterProvider.otherwise('/');
        }]);

    mod.config(['authServiceProvider', function (auth) {
            auth.setValues({
                apiUrl: 'api/users/',
                successState: 'tripGallery'
            });
            auth.setRoles({
                'client': [{
                        id: 'client',
                        label: 'Client',
                        icon: 'list-alt',
                        state: 'clientList'
                    }],
                'agency': [{
                        id: 'agency',
                        label: 'Agency',
                        icon: 'list-alt',
                        state: 'agencyList'
                    }],
                'admin': [{
                        id: 'client',
                        label: 'Client',
                        icon: 'list-alt',
                        state: 'clientList'
                    }, {
                        id: 'category',
                        label: 'Category',
                        icon: 'list-alt',
                        state: 'categoryList'
                    }, {
                        id: 'agency',
                        label: 'Agency',
                        icon: 'list-alt',
                        state: 'agencyList'
                    }, {
                        id: 'product',
                        label: 'Product',
                        icon: 'list-alt',
                        state: 'productList'
                    }
                    , {
                        id: 'commentary',
                        label: 'Commentarys',
                        icon: 'list-alt',
                        state: 'commentaryList'
                    }
                   ]
            });
        }]);

    /*
     * When there's an error changing state, ui-router doesn't raise an error
     * This configuration allows to print said errors
     */
    mod.run(['$rootScope', '$log', function ($rootScope, $log) {
            $rootScope.$on('$stateChangeError', function (event, toState, toParams, fromState, fromParams, error) {
                $log.warn(error);
            });
        }]);
})(window.angular);
<|endoftext|>


<commit_before>var nest = require('depnest')
var {Value, Proxy, Array: MutantArray, h, computed, map, when, onceTrue, throttle} = require('mutant')
var pull = require('pull-stream')
var Abortable = require('pull-abortable')
var Scroller = require('../../../lib/scroller')
var nextStepper = require('../../../lib/next-stepper')
var extend = require('xtend')
var paramap = require('pull-paramap')

var bumpMessages = {
  'vote': 'liked this message',
  'post': 'replied to this message',
  'about': 'added changes',
  'mention': 'mentioned you',
  'channel-mention': 'mentioned this channel'
}

// bump even for first message
var rootBumpTypes = ['mention', 'channel-mention']

exports.needs = nest({
  'about.obs.name': 'first',
  'app.sync.externalHandler': 'first',
  'message.html.render': 'first',
  'profile.html.person': 'first',
  'message.html.link': 'first',
  'message.sync.root': 'first',
  'feed.pull.rollup': 'first',
  'sbot.async.get': 'first',
  'keys.sync.id': 'first',
  'settings.obs.get': 'first',
})

exports.gives = nest({
  'feed.html.rollup': true
})

exports.create = function (api) {
  return nest('feed.html.rollup', function (getStream, {
    prepend,
    rootFilter = returnTrue,
    bumpFilter = returnTrue,
    displayFilter = returnTrue,
    updateStream, // override the stream used for realtime updates
    waitFor = true
  }) {
    var updates = Value(0)
    var yourId = api.keys.sync.id()
    var throttledUpdates = throttle(updates, 200)
    var updateLoader = h('a Notifier -loader', { href: '#', 'ev-click': refresh }, [
      'Show ', h('strong', [throttledUpdates]), ' ', plural(throttledUpdates, 'update', 'updates')
    ])

    var abortLastFeed = null
    var content = Value()
    var loading = Proxy(true)
    var newSinceRefresh = new Set()
    var highlightItems = new Set()

    var container = h('Scroller', {
      style: { overflow: 'auto' },
      hooks: [(element) => {
        // don't activate until added to DOM
        refresh()

        // deactivate when removed from DOM
        return () => {
          if (abortLastFeed) {
            abortLastFeed()
            abortLastFeed = null
          }
        }
      }]
    }, [
      h('div.wrapper', [
        h('section.prepend', prepend),
        content,
        when(loading, h('Loading -large'))
      ])
    ])

    const filters = api.settings.obs.get('filters')
    rootFilter = getFilter(filters())
    filters((filterSettings) => {
      rootFilter = getFilter(filterSettings)
      refresh()
    })

    onceTrue(waitFor, () => {
      // display pending updates
      pull(
        updateStream || pull(
          getStream({old: false}),
          LookupRoot()
        ),
        pull.filter((msg) => {
          // only render posts that have a root message
          var root = msg.root || msg
          return root && root.value && root.value.content && rootFilter(root) && bumpFilter(msg) && displayFilter(msg)
        }),
        pull.drain((msg) => {
          if (msg.value.content.type === 'vote') return
          if (api.app.sync.externalHandler(msg)) return
          newSinceRefresh.add(msg.key)

          if (updates() === 0 && msg.value.author === yourId && container.scrollTop < 20) {
            refresh()
          } else {
            updates.set(newSinceRefresh.size)
          }
        })
      )
    })

    var result = MutantArray([
      when(updates, updateLoader),
      container
    ])

    result.pendingUpdates = throttledUpdates
    result.reload = refresh

    return result

    function refresh () {
      onceTrue(waitFor, () => {
        if (abortLastFeed) abortLastFeed()
        updates.set(0)
        content.set(h('section.content'))

        var abortable = Abortable()
        abortLastFeed = abortable.abort

        highlightItems = newSinceRefresh
        newSinceRefresh = new Set()

        var done = Value(false)
        var stream = nextStepper(getStream, {reverse: true, limit: 50})
        var scroller = Scroller(container, content(), renderItem, () => done.set(true))

        // track loading state
        loading.set(computed([done, scroller.queue], (done, queue) => {
          return !done && queue < 5
        }))

        pull(
          stream,
          pull.filter(bumpFilter),
          abortable,
          api.feed.pull.rollup(rootFilter),
          scroller
        )
      })
    }

    function renderItem (item, opts) {
      var partial = opts && opts.partial
      var meta = null
      var previousId = item.key

      var groupedBumps = {}
      var lastBumpType = null

      var rootBumpType = bumpFilter(item)
      if (rootBumpTypes.includes(rootBumpType)) {
        lastBumpType = rootBumpType
        groupedBumps[lastBumpType] = [item]
      }

      item.replies.forEach(msg => {
        var value = bumpFilter(msg)
        if (value) {
          var type = typeof value === 'string' ? value : getType(msg)
          ;(groupedBumps[type] = groupedBumps[type] || []).unshift(msg)
          lastBumpType = type
        }
      })

      var replies = item.replies.filter(isReply)
      var replyElements = replies.filter(displayFilter).sort(byAssertedTime).slice(-3).map((msg) => {
        var result = api.message.html.render(msg, {
          inContext: true,
          inSummary: true,
          previousId,
          priority: highlightItems.has(msg.key) ? 2 : 0
        })
        previousId = msg.key
        return result
      })

      var renderedMessage = api.message.html.render(item, {
        inContext: true,
        priority: highlightItems.has(item.key) ? 2 : 0
      })

      if (!renderedMessage) return h('div')
      if (lastBumpType) {
        var bumps = lastBumpType === 'vote'
          ? getLikeAuthors(groupedBumps[lastBumpType])
          : getAuthors(groupedBumps[lastBumpType])

        var description = bumpMessages[lastBumpType] || 'added changes'
        meta = h('div.meta', { title: names(bumps) }, [
          many(bumps, api.profile.html.person), ' ', description
        ])
      }

      return h('FeedEvent -post', {
        attributes: {
          'data-root-id': item.key
        }
      }, [
        meta,
        renderedMessage,
        when(replyElements.length, [
          when(replies.length > replyElements.length || partial,
            h('a.full', {href: item.key}, ['View full thread (', replies.length, ')'])
          ),
          h('div.replies', replyElements)
        ])
      ])
    }
  })

  function names (ids) {
    var items = map(Array.from(ids), api.about.obs.name)
    return computed([items], (names) => names.map((n) => `- ${n}`).join('\n'))
  }

  function LookupRoot () {
    return paramap((msg, cb) => {
      var rootId = api.message.sync.root(msg)
      if (rootId) {
        api.sbot.async.get(rootId, (_, value) => {
          cb(null, extend(msg, {
            root: {key: rootId, value}
          }))
        })
      } else {
        cb(null, msg)
      }
    })
  }
}

function plural (value, single, many) {
  return computed(value, (value) => {
    if (value === 1) {
      return single
    } else {
      return many
    }
  })
}

function many (ids, fn) {
  ids = Array.from(ids)
  var featuredIds = ids.slice(0, 4)

  if (ids.length) {
    if (ids.length > 4) {
      return [
        fn(featuredIds[0]), ', ',
        fn(featuredIds[1]), ', ',
        fn(featuredIds[2]), ' and ',
        ids.length - 3, ' others'
      ]
    } else if (ids.length === 4) {
      return [
        fn(featuredIds[0]), ', ',
        fn(featuredIds[1]), ', ',
        fn(featuredIds[2]), ' and ',
        fn(featuredIds[3])
      ]
    } else if (ids.length === 3) {
      return [
        fn(featuredIds[0]), ', ',
        fn(featuredIds[1]), ' and ',
        fn(featuredIds[2])
      ]
    } else if (ids.length === 2) {
      return [
        fn(featuredIds[0]), ' and ',
        fn(featuredIds[1])
      ]
    } else {
      return fn(featuredIds[0])
    }
  }
}

function getAuthors (items) {
  return items.reduce((result, msg) => {
    result.add(msg.value.author)
    return result
  }, new Set())
}

function getLikeAuthors (items) {
  return items.reduce((result, msg) => {
    if (msg.value.content.type === 'vote') {
      if (msg.value.content && msg.value.content.vote && msg.value.content.vote.value === 1) {
        result.add(msg.value.author)
      } else {
        result.delete(msg.value.author)
      }
    }
    return result
  }, new Set())
}

function isReply (msg) {
  if (msg.value && msg.value.content) {
    var type = msg.value.content.type
    return type === 'post' || (type === 'about' && msg.value.content.attendee)
  }
}

function getType (msg) {
  return msg && msg.value && msg.value.content && msg.value.content.type
}

function returnTrue () {
  return true
}

function getFilter (filterSettings) {
  return function(msg) {
    return !(filterSettings.following && getType(msg) === 'contact')
  }
}

function byAssertedTime (a, b) {
  return a.value.timestamp - b.value.timestamp
}
<commit_msg>Handle filter settings initial state gracefully<commit_after>var nest = require('depnest')
var {Value, Proxy, Array: MutantArray, h, computed, map, when, onceTrue, throttle} = require('mutant')
var pull = require('pull-stream')
var Abortable = require('pull-abortable')
var Scroller = require('../../../lib/scroller')
var nextStepper = require('../../../lib/next-stepper')
var extend = require('xtend')
var paramap = require('pull-paramap')

var bumpMessages = {
  'vote': 'liked this message',
  'post': 'replied to this message',
  'about': 'added changes',
  'mention': 'mentioned you',
  'channel-mention': 'mentioned this channel'
}

// bump even for first message
var rootBumpTypes = ['mention', 'channel-mention']

exports.needs = nest({
  'about.obs.name': 'first',
  'app.sync.externalHandler': 'first',
  'message.html.render': 'first',
  'profile.html.person': 'first',
  'message.html.link': 'first',
  'message.sync.root': 'first',
  'feed.pull.rollup': 'first',
  'sbot.async.get': 'first',
  'keys.sync.id': 'first',
  'settings.obs.get': 'first',
})

exports.gives = nest({
  'feed.html.rollup': true
})

exports.create = function (api) {
  return nest('feed.html.rollup', function (getStream, {
    prepend,
    rootFilter = returnTrue,
    bumpFilter = returnTrue,
    displayFilter = returnTrue,
    updateStream, // override the stream used for realtime updates
    waitFor = true
  }) {
    var updates = Value(0)
    var yourId = api.keys.sync.id()
    var throttledUpdates = throttle(updates, 200)
    var updateLoader = h('a Notifier -loader', { href: '#', 'ev-click': refresh }, [
      'Show ', h('strong', [throttledUpdates]), ' ', plural(throttledUpdates, 'update', 'updates')
    ])

    var abortLastFeed = null
    var content = Value()
    var loading = Proxy(true)
    var newSinceRefresh = new Set()
    var highlightItems = new Set()

    var container = h('Scroller', {
      style: { overflow: 'auto' },
      hooks: [(element) => {
        // don't activate until added to DOM
        refresh()

        // deactivate when removed from DOM
        return () => {
          if (abortLastFeed) {
            abortLastFeed()
            abortLastFeed = null
          }
        }
      }]
    }, [
      h('div.wrapper', [
        h('section.prepend', prepend),
        content,
        when(loading, h('Loading -large'))
      ])
    ])

    const filters = api.settings.obs.get('filters')
    rootFilter = getFilter(filters())
    filters((filterSettings) => {
      rootFilter = getFilter(filterSettings)
      refresh()
    })

    onceTrue(waitFor, () => {
      // display pending updates
      pull(
        updateStream || pull(
          getStream({old: false}),
          LookupRoot()
        ),
        pull.filter((msg) => {
          // only render posts that have a root message
          var root = msg.root || msg
          return root && root.value && root.value.content && rootFilter(root) && bumpFilter(msg) && displayFilter(msg)
        }),
        pull.drain((msg) => {
          if (msg.value.content.type === 'vote') return
          if (api.app.sync.externalHandler(msg)) return
          newSinceRefresh.add(msg.key)

          if (updates() === 0 && msg.value.author === yourId && container.scrollTop < 20) {
            refresh()
          } else {
            updates.set(newSinceRefresh.size)
          }
        })
      )
    })

    var result = MutantArray([
      when(updates, updateLoader),
      container
    ])

    result.pendingUpdates = throttledUpdates
    result.reload = refresh

    return result

    function refresh () {
      onceTrue(waitFor, () => {
        if (abortLastFeed) abortLastFeed()
        updates.set(0)
        content.set(h('section.content'))

        var abortable = Abortable()
        abortLastFeed = abortable.abort

        highlightItems = newSinceRefresh
        newSinceRefresh = new Set()

        var done = Value(false)
        var stream = nextStepper(getStream, {reverse: true, limit: 50})
        var scroller = Scroller(container, content(), renderItem, () => done.set(true))

        // track loading state
        loading.set(computed([done, scroller.queue], (done, queue) => {
          return !done && queue < 5
        }))

        pull(
          stream,
          pull.filter(bumpFilter),
          abortable,
          api.feed.pull.rollup(rootFilter),
          scroller
        )
      })
    }

    function renderItem (item, opts) {
      var partial = opts && opts.partial
      var meta = null
      var previousId = item.key

      var groupedBumps = {}
      var lastBumpType = null

      var rootBumpType = bumpFilter(item)
      if (rootBumpTypes.includes(rootBumpType)) {
        lastBumpType = rootBumpType
        groupedBumps[lastBumpType] = [item]
      }

      item.replies.forEach(msg => {
        var value = bumpFilter(msg)
        if (value) {
          var type = typeof value === 'string' ? value : getType(msg)
          ;(groupedBumps[type] = groupedBumps[type] || []).unshift(msg)
          lastBumpType = type
        }
      })

      var replies = item.replies.filter(isReply)
      var replyElements = replies.filter(displayFilter).sort(byAssertedTime).slice(-3).map((msg) => {
        var result = api.message.html.render(msg, {
          inContext: true,
          inSummary: true,
          previousId,
          priority: highlightItems.has(msg.key) ? 2 : 0
        })
        previousId = msg.key
        return result
      })

      var renderedMessage = api.message.html.render(item, {
        inContext: true,
        priority: highlightItems.has(item.key) ? 2 : 0
      })

      if (!renderedMessage) return h('div')
      if (lastBumpType) {
        var bumps = lastBumpType === 'vote'
          ? getLikeAuthors(groupedBumps[lastBumpType])
          : getAuthors(groupedBumps[lastBumpType])

        var description = bumpMessages[lastBumpType] || 'added changes'
        meta = h('div.meta', { title: names(bumps) }, [
          many(bumps, api.profile.html.person), ' ', description
        ])
      }

      return h('FeedEvent -post', {
        attributes: {
          'data-root-id': item.key
        }
      }, [
        meta,
        renderedMessage,
        when(replyElements.length, [
          when(replies.length > replyElements.length || partial,
            h('a.full', {href: item.key}, ['View full thread (', replies.length, ')'])
          ),
          h('div.replies', replyElements)
        ])
      ])
    }
  })

  function names (ids) {
    var items = map(Array.from(ids), api.about.obs.name)
    return computed([items], (names) => names.map((n) => `- ${n}`).join('\n'))
  }

  function LookupRoot () {
    return paramap((msg, cb) => {
      var rootId = api.message.sync.root(msg)
      if (rootId) {
        api.sbot.async.get(rootId, (_, value) => {
          cb(null, extend(msg, {
            root: {key: rootId, value}
          }))
        })
      } else {
        cb(null, msg)
      }
    })
  }
}

function plural (value, single, many) {
  return computed(value, (value) => {
    if (value === 1) {
      return single
    } else {
      return many
    }
  })
}

function many (ids, fn) {
  ids = Array.from(ids)
  var featuredIds = ids.slice(0, 4)

  if (ids.length) {
    if (ids.length > 4) {
      return [
        fn(featuredIds[0]), ', ',
        fn(featuredIds[1]), ', ',
        fn(featuredIds[2]), ' and ',
        ids.length - 3, ' others'
      ]
    } else if (ids.length === 4) {
      return [
        fn(featuredIds[0]), ', ',
        fn(featuredIds[1]), ', ',
        fn(featuredIds[2]), ' and ',
        fn(featuredIds[3])
      ]
    } else if (ids.length === 3) {
      return [
        fn(featuredIds[0]), ', ',
        fn(featuredIds[1]), ' and ',
        fn(featuredIds[2])
      ]
    } else if (ids.length === 2) {
      return [
        fn(featuredIds[0]), ' and ',
        fn(featuredIds[1])
      ]
    } else {
      return fn(featuredIds[0])
    }
  }
}

function getAuthors (items) {
  return items.reduce((result, msg) => {
    result.add(msg.value.author)
    return result
  }, new Set())
}

function getLikeAuthors (items) {
  return items.reduce((result, msg) => {
    if (msg.value.content.type === 'vote') {
      if (msg.value.content && msg.value.content.vote && msg.value.content.vote.value === 1) {
        result.add(msg.value.author)
      } else {
        result.delete(msg.value.author)
      }
    }
    return result
  }, new Set())
}

function isReply (msg) {
  if (msg.value && msg.value.content) {
    var type = msg.value.content.type
    return type === 'post' || (type === 'about' && msg.value.content.attendee)
  }
}

function getType (msg) {
  return msg && msg.value && msg.value.content && msg.value.content.type
}

function returnTrue () {
  return true
}

function getFilter (filterSettings) {
  if(!filterSettings) return returnTrue

  return function(msg) {
    return !(filterSettings.following && getType(msg) === 'contact')
  }
}

function byAssertedTime (a, b) {
  return a.value.timestamp - b.value.timestamp
}
<|endoftext|>