<commit_before>/*
Copyright 2017 MTRNord and Cooperative EITA
Copyright 2017 Vector Creations Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import request from 'browser-request';
import counterpart from 'counterpart';
import Promise from 'bluebird';
import React from 'react';
import SettingsStore, {SettingLevel} from "./settings/SettingsStore";

const i18nFolder = 'i18n/';

// Control whether to also return original, untranslated strings
// Useful for debugging and testing
const ANNOTATE_STRINGS = false;

// We use english strings as keys, some of which contain full stops
counterpart.setSeparator('|');
// Fall back to English
counterpart.setFallbackLocale('en');

// Function which only purpose is to mark that a string is translatable
// Does not actually do anything. It's helpful for automatic extraction of translatable strings
export function _td(s) {
    return s;
}

// Wrapper for counterpart's translation function so that it handles nulls and undefineds properly
// Takes the same arguments as counterpart.translate()
function safeCounterpartTranslate(text, options) {
    // Horrible hack to avoid https://github.com/vector-im/riot-web/issues/4191
    // The interpolation library that counterpart uses does not support undefined/null
    // values and instead will throw an error. This is a problem since everywhere else
    // in JS land passing undefined/null will simply stringify instead, and when converting
    // valid ES6 template strings to i18n strings it's extremely easy to pass undefined/null
    // if there are no existing null guards. To avoid this making the app completely inoperable,
    // we'll check all the values for undefined/null and stringify them here.
    let count;

    if (options && typeof options === 'object') {
        count = options['count'];
        Object.keys(options).forEach((k) => {
            if (options[k] === undefined) {
                console.warn("safeCounterpartTranslate called with undefined interpolation name: " + k);
                options[k] = 'undefined';
            }
            if (options[k] === null) {
                console.warn("safeCounterpartTranslate called with null interpolation name: " + k);
                options[k] = 'null';
            }
        });
    }
    let translated = counterpart.translate(text, options);
    if (translated === undefined && count !== undefined) {
        // counterpart does not do fallback if no pluralisation exists
        // in the preferred language, so do it here
        translated = counterpart.translate(text, Object.assign({}, options, {locale: 'en'}));
    }
    return translated;
}

/*
 * Translates text and optionally also replaces XML-ish elements in the text with e.g. React components
 * @param {string} text The untranslated text, e.g "click here now to %(foo)s".
 * @param {object} variables Variable substitutions, e.g { foo: 'bar' }
 * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} }
 *
 * In both variables and tags, the values to substitute with can be either simple strings, React components,
 * or functions that return the value to use in the substitution (e.g. return a React component). In case of
 * a tag replacement, the function receives as the argument the text inside the element corresponding to the tag.
 *
 * Use tag substitutions if you need to translate text between tags (e.g. "Click here!"), otherwise
 * you will end up with literal "" in your output, rather than HTML. Note that you can also use variable
 * substitution to insert React components, but you can't use it to translate text between tags.
 *
 * @return a React  component if any non-strings were used in substitutions, otherwise a string
 */
export function _t(text, variables, tags) {
    // Don't do subsitutions in counterpart. We handle it ourselves so we can replace with React components
    // However, still pass the variables to counterpart so that it can choose the correct plural if count is given
    // It is enough to pass the count variable, but in the future counterpart might make use of other information too
    const args = Object.assign({ interpolate: false }, variables);

    // The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
    const translated = safeCounterpartTranslate(text, args);

    const substituted = substitute(translated, variables, tags);

    // For development/testing purposes it is useful to also output the original string
    // Don't do that for release versions
    if (ANNOTATE_STRINGS) {
        if (typeof substituted === 'string') {
            return `@@${text}##${substituted}@@`;
        } else {
            return {substituted};
        }
    } else {
        return substituted;
    }
}

/*
 * Similar to _t(), except only does substitutions, and no translation
 * @param {string} text The text, e.g "click here now to %(foo)s".
 * @param {object} variables Variable substitutions, e.g { foo: 'bar' }
 * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} }
 *
 * The values to substitute with can be either simple strings, or functions that return the value to use in
 * the substitution (e.g. return a React component). In case of a tag replacement, the function receives as
 * the argument the text inside the element corresponding to the tag.
 *
 * @return a React  component if any non-strings were used in substitutions, otherwise a string
 */
export function substitute(text, variables, tags) {
    const regexpMapping = {};

    if (variables !== undefined) {
        for (const variable in variables) {
            regexpMapping[`%\\(${variable}\\)s`] = variables[variable];
        }
    }

    if (tags !== undefined) {
        for (const tag in tags) {
            regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag];
        }
    }
    return replaceByRegexes(text, regexpMapping);
}

/*
 * Replace parts of a text using regular expressions
 * @param {string} text The text on which to perform substitutions
 * @param {object} mapping A mapping from regular expressions in string form to replacement string or a
 * function which will receive as the argument the capture groups defined in the regexp. E.g.
 * { 'Hello (.?) World': (sub) => sub.toUpperCase() }
 *
 * @return a React  component if any non-strings were used in substitutions, otherwise a string
 */
export function replaceByRegexes(text, mapping) {
    // We initially store our output as an array of strings and objects (e.g. React components).
    // This will then be converted to a string or a  at the end
    const output = [text];

    // If we insert any components we need to wrap the output in a span. React doesn't like just an array of components.
    let shouldWrapInSpan = false;

    for (const regexpString in mapping) {
        // TODO: Cache regexps
        const regexp = new RegExp(regexpString);

        // Loop over what output we have so far and perform replacements
        // We look for matches: if we find one, we get three parts: everything before the match, the replaced part,
        // and everything after the match. Insert all three into the output. We need to do this because we can insert objects.
        // Otherwise there would be no need for the splitting and we could do simple replcement.
        let matchFoundSomewhere = false; // If we don't find a match anywhere we want to log it
        for (const outputIndex in output) {
            const inputText = output[outputIndex];
            if (typeof inputText !== 'string') { // We might have inserted objects earlier, don't try to replace them
                continue;
            }

            const match = inputText.match(regexp);
            if (!match) {
                continue;
            }
            matchFoundSomewhere = true;

            const capturedGroups = match.slice(2);

            // The textual part before the match
            const head = inputText.substr(0, match.index);

            // The textual part after the match
            const tail = inputText.substr(match.index + match[0].length);

            let replaced;
            // If substitution is a function, call it
            if (mapping[regexpString] instanceof Function) {
                replaced = mapping[regexpString].apply(null, capturedGroups);
            } else {
                replaced = mapping[regexpString];
            }

            if (typeof replaced === 'object') {
                shouldWrapInSpan = true;
            }

            output.splice(outputIndex, 1); // Remove old element

            // Insert in reverse order as splice does insert-before and this way we get the final order correct
            if (tail !== '') {
                output.splice(outputIndex, 0, tail);
            }

            // Here we also need to check that it actually is a string before comparing against one
            // The head and tail are always strings
            if (typeof replaced !== 'string' || replaced !== '') {
                output.splice(outputIndex, 0, replaced);
            }

            if (head !== '') { // Don't push empty nodes, they are of no use
                output.splice(outputIndex, 0, head);
            }
        }
        if (!matchFoundSomewhere) { // The current regexp did not match anything in the input
            // Missing matches is entirely possible because you might choose to show some variables only in the case
            // of e.g. plurals. It's still a bit suspicious, and could be due to an error, so log it.
            // However, not showing count is so common that it's not worth logging. And other commonly unused variables
            // here, if there are any.
            if (regexpString !== '%\\(count\\)s') {
                console.log(`Could not find ${regexp} in ${text}`);
            }
        }
    }

    if (shouldWrapInSpan) {
        return React.createElement('span', null, ...output);
    } else {
        return output.join('');
    }
}

// Allow overriding the text displayed when no translation exists
// Currently only used in unit tests to avoid having to load
// the translations in riot-web
export function setMissingEntryGenerator(f) {
    counterpart.setMissingEntryGenerator(f);
}

export function setLanguage(preferredLangs) {
    if (!Array.isArray(preferredLangs)) {
        preferredLangs = [preferredLangs];
    }

    let langToUse;
    let availLangs;
    return getLangsJson().then((result) => {
        availLangs = result;

        for (let i = 0; i < preferredLangs.length; ++i) {
            if (availLangs.hasOwnProperty(preferredLangs[i])) {
                langToUse = preferredLangs[i];
                break;
            }
        }
        if (!langToUse) {
            // Fallback to en_EN if none is found
            langToUse = 'en';
            console.error("Unable to find an appropriate language");
        }

        return getLanguage(i18nFolder + availLangs[langToUse].fileName);
    }).then((langData) => {
        counterpart.registerTranslations(langToUse, langData);
        counterpart.setLocale(langToUse);
        SettingsStore.setValue("language", null, SettingLevel.DEVICE, langToUse);
        console.log("set language to " + langToUse);

        // Set 'en' as fallback language:
        if (langToUse != "en") {
            return getLanguage(i18nFolder + availLangs['en'].fileName);
        }
    }).then((langData) => {
        if (langData) counterpart.registerTranslations('en', langData);
    });
}

export function getAllLanguagesFromJson() {
    return getLangsJson().then((langsObject) => {
        const langs = [];
        for (const langKey in langsObject) {
            if (langsObject.hasOwnProperty(langKey)) {
                langs.push({
                    'value': langKey,
                    'label': langsObject[langKey].label,
                });
            }
        }
        return langs;
    });
}

export function getLanguagesFromBrowser() {
    if (navigator.languages && navigator.languages.length) return navigator.languages;
    if (navigator.language) return [navigator.language];
    return [navigator.userLanguage || "en"];
}

/**
 * Turns a language string, normalises it,
 * (see normalizeLanguageKey) into an array of language strings
 * with fallback to generic languages
 * (eg. 'pt-BR' => ['pt-br', 'pt'])
 *
 * @param {string} language The input language string
 * @return {string[]} List of normalised languages
 */
export function getNormalizedLanguageKeys(language) {
    const languageKeys = [];
    const normalizedLanguage = this.normalizeLanguageKey(language);
    const languageParts = normalizedLanguage.split('-');
    if (languageParts.length == 2 && languageParts[0] == languageParts[1]) {
        languageKeys.push(languageParts[0]);
    } else {
        languageKeys.push(normalizedLanguage);
        if (languageParts.length == 2) {
            languageKeys.push(languageParts[0]);
        }
    }
    return languageKeys;
}

/**
 * Returns a language string with underscores replaced with
 * hyphens, and lowercased.
 */
export function normalizeLanguageKey(language) {
    return language.toLowerCase().replace("_", "-");
}

export function getCurrentLanguage() {
    return counterpart.getLocale();
}

function getLangsJson() {
    return new Promise((resolve, reject) => {
        const url = require("../../riot-web/webapp/i18n/languages.json");
        request(
            { method: "GET", url },
            (err, response, body) => {
                if (err || response.status < 200 || response.status >= 300) {
                    reject({err: err, response: response});
                    return;
                }
                resolve(JSON.parse(body));
            },
        );
    });
}

function weblateToCounterpart(inTrs) {
    const outTrs = {};

    for (const key of Object.keys(inTrs)) {
        const keyParts = key.split('|', 2);
        if (keyParts.length === 2) {
            let obj = outTrs[keyParts[0]];
            if (obj === undefined) {
                obj = {};
                outTrs[keyParts[0]] = obj;
            }
            obj[keyParts[1]] = inTrs[key];
        } else {
            outTrs[key] = inTrs[key];
        }
    }

    return outTrs;
}

function getLanguage(langPath) {
    return new Promise((resolve, reject) => {
        request(
            { method: "GET", url: langPath },
            (err, response, body) => {
                if (err || response.status < 200 || response.status >= 300) {
                    reject({err: err, response: response});
                    return;
                }
                resolve(weblateToCounterpart(JSON.parse(body)));
            },
        );
    });
}
<commit_msg>move this path to  webpack config file<commit_after>/*
Copyright 2017 MTRNord and Cooperative EITA
Copyright 2017 Vector Creations Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import request from 'browser-request';
import counterpart from 'counterpart';
import Promise from 'bluebird';
import React from 'react';
import SettingsStore, {SettingLevel} from "./settings/SettingsStore";

const i18nFolder = 'i18n/';

// Control whether to also return original, untranslated strings
// Useful for debugging and testing
const ANNOTATE_STRINGS = false;

// We use english strings as keys, some of which contain full stops
counterpart.setSeparator('|');
// Fall back to English
counterpart.setFallbackLocale('en');

// Function which only purpose is to mark that a string is translatable
// Does not actually do anything. It's helpful for automatic extraction of translatable strings
export function _td(s) {
    return s;
}

// Wrapper for counterpart's translation function so that it handles nulls and undefineds properly
// Takes the same arguments as counterpart.translate()
function safeCounterpartTranslate(text, options) {
    // Horrible hack to avoid https://github.com/vector-im/riot-web/issues/4191
    // The interpolation library that counterpart uses does not support undefined/null
    // values and instead will throw an error. This is a problem since everywhere else
    // in JS land passing undefined/null will simply stringify instead, and when converting
    // valid ES6 template strings to i18n strings it's extremely easy to pass undefined/null
    // if there are no existing null guards. To avoid this making the app completely inoperable,
    // we'll check all the values for undefined/null and stringify them here.
    let count;

    if (options && typeof options === 'object') {
        count = options['count'];
        Object.keys(options).forEach((k) => {
            if (options[k] === undefined) {
                console.warn("safeCounterpartTranslate called with undefined interpolation name: " + k);
                options[k] = 'undefined';
            }
            if (options[k] === null) {
                console.warn("safeCounterpartTranslate called with null interpolation name: " + k);
                options[k] = 'null';
            }
        });
    }
    let translated = counterpart.translate(text, options);
    if (translated === undefined && count !== undefined) {
        // counterpart does not do fallback if no pluralisation exists
        // in the preferred language, so do it here
        translated = counterpart.translate(text, Object.assign({}, options, {locale: 'en'}));
    }
    return translated;
}

/*
 * Translates text and optionally also replaces XML-ish elements in the text with e.g. React components
 * @param {string} text The untranslated text, e.g "click here now to %(foo)s".
 * @param {object} variables Variable substitutions, e.g { foo: 'bar' }
 * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} }
 *
 * In both variables and tags, the values to substitute with can be either simple strings, React components,
 * or functions that return the value to use in the substitution (e.g. return a React component). In case of
 * a tag replacement, the function receives as the argument the text inside the element corresponding to the tag.
 *
 * Use tag substitutions if you need to translate text between tags (e.g. "Click here!"), otherwise
 * you will end up with literal "" in your output, rather than HTML. Note that you can also use variable
 * substitution to insert React components, but you can't use it to translate text between tags.
 *
 * @return a React  component if any non-strings were used in substitutions, otherwise a string
 */
export function _t(text, variables, tags) {
    // Don't do subsitutions in counterpart. We handle it ourselves so we can replace with React components
    // However, still pass the variables to counterpart so that it can choose the correct plural if count is given
    // It is enough to pass the count variable, but in the future counterpart might make use of other information too
    const args = Object.assign({ interpolate: false }, variables);

    // The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
    const translated = safeCounterpartTranslate(text, args);

    const substituted = substitute(translated, variables, tags);

    // For development/testing purposes it is useful to also output the original string
    // Don't do that for release versions
    if (ANNOTATE_STRINGS) {
        if (typeof substituted === 'string') {
            return `@@${text}##${substituted}@@`;
        } else {
            return {substituted};
        }
    } else {
        return substituted;
    }
}

/*
 * Similar to _t(), except only does substitutions, and no translation
 * @param {string} text The text, e.g "click here now to %(foo)s".
 * @param {object} variables Variable substitutions, e.g { foo: 'bar' }
 * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} }
 *
 * The values to substitute with can be either simple strings, or functions that return the value to use in
 * the substitution (e.g. return a React component). In case of a tag replacement, the function receives as
 * the argument the text inside the element corresponding to the tag.
 *
 * @return a React  component if any non-strings were used in substitutions, otherwise a string
 */
export function substitute(text, variables, tags) {
    const regexpMapping = {};

    if (variables !== undefined) {
        for (const variable in variables) {
            regexpMapping[`%\\(${variable}\\)s`] = variables[variable];
        }
    }

    if (tags !== undefined) {
        for (const tag in tags) {
            regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag];
        }
    }
    return replaceByRegexes(text, regexpMapping);
}

/*
 * Replace parts of a text using regular expressions
 * @param {string} text The text on which to perform substitutions
 * @param {object} mapping A mapping from regular expressions in string form to replacement string or a
 * function which will receive as the argument the capture groups defined in the regexp. E.g.
 * { 'Hello (.?) World': (sub) => sub.toUpperCase() }
 *
 * @return a React  component if any non-strings were used in substitutions, otherwise a string
 */
export function replaceByRegexes(text, mapping) {
    // We initially store our output as an array of strings and objects (e.g. React components).
    // This will then be converted to a string or a  at the end
    const output = [text];

    // If we insert any components we need to wrap the output in a span. React doesn't like just an array of components.
    let shouldWrapInSpan = false;

    for (const regexpString in mapping) {
        // TODO: Cache regexps
        const regexp = new RegExp(regexpString);

        // Loop over what output we have so far and perform replacements
        // We look for matches: if we find one, we get three parts: everything before the match, the replaced part,
        // and everything after the match. Insert all three into the output. We need to do this because we can insert objects.
        // Otherwise there would be no need for the splitting and we could do simple replcement.
        let matchFoundSomewhere = false; // If we don't find a match anywhere we want to log it
        for (const outputIndex in output) {
            const inputText = output[outputIndex];
            if (typeof inputText !== 'string') { // We might have inserted objects earlier, don't try to replace them
                continue;
            }

            const match = inputText.match(regexp);
            if (!match) {
                continue;
            }
            matchFoundSomewhere = true;

            const capturedGroups = match.slice(2);

            // The textual part before the match
            const head = inputText.substr(0, match.index);

            // The textual part after the match
            const tail = inputText.substr(match.index + match[0].length);

            let replaced;
            // If substitution is a function, call it
            if (mapping[regexpString] instanceof Function) {
                replaced = mapping[regexpString].apply(null, capturedGroups);
            } else {
                replaced = mapping[regexpString];
            }

            if (typeof replaced === 'object') {
                shouldWrapInSpan = true;
            }

            output.splice(outputIndex, 1); // Remove old element

            // Insert in reverse order as splice does insert-before and this way we get the final order correct
            if (tail !== '') {
                output.splice(outputIndex, 0, tail);
            }

            // Here we also need to check that it actually is a string before comparing against one
            // The head and tail are always strings
            if (typeof replaced !== 'string' || replaced !== '') {
                output.splice(outputIndex, 0, replaced);
            }

            if (head !== '') { // Don't push empty nodes, they are of no use
                output.splice(outputIndex, 0, head);
            }
        }
        if (!matchFoundSomewhere) { // The current regexp did not match anything in the input
            // Missing matches is entirely possible because you might choose to show some variables only in the case
            // of e.g. plurals. It's still a bit suspicious, and could be due to an error, so log it.
            // However, not showing count is so common that it's not worth logging. And other commonly unused variables
            // here, if there are any.
            if (regexpString !== '%\\(count\\)s') {
                console.log(`Could not find ${regexp} in ${text}`);
            }
        }
    }

    if (shouldWrapInSpan) {
        return React.createElement('span', null, ...output);
    } else {
        return output.join('');
    }
}

// Allow overriding the text displayed when no translation exists
// Currently only used in unit tests to avoid having to load
// the translations in riot-web
export function setMissingEntryGenerator(f) {
    counterpart.setMissingEntryGenerator(f);
}

export function setLanguage(preferredLangs) {
    if (!Array.isArray(preferredLangs)) {
        preferredLangs = [preferredLangs];
    }

    let langToUse;
    let availLangs;
    return getLangsJson().then((result) => {
        availLangs = result;

        for (let i = 0; i < preferredLangs.length; ++i) {
            if (availLangs.hasOwnProperty(preferredLangs[i])) {
                langToUse = preferredLangs[i];
                break;
            }
        }
        if (!langToUse) {
            // Fallback to en_EN if none is found
            langToUse = 'en';
            console.error("Unable to find an appropriate language");
        }

        return getLanguage(i18nFolder + availLangs[langToUse].fileName);
    }).then((langData) => {
        counterpart.registerTranslations(langToUse, langData);
        counterpart.setLocale(langToUse);
        SettingsStore.setValue("language", null, SettingLevel.DEVICE, langToUse);
        console.log("set language to " + langToUse);

        // Set 'en' as fallback language:
        if (langToUse != "en") {
            return getLanguage(i18nFolder + availLangs['en'].fileName);
        }
    }).then((langData) => {
        if (langData) counterpart.registerTranslations('en', langData);
    });
}

export function getAllLanguagesFromJson() {
    return getLangsJson().then((langsObject) => {
        const langs = [];
        for (const langKey in langsObject) {
            if (langsObject.hasOwnProperty(langKey)) {
                langs.push({
                    'value': langKey,
                    'label': langsObject[langKey].label,
                });
            }
        }
        return langs;
    });
}

export function getLanguagesFromBrowser() {
    if (navigator.languages && navigator.languages.length) return navigator.languages;
    if (navigator.language) return [navigator.language];
    return [navigator.userLanguage || "en"];
}

/**
 * Turns a language string, normalises it,
 * (see normalizeLanguageKey) into an array of language strings
 * with fallback to generic languages
 * (eg. 'pt-BR' => ['pt-br', 'pt'])
 *
 * @param {string} language The input language string
 * @return {string[]} List of normalised languages
 */
export function getNormalizedLanguageKeys(language) {
    const languageKeys = [];
    const normalizedLanguage = this.normalizeLanguageKey(language);
    const languageParts = normalizedLanguage.split('-');
    if (languageParts.length == 2 && languageParts[0] == languageParts[1]) {
        languageKeys.push(languageParts[0]);
    } else {
        languageKeys.push(normalizedLanguage);
        if (languageParts.length == 2) {
            languageKeys.push(languageParts[0]);
        }
    }
    return languageKeys;
}

/**
 * Returns a language string with underscores replaced with
 * hyphens, and lowercased.
 */
export function normalizeLanguageKey(language) {
    return language.toLowerCase().replace("_", "-");
}

export function getCurrentLanguage() {
    return counterpart.getLocale();
}

function getLangsJson() {
    return new Promise((resolve, reject) => {
        // LANGUAGES_FILE is a webpack compile-time define, see webpack config
        request(
            { method: "GET", url: require(LANGUAGES_FILE) },
            (err, response, body) => {
                if (err || response.status < 200 || response.status >= 300) {
                    reject({err: err, response: response});
                    return;
                }
                resolve(JSON.parse(body));
            },
        );
    });
}

function weblateToCounterpart(inTrs) {
    const outTrs = {};

    for (const key of Object.keys(inTrs)) {
        const keyParts = key.split('|', 2);
        if (keyParts.length === 2) {
            let obj = outTrs[keyParts[0]];
            if (obj === undefined) {
                obj = {};
                outTrs[keyParts[0]] = obj;
            }
            obj[keyParts[1]] = inTrs[key];
        } else {
            outTrs[key] = inTrs[key];
        }
    }

    return outTrs;
}

function getLanguage(langPath) {
    return new Promise((resolve, reject) => {
        request(
            { method: "GET", url: langPath },
            (err, response, body) => {
                if (err || response.status < 200 || response.status >= 300) {
                    reject({err: err, response: response});
                    return;
                }
                resolve(weblateToCounterpart(JSON.parse(body)));
            },
        );
    });
}
<|endoftext|>


<commit_before>$(document).ready(function(){
	$('.col-md-3').hover(function(){
		$(this).css('background','rgba(127,245,235,0.8)');
		$(this).find('.wishlist-button').show();
	}, function(){
		$(this).css('background','white');
		$(this).find('.wishlist-button').hide();
	});
});<commit_msg>remove background highlighting on hover<commit_after>$(document).ready(function(){
	$('.col-md-4').hover(function(){
		// $(this).css('background','rgba(127,245,235,0.8)');
		$(this).find('.wishlist-button').show();
	}, function(){
		// $(this).css('background','white');
		$(this).find('.wishlist-button').hide();
	});
});<|endoftext|>


<commit_before>var READ_SIZE = 100;

var path = require("path");
var express = require("express");
var app = express();
var eventbrite = require("eventbrite");
var util = require("./Util");
var argv = require('optimist').argv;
var eb_client = eventbrite({"app_key" : argv.eventbriteKey});

app.use(express.static(path.join(__dirname, "../public")));
app.get("/", function (req, res) { res.redirect("../index.html"); });

app.get("/api/getEvents", function(req, res) {
  getEvents(req.query, res);
});

app.get("/api/getEventsById", function(req, res) {
  getEventsById(req.query, res);
});

var buildEventSearchParam = function(lat, lng, radius, dateFrom, dateTo, type) {
  return {
    'latitude': lat,
    'longitude': lng,
    "within_unit" : "K",
    "max" : READ_SIZE,
    "page" : 1,
    "within" : Math.ceil(radius),
    "date" : util.getEventbriteDateFormat(dateFrom) + " " + util.getEventbriteDateFormat(dateTo),
    "category" : util.getTypeString(type),
    "sort_by" : "id"
  };
};

var getEvents = function(args, res) {
  var param = buildEventSearchParam(args.lat, args.lng, args.radius, args.dateFrom, args.dateTo, args.type);

  eb_client.event_search(param, function(err, data) {
    if (err || !data) {
      console.log('ERROR: event search failed. \n', err);
      return;
    }
    res.json(data);
  });
};

var getEventsById = function(args, res) {
  var param = {
    'id': args.id,
    'radius': Math.ceil(args.radius)
  };

  eb_client.event_get(param, function(err, data) {
    if (err || !data) {
      console.log('ERROR: get event failed. \n', err);
      return;
    }

    var lat = data.event.venue.latitude;
    var lng = data.event.venue.longitude;

    var startDate = data.event.start_date.split(" ")[0].split("-");
    var endDate = new Date(startDate);

    startDate = startDate[1] + "/" + startDate[2] + "/" + startDate[0];

    endDate.setDate(endDate.getDate() + 7);
    var endDateMonth = endDate.getMonth() + 1;
    if (endDateMonth < 10) {
      endDateMonth = "0" + endDateMonth;
    }
    var endDateDay = endDate.getDate();
    if (endDateDay < 10) {
      endDateDay = "0" + endDateDay;
    }
    endDate = endDateMonth + "/" + endDateDay + "/" + endDate.getFullYear();

    var param = buildEventSearchParam(lat, lng, args.radius, startDate, endDate);
    eb_client.event_search(param, function(err, data) {
      data.center = {lat: lat, lng: lng};
      data.date = {startDate : startDate, endDate : endDate};

      res.json(data);
    });
  });
};

app.listen(argv.port);
console.log("## Serenedi started ##");
<commit_msg>Ludimentary logging<commit_after>var READ_SIZE = 100;

var path = require("path");
var express = require("express");
var app = express();
var eventbrite = require("eventbrite");
var util = require("./Util");
var argv = require('optimist').argv;
var eb_client = eventbrite({"app_key" : argv.eventbriteKey});

app.use(express.static(path.join(__dirname, "../public")));
app.get("/", function (req, res) { res.redirect("../index.html"); });

app.get("/api/getEvents", function(req, res) {
  getEvents(req.query, res);
});

app.get("/api/getEventsById", function(req, res) {
  getEventsById(req.query, res);
});

var buildEventSearchParam = function(lat, lng, radius, dateFrom, dateTo, type) {
  return {
    'latitude': lat,
    'longitude': lng,
    "within_unit" : "K",
    "max" : READ_SIZE,
    "page" : 1,
    "within" : Math.ceil(radius),
    "date" : util.getEventbriteDateFormat(dateFrom) + " " + util.getEventbriteDateFormat(dateTo),
    "category" : util.getTypeString(type),
    "sort_by" : "id"
  };
};

var getEvents = function(args, res) {
  var param = buildEventSearchParam(args.lat, args.lng, args.radius, args.dateFrom, args.dateTo, args.type);
  console.log('[LOG] get events\n', param);

  eb_client.event_search(param, function(err, data) {
    if (err || !data) {
      console.log('[ERROR] event search failed. \n', err);
      return;
    }
    res.json(data);
  });
};

var getEventsById = function(args, res) {
  var param = {
    'id': args.id,
    'radius': Math.ceil(args.radius)
  };
  console.log('[LOG] get events by id\n', param);

  eb_client.event_get(param, function(err, data) {
    if (err || !data) {
      console.log('[ERROR] get event failed. \n', err, data);
      return;
    }

    var lat = data.event.venue.latitude;
    var lng = data.event.venue.longitude;

    var startDate = data.event.start_date.split(" ")[0].split("-");
    var endDate = new Date(startDate);

    startDate = startDate[1] + "/" + startDate[2] + "/" + startDate[0];

    endDate.setDate(endDate.getDate() + 7);
    var endDateMonth = endDate.getMonth() + 1;
    if (endDateMonth < 10) {
      endDateMonth = "0" + endDateMonth;
    }
    var endDateDay = endDate.getDate();
    if (endDateDay < 10) {
      endDateDay = "0" + endDateDay;
    }
    endDate = endDateMonth + "/" + endDateDay + "/" + endDate.getFullYear();

    var param = buildEventSearchParam(lat, lng, args.radius, startDate, endDate);
    console.log('[LOG] get events\n', param);

    eb_client.event_search(param, function(err, data) {
      data.center = {'lat': lat, 'lng': lng};
      data.date = {'startDate': startDate, 'endDate': endDate};

      res.json(data);
    });
  });
};

app.listen(argv.port);
console.log("## Serenedi started ##");
<|endoftext|>


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

/** Add new Axiom types (Implements, Constants, Topics, Properties, Methods and Listeners) to Model. */
foam.CLASS({
  refines: 'foam.core.Model',

  // documentation: 'Add new Axiom types (Implements, Constants, Topics, Properties, Methods and Listeners) to Model.',

  properties: [
    {
      class: 'AxiomArray',
      of: 'Property',
      name: 'properties',
      adaptArrayElement: foam.core.Model.PROPERTIES.adaptArrayElement
    },
    {
      class: 'AxiomArray',
      of: 'Method',
      name: 'methods',
      adaptArrayElement: function(o) {
        if ( typeof o === 'function' ) {
          console.assert(o.name, 'Method must be named');
          var m = foam.core.Method.create();
          m.name = o.name;
          m.code = o;
          return m;
        }
        return foam.core.Method.isInstance(o) ? o : foam.core.Method.create(o);
      }
    }
  ]
});

foam.boot.phase3();

foam.CLASS({
  refines: 'foam.core.FObject',

  // documentation: 'Upgrade FObject to fully bootstraped form.',

  axioms: [
    {
      name: 'X',
      installInProto: function(p) {
        Object.defineProperty(p, 'X', {
          get: function() {
            var x = this.getPrivate_('X');
            if ( ! x ) {
              var ySource = this.getPrivate_('ySource');
              if ( ySource ) {
                this.setPrivate_('X', x = ySource.Y || ySource.X);
                this.setPrivate_('ySource', undefined);
              } else {
                // TODO: Why isn't this an error?
                // console.error('Missing X in ', this.cls_.id);
                return undefined;
              }
            }
            return x;
          },
          set: function(x) {
            if ( x ) {
              this.setPrivate_(foam.core.FObject.isInstance(x) ? 'ySource' : 'X', x);
            }
          }
        });

        // If no delcared exports, then sub-context is the same as context.
        Object.defineProperty(p, 'Y', { get: function() { return this.X; } });
      }
    }
  ],

  methods: [
    /**
      Called to process constructor arguments.
      Replaces simpler version defined in original FObject definition.
    */
    function initArgs(args, X) {
      this.X = X || foam.X;
      if ( ! args ) return;

      // If args are just a simple {} map, just copy
      if ( args.__proto__ === Object.prototype || ! args.__proto__ ) {
        for ( var key in args ) {
          var a = this.cls_.getAxiomByName(key);
          if ( a && foam.core.Property.isInstance(a) ) {
            this[key] = args[key];
          } else {
            this.unknownArg(key, args[key]);
          }
        }
      }
      // If an FObject, copy values from instance_
      else if ( args.instance_ ) {
        for ( var key in args.instance_ ) {
          var a = this.cls_.getAxiomByName(key);
          if ( a && foam.core.Property.isInstance(a) ) {
            this[key] = args[key];
          }
        }
      }
      // Else call copyFrom(), which is the slowest version because
      // it is O(# of properties) not O(# of args)
      else {
        // TODO: ???: How is this possible?
        this.copyFrom(args);
      }
    },

    /**
      Template method used to report an unknown argument passed
      to a constructor. Is set in debug.js.
    */
    function unknownArg(key, value) {
      // NOP
    }
  ]
});

foam.boot.end();

// TODO: doc
foam.CLASS({
  refines: 'foam.core.Property',

  properties: [
    {
      class: 'Boolean',
      name: 'transient'
    },
    {
      class: 'Boolean',
      name: 'networkTransient',
      expression: function(transient) {
        return transient;
      }
    },
    {
      class: 'Boolean',
      name: 'storageTransient',
      expression: function(transient) {
        return transient;
      }
    }
  ]
});
<commit_msg>Removed TODO and added docs.<commit_after>/*
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/** Add new Axiom types (Implements, Constants, Topics, Properties, Methods and Listeners) to Model. */
foam.CLASS({
  refines: 'foam.core.Model',

  // documentation: 'Add new Axiom types (Implements, Constants, Topics, Properties, Methods and Listeners) to Model.',

  properties: [
    {
      class: 'AxiomArray',
      of: 'Property',
      name: 'properties',
      adaptArrayElement: foam.core.Model.PROPERTIES.adaptArrayElement
    },
    {
      class: 'AxiomArray',
      of: 'Method',
      name: 'methods',
      adaptArrayElement: function(o) {
        if ( typeof o === 'function' ) {
          console.assert(o.name, 'Method must be named');
          var m = foam.core.Method.create();
          m.name = o.name;
          m.code = o;
          return m;
        }
        return foam.core.Method.isInstance(o) ? o : foam.core.Method.create(o);
      }
    }
  ]
});

foam.boot.phase3();

foam.CLASS({
  refines: 'foam.core.FObject',

  // documentation: 'Upgrade FObject to fully bootstraped form.',

  axioms: [
    {
      name: 'X',
      installInProto: function(p) {
        Object.defineProperty(p, 'X', {
          get: function() {
            var x = this.getPrivate_('X');
            if ( ! x ) {
              var ySource = this.getPrivate_('ySource');
              if ( ySource ) {
                this.setPrivate_('X', x = ySource.Y || ySource.X);
                this.setPrivate_('ySource', undefined);
              } else {
                // TODO: Why isn't this an error?
                // console.error('Missing X in ', this.cls_.id);
                return undefined;
              }
            }
            return x;
          },
          set: function(x) {
            if ( x ) {
              this.setPrivate_(foam.core.FObject.isInstance(x) ? 'ySource' : 'X', x);
            }
          }
        });

        // If no delcared exports, then sub-context is the same as context.
        Object.defineProperty(p, 'Y', { get: function() { return this.X; } });
      }
    }
  ],

  methods: [
    /**
      Called to process constructor arguments.
      Replaces simpler version defined in original FObject definition.
    */
    function initArgs(args, X) {
      this.X = X || foam.X;
      if ( ! args ) return;

      // If args are just a simple {} map, just copy
      if ( args.__proto__ === Object.prototype || ! args.__proto__ ) {
        for ( var key in args ) {
          var a = this.cls_.getAxiomByName(key);
          if ( a && foam.core.Property.isInstance(a) ) {
            this[key] = args[key];
          } else {
            this.unknownArg(key, args[key]);
          }
        }
      }
      // If an FObject, copy values from instance_
      else if ( args.instance_ ) {
        for ( var key in args.instance_ ) {
          var a = this.cls_.getAxiomByName(key);
          if ( a && foam.core.Property.isInstance(a) ) {
            this[key] = args[key];
          }
        }
      }
      // Else call copyFrom(), which is the slowest version because
      // it is O(# of properties) not O(# of args)
      // This is possible if called with a map with a prototype other
      // than Object.prototype or null. Should rarely happen.
      else {
        this.copyFrom(args);
      }
    },

    /**
      Template method used to report an unknown argument passed
      to a constructor. Is set in debug.js.
    */
    function unknownArg(key, value) {
      // NOP
    }
  ]
});

foam.boot.end();

// TODO: doc
foam.CLASS({
  refines: 'foam.core.Property',

  properties: [
    {
      class: 'Boolean',
      name: 'transient'
    },
    {
      class: 'Boolean',
      name: 'networkTransient',
      expression: function(transient) {
        return transient;
      }
    },
    {
      class: 'Boolean',
      name: 'storageTransient',
      expression: function(transient) {
        return transient;
      }
    }
  ]
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import capitalize from 'capitalize';
import { withItem, withCollection } from '../../decorators';
import { Modal, Button, Form, Input, DatePicker, Select, Slider, Tabs, Collapse, Checkbox } from 'antd';
import { SlateMate } from 'olymp/slate';
import moment from 'moment';
import Image from '../../edits/image';

const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
const Panel = Collapse.Panel;

const modalSettings = { visible: true, style: { top: 20 }, okText: 'Speichern', cancelText: 'Abbruch' };
const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 18 } };

const preventDefaultAnd = (func, args) => e => {
  e.preventDefault();
  func(args);
};
const SubForm = withCollection(({ value = [], collection, onChange, ...rest }) => {
  const removeItem = index => onChange(value.filter((x, i) => i !== index));
  const createItem = () => onChange([...value, {}]);
  const patchItem = (index, nestedValue) => onChange(value.map((x, i) => i === index ? nestedValue : x));
  const getHeader = (title, index) => (

      {title}

  );
  return (

      <Collapse accordion>
        {value.map((value, i) => (
          <Panel header={getHeader(value.name || `Eintrag ${i}`, i)} key={i}>

              {collection.fields.filter(({ name }) => name !== 'id').map(({ type, name }) =>
                <FormItem key={name}>
                  {getFormEditor(
                    type,
                    name,
                    {
                      value: value[name],
                      onChange: v => patchItem(i, {
                        ...value,
                        [name]: v && v.target ? v.target.value : v,
                      }),
                      disabled: (name === 'createdAt' || name === 'createdBy' || name === 'updatedAt' || name === 'updatedBy'),
                    }
                  )}
                </FormItem>
              )}

          </Panel>
        ))}
      </Collapse>
      <Button onClick={createItem}>Erstellen</Button>

  );
});
const MultiSlider = ({ value = [] }) => {
  return (

      {value.map((v, i) => (

          <Slider marks={{ 8: '08:00', 13: '13:00', 18: '18:00' }} range={3} min={7} max={19} step={0.5}
                  tipFormatter={v => v % 1 === 0 ? `${v}:00` : `${Math.floor(v)}:30`} />

      ))}

  );
};

class DatePickerInt extends Component {
  onChange = (e) => {
    const { onChange } = this.props;
    const value = parseInt(e.format('x'));
    onChange(value);
  }
  getValue = () => {
    let { value } = this.props;
    // 2014-09-14T10:32:27.831Z
    if (typeof value === 'string') value = parseInt(moment(value.replace(/"/g, '')).format('x'));
    return value ? moment(value) : undefined;
  }
  render() {
    return <DatePicker {...this.props} value={this.getValue()} onChange={this.onChange} />;
  }
}

class SlateMateExt extends Component {
  state = { show: false };
  onChange = (e) => {
    const { onChange } = this.props;
    onChange(e);
  }
  render() {
    const { show } = this.state;
    const { value } = this.props;
    if (show) return <SlateMate {...this.props} value={value} onChange={this.onChange} />;
    return <Button onClick={() => this.setState({ show: true })}>Anzeigen</Button>;
  }
}

const getFormEditor = (type, name, props = {}) => {
  if (type.kind === 'LIST') {
    if (type.ofType.name === 'String') {
      return <Select {...props} tags searchPlaceholder="Suche ..." />;
    } else if (type.ofType.name.indexOf('nested') === 0) {
      return <SubForm {...props} name={type.ofType.name} type={type} />;
    }
  }

  if (type.kind === 'ENUM' && type.enumValues) {
    return (
      <Select {...props}>
        {type.enumValues.map(x => (
          <Select.Option key={x.name} value={x.name}>{x.name}</Select.Option>
        ))}
      </Select>
    );
  }

  switch (type.name) {
    case 'Json':
      return <SlateMateExt {...props} className="form-control" placeholder={name} />;

    case 'Boolean':
      return <Checkbox {...props} />;

    case 'Date':
      return <DatePickerInt {...props} placeholder={name} format="DD.MM.YYYY" />;

    case 'DateTime':
      return <DatePickerInt {...props} placeholder={name} format="DD.MM.YYYY HH:mm" showTime={{ format: 'HH:mm' }} />;

    case 'Color':
      return <Input {...props} placeholder={name} type="color" addonBefore={} />;

    case 'Markdown':
      return <Input {...props} placeholder={name} type="textarea" autosize />;

    case 'Slug':
      return <Input {...props} placeholder={name} addonBefore={} />;

    case 'Email':
      return <Input {...props} placeholder={name} addonBefore={} />;

    case 'PhoneNumber':
      return <Input {...props} placeholder={name} addonBefore={} />;

    case 'Website':
      return <Input {...props} placeholder={name} addonBefore={} />;

    case 'TimeRange':
      return <MultiSlider {...props} />;

    case 'image':
      return <Image {...props} width="100%" noPreview />;

    /* case 'user':
      return type.fields.map(field => (
        <Select.Option key={field.name} value={field.name}>{field.name}</Select.Option>
      )); */

    default:
      return <Input {...props} placeholder={name} />;
  }
};

const getInitialValue = ({ item, form }, { name, description }) => {
  if (item[name]) {
    // Wenn Item schon existiert, den vorhandenen Wert nehmen
    return item[name];
  } else if (description && description.indexOf('default:') !== -1) {
    // Wenn ein default-Wert existiert
    return description.split('default:')[1].split(' ')[0].split('\n')[0];
  } else if (name === 'state') {
    // Bei State
    return 'DRAFT';
  } else if (name === 'slug' && form.getFieldValue('name')) {
    // Bei Slug
    let url = '/' + encodeURIComponent(form.getFieldValue('name').split(' ').join('-').toLowerCase())
      .split('%C3%A4').join('ä')
      .split('%C3%B6').join('ö')
      .split('%C3%BC').join('ü')
      .split('%C3%A4').join('Ä')
      .split('%C3%B6').join('Ö')
      .split('%C3%BC').join('Ü');
    if (form.getFieldValue('date')) {
      url = moment(form.getFieldValue('date')).format('DD-MM-YYYY') + '-' + url;
    }
    return url;
  }

  return undefined;
};
const CollectionCreateForm = Form.create()(
  (props) => {
    const { collection, form, onCreate, onCancel, saving } = props;
    const { getFieldDecorator } = form;

    const fields = collection.fields.filter(({ name }) => name !== 'id').reduce((state, item) => {
      const group = item.description && item.description.indexOf('detail:') !== -1 ? item.description.split('detail:')[1].split('\n')[0] : 'Allgemein';
      if (!state[group]) state[group] = [];
      state[group].push(item);

      return state;
    }, {});

    const renderForm = fields => (
      <Form horizontal>
        {fields.filter(({ name }) => name !== 'id').map((field) => {
          const title = field.description && field.description.indexOf('title:') !== -1 ? field.description.split('title:')[1].split('\n')[0] : toLabel(field.name);

          return (
            <FormItem key={field.name} label={title} {...formItemLayout}>
              {getFieldDecorator(field.name, { initialValue: getInitialValue(props, field) })(
                getFormEditor(
                  field.type,
                  name,
                  (field.name === 'createdAt' || field.name === 'createdBy' || field.name === 'updatedAt' || field.name === 'updatedBy') ? { disabled: true } : {}
                )
              )}
            </FormItem>
          );
        })}
      </Form>
    );

    return (
      <Modal {...modalSettings} confirmLoading={saving} title="Bearbeiten" onCancel={onCancel} onOk={onCreate}>
        {Object.keys(fields).length === 1 ? renderForm(fields.Allgemein) : (
          <Tabs defaultActiveKey="0" type="card">
            {Object.keys(fields).map((key, i) => (
              <TabPane tab={key} key={i}>
                {renderForm(fields[key])}
              </TabPane>
            ))}
          </Tabs>
        )}
      </Modal>
    );
  }
);

@withCollection
@withItem({})
export default class MainDetail extends Component {
  handleCancel = () => {
    this.props.onClose();
  }

  handleCreate = () => {
    const { save, onClose } = this.props;
    const form = this.form;
    form.validateFields((err, values) => {
      if (err) {
        return;
      }

      // console.log('Received values of form: ', values);
      save(values, { commit: false }).then(onClose);
    });
  }

  render() {
    const { item } = this.props;
    if (!item) return null;
    return (
      <CollectionCreateForm
        {...this.props}
        ref={form => this.form = form}
        onCancel={this.handleCancel}
        onCreate={this.handleCreate}
      />
    );
  }
}

const toLabel = (x) => {
  const uml = x.replace(/ae/g, 'ä').replace(/oe/g, 'ö').replace(/ü/g, 'ue').replace(/Ae/g, 'Ä').replace(/Oe/g, 'Ö').replace(/Ue/g, 'Ü');
  const snake = uml.replace(/([A-Z])/g, ($1) => `-${$1}`);
  const capitalized = capitalize(snake);
  return capitalized;
};
<commit_msg>use fade animation on modal (only for collections) -> what do you think??<commit_after>import React, { Component } from 'react';
import capitalize from 'capitalize';
import { withItem, withCollection } from '../../decorators';
import { Modal, Button, Form, Input, DatePicker, Select, Slider, Tabs, Collapse, Checkbox } from 'antd';
import { SlateMate } from 'olymp/slate';
import moment from 'moment';
import Image from '../../edits/image';

const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
const Panel = Collapse.Panel;

const modalSettings = { visible: true, style: { top: 20 }, okText: 'Speichern', cancelText: 'Abbruch', transitionName: 'fade', maskTransitionName: 'fade' };
const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 18 } };

const preventDefaultAnd = (func, args) => e => {
  e.preventDefault();
  func(args);
};
const SubForm = withCollection(({ value = [], collection, onChange, ...rest }) => {
  const removeItem = index => onChange(value.filter((x, i) => i !== index));
  const createItem = () => onChange([...value, {}]);
  const patchItem = (index, nestedValue) => onChange(value.map((x, i) => i === index ? nestedValue : x));
  const getHeader = (title, index) => (

      {title}

  );
  return (

      <Collapse accordion>
        {value.map((value, i) => (
          <Panel header={getHeader(value.name || `Eintrag ${i}`, i)} key={i}>

              {collection.fields.filter(({ name }) => name !== 'id').map(({ type, name }) =>
                <FormItem key={name}>
                  {getFormEditor(
                    type,
                    name,
                    {
                      value: value[name],
                      onChange: v => patchItem(i, {
                        ...value,
                        [name]: v && v.target ? v.target.value : v,
                      }),
                      disabled: (name === 'createdAt' || name === 'createdBy' || name === 'updatedAt' || name === 'updatedBy'),
                    }
                  )}
                </FormItem>
              )}

          </Panel>
        ))}
      </Collapse>
      <Button onClick={createItem}>Erstellen</Button>

  );
});
const MultiSlider = ({ value = [] }) => {
  return (

      {value.map((v, i) => (

          <Slider marks={{ 8: '08:00', 13: '13:00', 18: '18:00' }} range={3} min={7} max={19} step={0.5}
                  tipFormatter={v => v % 1 === 0 ? `${v}:00` : `${Math.floor(v)}:30`} />

      ))}

  );
};

class DatePickerInt extends Component {
  onChange = (e) => {
    const { onChange } = this.props;
    const value = parseInt(e.format('x'));
    onChange(value);
  }
  getValue = () => {
    let { value } = this.props;
    // 2014-09-14T10:32:27.831Z
    if (typeof value === 'string') value = parseInt(moment(value.replace(/"/g, '')).format('x'));
    return value ? moment(value) : undefined;
  }
  render() {
    return <DatePicker {...this.props} value={this.getValue()} onChange={this.onChange} />;
  }
}

class SlateMateExt extends Component {
  state = { show: false };
  onChange = (e) => {
    const { onChange } = this.props;
    onChange(e);
  }
  render() {
    const { show } = this.state;
    const { value } = this.props;
    if (show) return <SlateMate {...this.props} value={value} onChange={this.onChange} />;
    return <Button onClick={() => this.setState({ show: true })}>Anzeigen</Button>;
  }
}

const getFormEditor = (type, name, props = {}) => {
  if (type.kind === 'LIST') {
    if (type.ofType.name === 'String') {
      return <Select {...props} tags searchPlaceholder="Suche ..." />;
    } else if (type.ofType.name.indexOf('nested') === 0) {
      return <SubForm {...props} name={type.ofType.name} type={type} />;
    }
  }

  if (type.kind === 'ENUM' && type.enumValues) {
    return (
      <Select {...props}>
        {type.enumValues.map(x => (
          <Select.Option key={x.name} value={x.name}>{x.name}</Select.Option>
        ))}
      </Select>
    );
  }

  switch (type.name) {
    case 'Json':
      return <SlateMateExt {...props} className="form-control" placeholder={name} />;

    case 'Boolean':
      return <Checkbox {...props} />;

    case 'Date':
      return <DatePickerInt {...props} placeholder={name} format="DD.MM.YYYY" />;

    case 'DateTime':
      return <DatePickerInt {...props} placeholder={name} format="DD.MM.YYYY HH:mm" showTime={{ format: 'HH:mm' }} />;

    case 'Color':
      return <Input {...props} placeholder={name} type="color" addonBefore={} />;

    case 'Markdown':
      return <Input {...props} placeholder={name} type="textarea" autosize />;

    case 'Slug':
      return <Input {...props} placeholder={name} addonBefore={} />;

    case 'Email':
      return <Input {...props} placeholder={name} addonBefore={} />;

    case 'PhoneNumber':
      return <Input {...props} placeholder={name} addonBefore={} />;

    case 'Website':
      return <Input {...props} placeholder={name} addonBefore={} />;

    case 'TimeRange':
      return <MultiSlider {...props} />;

    case 'image':
      return <Image {...props} width="100%" noPreview />;

    /* case 'user':
      return type.fields.map(field => (
        <Select.Option key={field.name} value={field.name}>{field.name}</Select.Option>
      )); */

    default:
      return <Input {...props} placeholder={name} />;
  }
};

const getInitialValue = ({ item, form }, { name, description }) => {
  if (item[name]) {
    // Wenn Item schon existiert, den vorhandenen Wert nehmen
    return item[name];
  } else if (description && description.indexOf('default:') !== -1) {
    // Wenn ein default-Wert existiert
    return description.split('default:')[1].split(' ')[0].split('\n')[0];
  } else if (name === 'state') {
    // Bei State
    return 'DRAFT';
  } else if (name === 'slug' && form.getFieldValue('name')) {
    // Bei Slug
    let url = '/' + encodeURIComponent(form.getFieldValue('name').split(' ').join('-').toLowerCase())
      .split('%C3%A4').join('ä')
      .split('%C3%B6').join('ö')
      .split('%C3%BC').join('ü')
      .split('%C3%A4').join('Ä')
      .split('%C3%B6').join('Ö')
      .split('%C3%BC').join('Ü');
    if (form.getFieldValue('date')) {
      url = moment(form.getFieldValue('date')).format('DD-MM-YYYY') + '-' + url;
    }
    return url;
  }

  return undefined;
};
const CollectionCreateForm = Form.create()(
  (props) => {
    const { collection, form, onCreate, onCancel, saving } = props;
    const { getFieldDecorator } = form;

    const fields = collection.fields.filter(({ name }) => name !== 'id').reduce((state, item) => {
      const group = item.description && item.description.indexOf('detail:') !== -1 ? item.description.split('detail:')[1].split('\n')[0] : 'Allgemein';
      if (!state[group]) state[group] = [];
      state[group].push(item);

      return state;
    }, {});

    const renderForm = fields => (
      <Form horizontal>
        {fields.filter(({ name }) => name !== 'id').map((field) => {
          const title = field.description && field.description.indexOf('title:') !== -1 ? field.description.split('title:')[1].split('\n')[0] : toLabel(field.name);

          return (
            <FormItem key={field.name} label={title} {...formItemLayout}>
              {getFieldDecorator(field.name, { initialValue: getInitialValue(props, field) })(
                getFormEditor(
                  field.type,
                  name,
                  (field.name === 'createdAt' || field.name === 'createdBy' || field.name === 'updatedAt' || field.name === 'updatedBy') ? { disabled: true } : {}
                )
              )}
            </FormItem>
          );
        })}
      </Form>
    );

    return (
      <Modal {...modalSettings} confirmLoading={saving} title="Bearbeiten" onCancel={onCancel} onOk={onCreate}>
        {Object.keys(fields).length === 1 ? renderForm(fields.Allgemein) : (
          <Tabs defaultActiveKey="0" type="card">
            {Object.keys(fields).map((key, i) => (
              <TabPane tab={key} key={i}>
                {renderForm(fields[key])}
              </TabPane>
            ))}
          </Tabs>
        )}
      </Modal>
    );
  }
);

@withCollection
@withItem({})
export default class MainDetail extends Component {
  handleCancel = () => {
    this.props.onClose();
  }

  handleCreate = () => {
    const { save, onClose } = this.props;
    const form = this.form;
    form.validateFields((err, values) => {
      if (err) {
        return;
      }

      // console.log('Received values of form: ', values);
      save(values, { commit: false }).then(onClose);
    });
  }

  render() {
    const { item } = this.props;
    if (!item) return null;
    return (
      <CollectionCreateForm
        {...this.props}
        ref={form => this.form = form}
        onCancel={this.handleCancel}
        onCreate={this.handleCreate}
      />
    );
  }
}

const toLabel = (x) => {
  const uml = x.replace(/ae/g, 'ä').replace(/oe/g, 'ö').replace(/ü/g, 'ue').replace(/Ae/g, 'Ä').replace(/Oe/g, 'Ö').replace(/Ue/g, 'Ü');
  const snake = uml.replace(/([A-Z])/g, ($1) => `-${$1}`);
  const capitalized = capitalize(snake);
  return capitalized;
};
<|endoftext|>


<commit_before>import mobxStore from '../../mobxStore'
import { path as pathUtil } from '../../utils'
import api from '../../backendAPI'
import * as Modal from '../../components/Modal/actions'
import TabStore from 'components/Tab/store'
import FileStore from 'commons/File/store'
import { notify } from '../../components/Notification/actions'
import i18n from 'utils/createI18n'

const nodeToNearestDirPath = (node) => {
  if (!node) node = { isDir: true, path: '/' } // fake a root node if !node
  if (node.isDir) {
    var path = node.path
  } else {
    var path = node.parent.path
  }
  if (path != '/') path += '/'
  return path
}

const nodeToParentDirPath = (node) => {
  const pathSplitted = node.path.split('/')
  if (pathSplitted.pop() == '') { pathSplitted.pop() }
  return `${pathSplitted.join('/')}/`
}

function createFileWithContent (content) {
  return function createFileAtPath (path) {
    return api.createFile(path, content)
      .then(() => { if (content) api.writeFile(path, content) })
      .then(Modal.dismissModal)
      .then(() => path)
      // if error, try again.
      .catch(err =>
        Modal.updateModal({ statusMessage: err.msg }).then(createFileAtPath)
      )
  }
}

function createFolderAtPath (path) {
  return api.createFolder(path)
  .then((data) => {
    if (data.code < 0) {
      Modal.updateModal({ statusMessage: data.msg }).then(createFolderAtPath)
    } else {
      Modal.dismissModal()
    }
  })
  .then(() => path)
    // if error, try again.
  .catch(err =>
    Modal.updateModal({ statusMessage: err.msg }).then(createFolderAtPath)
  )
}

export function openFile ({ path, editor = {}, others = {} }) {
  return api.readFile(path)
    .then((data) => {
      FileStore.loadNodeData(data)
      return data
    })
    .then(() => {
      const activeTabGroup = TabStore.getState().activeTabGroup
      const existingTabs = TabStore.findTab(
        tab => tab.file && tab.file.path === path && tab.tabGroup === activeTabGroup
      )
      if (existingTabs.length) {
        const existingTab = existingTabs[0]
        existingTab.activate()
      } else {
        TabStore.createTab({
          icon: 'fa fa-file-text-o',
          editor: {
            ...editor,
            filePath: path,
          },
          ...others
        })
      }
    })
}

export default {
  'file:open_file': (c) => {
    if (typeof c.data === 'string') {
      openFile({ path: c.data })
    } else {
      openFile(c.data)
    }
  },
  'file:new_file': (c) => {
    const node = c.context
    const path = nodeToNearestDirPath(node)
    const defaultValue = pathUtil.join(path, 'untitled')

    const createFile = createFileWithContent(null)

    Modal.showModal('Prompt', {
      message: i18n`file.newFilePath`,
      defaultValue,
      selectionRange: [path.length, defaultValue.length]
    })
    .then(createFile)
    .then(path => openFile({ path }))
  },
  'file:new_folder': (c) => {
    const node = c.context
    const path = nodeToNearestDirPath(node)
    const defaultValue = pathUtil.join(path, 'untitled')
    Modal.showModal('Prompt', {
      message: i18n`file.newFileFolderPath`,
      defaultValue,
      selectionRange: [path.length, defaultValue.length],
    }).then(createFolderAtPath)
  },
  'file:save': (c) => {
    const { EditorTabState } = mobxStore
    const activeTab = EditorTabState.activeTab
    const content = activeTab ? activeTab.editor.cm.getValue() : ''

    if (!activeTab.file) {
      const createFile = createFileWithContent(content)
      Modal.showModal('Prompt', {
        message: i18n`file.newFilePath`,
        defaultValue: '/untitled',
        selectionRange: [1, '/untitled'.length]
      })
        .then(createFile)
        .then((path) => {
          api.readFile(path).then((data) => {
            FileStore.loadNodeData(data)
            TabStore.updateTab({
              id: activeTab.id,
              editor: { filePath: path },
            })
          })
        })
    } else {
      api.writeFile(activeTab.file.path, content)
        .then(() => {
          FileStore.updateFile({
            path: activeTab.file.path,
            isSynced: true,
            content,
          })
        })
    }
  },

  'file:rename': (c) => {
    const node = c.context
    const parentPath = nodeToParentDirPath(node)
    const existingTabs = TabStore.findTab(
      tab => tab.file && tab.file.path === node.path
    )

    const moveFile = (from, newPath, force) => {
      api.moveFile(node.path, newPath, force)
        .then(() => Modal.dismissModal())
        .catch(err =>
          Modal.updateModal({ statusMessage: err.msg })
          .then((newPath, force) =>
            moveFile(from, newPath, force)
          )
        ).then(() => {
          if (existingTabs.length) {
            api.readFile(newPath).then((data) => {
              FileStore.loadNodeData(data)
              existingTabs.forEach((tab) => {
                TabStore.updateTab({
                  id: tab.id,
                  editor: { filePath: newPath },
                })
              })
            })
          }
        })
    }

    Modal.showModal('Prompt', {
      message: i18n`file.newFileName`,
      defaultValue: node.path,
      selectionRange: [parentPath.length, node.path.length]
    }).then(newPath => moveFile(node.path, newPath))
  },

  'file:delete': async (c) => {
    const confirmed = await Modal.showModal('Confirm', {
      header: i18n`file.deleteHeader`,
      message: i18n`file.deleteMessage${{ file: c.context.path }}`,
      okText: i18n`file.deleteButton`
    })

    if (confirmed) {
      api.deleteFile(c.context.path)
        .then(() => notify({ message: i18n`file.deleteNotifySuccess` }))
        .catch(err =>
          notify({ message: i18n`file.deleteNotifyFailed${err.msg}` })
        )
    }

    Modal.dismissModal()
  },

  'file:download': (c) => {
    api.downloadFile(c.context.path, c.context.isDir)
  },

  // 'file:unsaved_files_list':
}
<commit_msg>:bug: fix handling of "fs:rename" action.<commit_after>import mobxStore from '../../mobxStore'
import { path as pathUtil } from '../../utils'
import api from '../../backendAPI'
import * as Modal from '../../components/Modal/actions'
import TabStore from 'components/Tab/store'
import FileStore from 'commons/File/store'
import { notify } from '../../components/Notification/actions'
import i18n from 'utils/createI18n'

const nodeToNearestDirPath = (node) => {
  if (!node) node = { isDir: true, path: '/' } // fake a root node if !node
  if (node.isDir) {
    var path = node.path
  } else {
    var path = node.parent.path
  }
  if (path != '/') path += '/'
  return path
}

const nodeToParentDirPath = (node) => {
  const pathSplitted = node.path.split('/')
  if (pathSplitted.pop() == '') { pathSplitted.pop() }
  return `${pathSplitted.join('/')}/`
}

function createFileWithContent (content) {
  return function createFileAtPath (path) {
    return api.createFile(path, content)
      .then(() => { if (content) api.writeFile(path, content) })
      .then(Modal.dismissModal)
      .then(() => path)
      // if error, try again.
      .catch(err =>
        Modal.updateModal({ statusMessage: err.msg }).then(createFileAtPath)
      )
  }
}

function createFolderAtPath (path) {
  return api.createFolder(path)
  .then((data) => {
    if (data.code < 0) {
      Modal.updateModal({ statusMessage: data.msg }).then(createFolderAtPath)
    } else {
      Modal.dismissModal()
    }
  })
  .then(() => path)
    // if error, try again.
  .catch(err =>
    Modal.updateModal({ statusMessage: err.msg }).then(createFolderAtPath)
  )
}

export function openFile ({ path, editor = {}, others = {} }) {
  return api.readFile(path)
    .then((data) => {
      FileStore.loadNodeData(data)
      return data
    })
    .then(() => {
      const activeTabGroup = TabStore.getState().activeTabGroup
      const existingTabs = TabStore.findTab(
        tab => tab.file && tab.file.path === path && tab.tabGroup === activeTabGroup
      )
      if (existingTabs.length) {
        const existingTab = existingTabs[0]
        existingTab.activate()
      } else {
        TabStore.createTab({
          icon: 'fa fa-file-text-o',
          editor: {
            ...editor,
            filePath: path,
          },
          ...others
        })
      }
    })
}

export default {
  'file:open_file': (c) => {
    if (typeof c.data === 'string') {
      openFile({ path: c.data })
    } else {
      openFile(c.data)
    }
  },
  'file:new_file': (c) => {
    const node = c.context
    const path = nodeToNearestDirPath(node)
    const defaultValue = pathUtil.join(path, 'untitled')

    const createFile = createFileWithContent(null)

    Modal.showModal('Prompt', {
      message: i18n`file.newFilePath`,
      defaultValue,
      selectionRange: [path.length, defaultValue.length]
    })
    .then(createFile)
    .then(path => openFile({ path }))
  },
  'file:new_folder': (c) => {
    const node = c.context
    const path = nodeToNearestDirPath(node)
    const defaultValue = pathUtil.join(path, 'untitled')
    Modal.showModal('Prompt', {
      message: i18n`file.newFileFolderPath`,
      defaultValue,
      selectionRange: [path.length, defaultValue.length],
    }).then(createFolderAtPath)
  },
  'file:save': (c) => {
    const { EditorTabState } = mobxStore
    const activeTab = EditorTabState.activeTab
    const content = activeTab ? activeTab.editor.cm.getValue() : ''

    if (!activeTab.file) {
      const createFile = createFileWithContent(content)
      Modal.showModal('Prompt', {
        message: i18n`file.newFilePath`,
        defaultValue: '/untitled',
        selectionRange: [1, '/untitled'.length]
      })
        .then(createFile)
        .then((path) => {
          api.readFile(path).then((data) => {
            FileStore.loadNodeData(data)
            TabStore.updateTab({
              id: activeTab.id,
              editor: { filePath: path },
            })
          })
        })
    } else {
      api.writeFile(activeTab.file.path, content)
        .then(() => {
          FileStore.updateFile({
            path: activeTab.file.path,
            isSynced: true,
            content,
          })
        })
    }
  },

  'file:rename': (c) => {
    const node = c.context
    const oldPath = node.path
    const parentPath = nodeToParentDirPath(node)
    const existingTabs = TabStore.findTab(
      tab => tab.file && tab.file.path.startsWith(oldPath)
    )

    const moveFile = (from, to, force) => {
      api.moveFile(from, to, force)
        .then(() => Modal.dismissModal())
        .catch(err =>
          Modal.updateModal({ statusMessage: err.msg }).then((_to, _force) =>
            moveFile(from, _to, _force)
          )
        ).then(() => {
          if (existingTabs.length) {
            existingTabs.forEach((tab) => {
              const newPath = tab.file.path.replace(from, to)
              api.readFile(newPath).then((data) => {
                FileStore.loadNodeData(data)
                TabStore.updateTab({ id: tab.id, editor: { filePath: newPath } })
              })
            })
          }
        })
    }

    Modal.showModal('Prompt', {
      message: i18n`file.newFileName`,
      defaultValue: oldPath,
      selectionRange: [parentPath.length, oldPath.length]
    }).then(newPath => moveFile(oldPath, newPath))
  },

  'file:delete': async (c) => {
    const confirmed = await Modal.showModal('Confirm', {
      header: i18n`file.deleteHeader`,
      message: i18n`file.deleteMessage${{ file: c.context.path }}`,
      okText: i18n`file.deleteButton`
    })

    if (confirmed) {
      api.deleteFile(c.context.path)
        .then(() => notify({ message: i18n`file.deleteNotifySuccess` }))
        .catch(err =>
          notify({ message: i18n`file.deleteNotifyFailed${err.msg}` })
        )
    }

    Modal.dismissModal()
  },

  'file:download': (c) => {
    api.downloadFile(c.context.path, c.context.isDir)
  },

  // 'file:unsaved_files_list':
}
<|endoftext|>


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

import React, {Component, cloneElement, Fragment} from 'react';
import PropTypes from 'prop-types';
import {findDOMNode} from 'react-dom';

import Popover from '../Popover';
import Theme from '../Theme';

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

class PopoverProvider extends Component {

    static Position = Tip.Position;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            tipVisible: false
        };

    }

    showTip = () => {
        if (!this.state.tipVisible) {
            this.setState({
                tipVisible: true
            });
        }
    };

    hideTip = () => {
        this.setState({
            tipVisible: false
        });
    };

    componentDidMount() {
        this.refs.trigger && (this.triggerEl = findDOMNode(this.refs.trigger));
    }

    render() {

        const {children, text, onTipRender, ...restProps} = this.props,
            {tipVisible} = this.state;

        if (!text) {
            return children;
        }

        return (
            <Fragment>

                {
                    cloneElement(children, {
                        ref: 'trigger',
                        onMouseOver: e => {

                            if (children && children.props && children.props.onMouseOver
                                && typeof children.props.onMouseOver === 'function') {
                                children.props.onMouseOver(e);
                            }

                            this.showTip();

                        },
                        onMouseOut: e => {

                            if (children && children.props && children.props.onMouseOut
                                && typeof children.props.onMouseOut === 'function') {
                                children.props.onMouseOut(e);
                            }

                            this.hideTip();

                        }
                    })
                }

                <Popover {...restProps}
                         triggerEl={this.triggerEl}
                         visible={tipVisible}
                         onRender={onTipRender}>
                    {text}
                </Popover>

            </Fragment>
        );
    }

}

PopoverProvider.propTypes = {

    /**
     * The CSS class name of the tip element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * If true,the popover will have a triangle on the top of the DOM element.
     */
    hasTriangle: PropTypes.bool,

    triangle: PropTypes.element,

    /**
     * The popover theme.Can be primary,highlight,success,warning,error.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The popover alignment.
     */
    position: PropTypes.oneOf(Util.enumerateValue(Tip.Position)),

    /**
     * If true, menu will have animation effects.
     */
    isAnimated: PropTypes.bool,

    shouldPreventContainerScroll: PropTypes.bool,

    /**
     * The depth of Paper component.
     */
    depth: PropTypes.number,

    isTriggerPositionFixed: PropTypes.bool,

    /**
     * The function of tip render.
     */
    onTipRender: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func,

    text: PropTypes.any

};

PopoverProvider.defaultProps = {

    theme: Theme.DARK,

    hasTriangle: true,
    position: Tip.Position.BOTTOM,
    isAnimated: true,
    shouldPreventContainerScroll: true,
    isTriggerPositionFixed: false

};

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

import React, {Component, cloneElement, Fragment} from 'react';
import PropTypes from 'prop-types';
import {findDOMNode} from 'react-dom';

import Popover from '../Popover';

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

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

class PopoverProvider extends Component {

    static Position = Position;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

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

    }

    showTip = () => {
        if (!this.state.visible) {
            this.setState({
                visible: true
            });
        }
    };

    hideTip = () => {
        this.setState({
            visible: false
        });
    };

    componentDidMount() {
        this.refs.trigger && (this.triggerEl = findDOMNode(this.refs.trigger));
    }

    static getDerivedStateFromProps(props, state) {
        return {
            prevProps: props,
            value: ComponentUtil.getDerivedState(props, state, 'visible')
        };
    }

    render() {

        const {children, popoverContent, ...restProps} = this.props,
            {visible} = this.state;

        if (!popoverContent) {
            return children;
        }

        return (
            <Fragment>

                {
                    cloneElement(children, {
                        ref: 'trigger',
                        onMouseOver: e => {

                            if (children && children.props && children.props.onMouseOver
                                && typeof children.props.onMouseOver === 'function') {
                                children.props.onMouseOver(e);
                            }

                            this.showTip();

                        },
                        onMouseOut: e => {

                            if (children && children.props && children.props.onMouseOut
                                && typeof children.props.onMouseOut === 'function') {
                                children.props.onMouseOut(e);
                            }

                            this.hideTip();

                        }
                    })
                }

                <Popover {...restProps}
                         triggerEl={this.triggerEl}
                         visible={visible}>
                    {popoverContent}
                </Popover>

            </Fragment>
        );
    }

}

PopoverProvider.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * The CSS class name of the content element.
     */
    contentClassName: PropTypes.string,

    modalClassName: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * This is the DOM element that will be used to set the position of the popover.
     */
    triggerEl: PropTypes.object,

    /**
     * If true,the popover is visible.
     */
    visible: PropTypes.bool,

    /**
     * If true,the popover will have a triangle on the top of the DOM element.
     */
    hasTriangle: PropTypes.bool,

    triangle: PropTypes.element,

    /**
     * The popover theme.Can be primary,highlight,success,warning,error.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The popover alignment.The value can be Popover.Position.LEFT or Popover.Position.RIGHT.
     */
    position: PropTypes.oneOf(Util.enumerateValue(Position)),

    /**
     * If true, popover will have animation effects.
     */
    isAnimated: PropTypes.bool,

    /**
     * The depth of Paper component.
     */
    depth: PropTypes.number,

    isBlurClose: PropTypes.bool,
    shouldPreventContainerScroll: PropTypes.bool,
    isTriggerPositionFixed: PropTypes.bool,
    resetPositionWait: PropTypes.number,
    showModal: PropTypes.bool,

    /**
     * The function of popover render.
     */
    onRender: PropTypes.func,

    /**
     * The function of popover rendered.
     */
    onRendered: PropTypes.func,

    /**
     * The function of popover destroy.
     */
    onDestroy: PropTypes.func,

    /**
     * The function of popover destroyed.
     */
    onDestroyed: PropTypes.func,

    /**
     * Callback function fired when the popover is requested to be closed.
     */
    onRequestClose: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func,

    popoverContent: PropTypes.any

};

PopoverProvider.defaultProps = {

    visible: false,
    hasTriangle: true,
    theme: Theme.DEFAULT,
    position: Position.BOTTOM,
    isAnimated: true,

    isBlurClose: true,
    shouldPreventContainerScroll: true,
    isTriggerPositionFixed: false,
    resetPositionWait: 250,
    showModal: false

};

export default PopoverProvider;
<|endoftext|>


<commit_before>
// @require core/svelto/svelto.js

//URL: https://github.com/developit/unfetch

//TODO: Add a demo for it

(function ( $, _, Svelto ) {

  /* DEFAULTS */

  let defaults = {
    // url: 'https://example.com',
    method: 'get',
    methodCacheableRe: /^(get|head)$/i,
    methodBodyableRe: /^(?!get|head)$/i,
    // body: {},
    cache: true,
    credentials: 'include', // Include cookies
    headers: {
      'X-Requested-With': 'XMLHttpRequest'
    },
    timeout: 0,
    notOKisError: true,
    request: () => new XMLHttpRequest (),
    beforesend: _.noop,
    error: _.noop,
    success: _.noop,
    complete: _.noop
  };

  /* FETCH */

  function fetch ( url, options ) {

    if ( !options && _.isPlainObject ( url ) ) {
      options = url;
      url = options.url;
    }

    options = _.merge ( {}, fetch.defaults, options );

    let isMethodBodyable = options.methodBodyableRe.test ( options.method );

    if ( options.body && !isMethodBodyable ) {
      options.method = 'post';
    }

    let isMethodCacheable = options.methodCacheableRe.test ( options.method );

    if ( options.cache && isMethodCacheable && fetch.cache[url] ) {
      let response = fetch.cache[url];
      response.then ( res => callbacksOnBeforeSend ( options, res.request ) );
      response.then ( res => callbacksOnLoad ( options, res ) );
      return response;
    }

    if ( options.batchUrl ) return fetch.batch ( url, options.batchUrl, options );

    let response = new Promise ( ( resolve, reject ) => {

      let request = options.request ();

      if ( !options.cache && isMethodCacheable ) {
        url += ( url.includes ( '?' ) ? '&' : '?' ) + `anticache=${new Date ().getTime ()}`;
      }

      request.open ( options.method, url, true );
      request.timeout = options.timeout;
      request.withCredentials = ( options.credentials === 'include' );

      if ( _.isPlainObject ( options.body ) ) {

        _.extend ( options.headers, {
          'Content-Type': 'application/json'
        });

        options.body = JSON.stringify ( options.body );

      }

      _.forOwn ( options.headers, ( val, key ) => {
        request.setRequestHeader ( key, val );
      });

      request.onload = () => {
        let response = fetch.request2response ( request );
        callbacksOnLoad ( options, response );
        resolve ( response );
      };

      request.onerror = () => {
        let response = fetch.request2response ( request );
        callbacksOnError ( options, response );
        reject ( response );
      };

      callbacksOnBeforeSend ( options, request );

      request.send ( options.body );

    });

    if ( options.cache && isMethodCacheable ) fetch.cache[url] = response;

    return response;

  }

  /* BATCH */

  async function batch ( url, batchUrl, options ) {

    options = _.omit ( options, ['url', 'batchUrl'] );

    const responses = await ( await fetch ( batchUrl, options ) ).json ();

    _.forOwn ( responses, ( response, url ) => {

      const responseText = _.isString ( response ) ? response : JSON.stringify ( response );
      const request = {
        __fake: true,
        getAllResponseHeaders: () => '',
        status: 200,
        statusText: 'OK',
        responseURL: url,
        response: responseText,
        responseText
      };

      fetch.cache[url] = Promise.resolve ( fetch.request2response ( request ) );

    });

    return fetch.cache[url] || fetch ( url, options );

  }

  /* REQUEST 2 RESPONSE */

  function request2response ( request ) {

    let headers = {},
        headersKeys = [],
        headersEntries = [];

    request.getAllResponseHeaders ().replace ( /^(.*?):\s*([\s\S]*?)$/gm, ( match, key, value ) => {
      key = key.toLowerCase ();
      headersKeys.push ( key );
      headersEntries.push ([ key, value ]);
      let prevValue = headers[key];
      headers[key] = prevValue ? `${prevValue},${value}` : value;
    });

    return {
      request,
      ok: ( request.status / 200 | 0 ) === 1, // 200-299
      status: request.status,
      statusText: request.statusText,
      url: request.responseURL,
      clone: () => fetch.request2response ( request ),
      text: () => Promise.resolve ( request.responseText ),
      json: () => Promise.resolve ( JSON.parse ( request.responseText ) ),
      blob: () => Promise.resolve ( new Blob ([ request.response ]) ),
      headers: {
        keys: () => headersKeys,
        entries: () => headersEntries,
        get: key => headers[key.toLowerCase ()],
        has: key => key.toLowerCase () in headers
      }
    };

  }

  /* CALLBACKS */

  function callbacksOnLoad ( options, res ) {
    if ( options.notOKisError && !res.ok ) {
      options.error ( res );
    } else {
      options.success ( res );
    }
    options.complete ( res );
  }

  function callbacksOnError ( options, res ) {
    options.error ( res );
    options.complete ( res );
  }

  function callbacksOnBeforeSend ( options, req ) {
    options.beforesend ( req );
  }

  /* BINDING */

  fetch.batch = batch;
  fetch.defaults = defaults;
  fetch.cache = {};
  fetch.request2response = request2response;

  /* EXPORT */

  Svelto.fetch = fetch;

}( Svelto.$, Svelto._, Svelto ));
<commit_msg>Avoiding using `_.forOwn`<commit_after>
// @require core/svelto/svelto.js

//URL: https://github.com/developit/unfetch

//TODO: Add a demo for it

(function ( $, _, Svelto ) {

  /* DEFAULTS */

  let defaults = {
    // url: 'https://example.com',
    method: 'get',
    methodCacheableRe: /^(get|head)$/i,
    methodBodyableRe: /^(?!get|head)$/i,
    // body: {},
    cache: true,
    credentials: 'include', // Include cookies
    headers: {
      'X-Requested-With': 'XMLHttpRequest'
    },
    timeout: 0,
    notOKisError: true,
    request: () => new XMLHttpRequest (),
    beforesend: _.noop,
    error: _.noop,
    success: _.noop,
    complete: _.noop
  };

  /* FETCH */

  function fetch ( url, options ) {

    if ( !options && _.isPlainObject ( url ) ) {
      options = url;
      url = options.url;
    }

    options = _.merge ( {}, fetch.defaults, options );

    let isMethodBodyable = options.methodBodyableRe.test ( options.method );

    if ( options.body && !isMethodBodyable ) {
      options.method = 'post';
    }

    let isMethodCacheable = options.methodCacheableRe.test ( options.method );

    if ( options.cache && isMethodCacheable && fetch.cache[url] ) {
      let response = fetch.cache[url];
      response.then ( res => callbacksOnBeforeSend ( options, res.request ) );
      response.then ( res => callbacksOnLoad ( options, res ) );
      return response;
    }

    if ( options.batchUrl ) return fetch.batch ( url, options.batchUrl, options );

    let response = new Promise ( ( resolve, reject ) => {

      let request = options.request ();

      if ( !options.cache && isMethodCacheable ) {
        url += ( url.includes ( '?' ) ? '&' : '?' ) + `anticache=${new Date ().getTime ()}`;
      }

      request.open ( options.method, url, true );
      request.timeout = options.timeout;
      request.withCredentials = ( options.credentials === 'include' );

      if ( _.isPlainObject ( options.body ) ) {

        _.extend ( options.headers, {
          'Content-Type': 'application/json'
        });

        options.body = JSON.stringify ( options.body );

      }

      for ( let key in options.headers ) {
        if ( !options.headers.hasOwnProperty ( key ) ) continue;
        request.setRequestHeader ( key, options.headers[key] );
      }

      request.onload = () => {
        let response = fetch.request2response ( request );
        callbacksOnLoad ( options, response );
        resolve ( response );
      };

      request.onerror = () => {
        let response = fetch.request2response ( request );
        callbacksOnError ( options, response );
        reject ( response );
      };

      callbacksOnBeforeSend ( options, request );

      request.send ( options.body );

    });

    if ( options.cache && isMethodCacheable ) fetch.cache[url] = response;

    return response;

  }

  /* BATCH */

  async function batch ( url, batchUrl, options ) {

    options = _.omit ( options, ['url', 'batchUrl'] );

    const responses = await ( await fetch ( batchUrl, options ) ).json ();

    for ( let url in responses ) {

      if ( !responses.hasOwnProperty ( url ) ) continue;

      const response = responses[url],
            responseText = _.isString ( response ) ? response : JSON.stringify ( response );

      const request = {
        __fake: true,
        getAllResponseHeaders: () => '',
        status: 200,
        statusText: 'OK',
        responseURL: url,
        response: responseText,
        responseText
      };

      fetch.cache[url] = Promise.resolve ( fetch.request2response ( request ) );

    }

    return fetch.cache[url] || fetch ( url, options );

  }

  /* REQUEST 2 RESPONSE */

  function request2response ( request ) {

    let headers = {},
        headersKeys = [],
        headersEntries = [];

    request.getAllResponseHeaders ().replace ( /^(.*?):\s*([\s\S]*?)$/gm, ( match, key, value ) => {
      key = key.toLowerCase ();
      headersKeys.push ( key );
      headersEntries.push ([ key, value ]);
      let prevValue = headers[key];
      headers[key] = prevValue ? `${prevValue},${value}` : value;
    });

    return {
      request,
      ok: ( request.status / 200 | 0 ) === 1, // 200-299
      status: request.status,
      statusText: request.statusText,
      url: request.responseURL,
      clone: () => fetch.request2response ( request ),
      text: () => Promise.resolve ( request.responseText ),
      json: () => Promise.resolve ( JSON.parse ( request.responseText ) ),
      blob: () => Promise.resolve ( new Blob ([ request.response ]) ),
      headers: {
        keys: () => headersKeys,
        entries: () => headersEntries,
        get: key => headers[key.toLowerCase ()],
        has: key => key.toLowerCase () in headers
      }
    };

  }

  /* CALLBACKS */

  function callbacksOnLoad ( options, res ) {
    if ( options.notOKisError && !res.ok ) {
      options.error ( res );
    } else {
      options.success ( res );
    }
    options.complete ( res );
  }

  function callbacksOnError ( options, res ) {
    options.error ( res );
    options.complete ( res );
  }

  function callbacksOnBeforeSend ( options, req ) {
    options.beforesend ( req );
  }

  /* BINDING */

  fetch.batch = batch;
  fetch.defaults = defaults;
  fetch.cache = {};
  fetch.request2response = request2response;

  /* EXPORT */

  Svelto.fetch = fetch;

}( Svelto.$, Svelto._, Svelto ));
<|endoftext|>


<commit_before>app.service('psEncoder', function () {

    //eto ebuchaja kopija govnokoda iz propertySeta JDB
    this.encode = function (xmlString) {
        if (!xmlString)
            return "";
        var xml = $($.parseXML(xmlString.replace(/(\r\n|\n|\r)/gm,""))).children()[0];
        var write = function (value) {
            r += value + "*";
        }

        var writePs = function (target) {

            write(target.attributes.length);
            write(target.children.length);
            writeString(target.nodeName);
			var textValue = _.find(target.childNodes, function(node) { return node.data; });
            writeValue(textValue && textValue.data);

            for (var i = 0; i < target.attributes.length; i++) {
                writeString(target.attributes[i].name);
                writeString(target.attributes[i].value);
            }

            for (var i = 0; i < target.children.length; i++) {
                writePs(target.children[i]);
            }
        }

        var writeString = function (value) {
            var v = value || "";
            write(v.length);
            r += v;
        }

        var writeValue = function (value) { //meaning ps <Value>, tut eshe dolzhna bytj realizacija base64, no mne lenj
            write(3);
            writeString(value);
        }

        var r = "@";
        //header
        write(0);
        write(0);

        //ps
        writePs(xml);

        return r;
    }
});<commit_msg>added encoding<commit_after>app.service('psEncoder', function () {

	this.encode = function (xmlString) {
		if (!xmlString)
			return "";

		var xml = $($.parseXML(this.escape(xmlString))).children()[0];
		var write = function (value) {
			r += value + "*";
		}

		var writePs = function (target) {

			write(target.attributes.length);
			write(target.children.length);
			writeString(target.nodeName);
			var textValue = _.find(target.childNodes, function (node) {
				return node.data;
			});
			writeValue(textValue && textValue.data);

			for (var i = 0; i < target.attributes.length; i++) {
				writeString(target.attributes[i].name);
				writeString(target.attributes[i].value);
			}

			for (var i = 0; i < target.children.length; i++) {
				writePs(target.children[i]);
			}
		}

		var writeString = function (value) {
			var v = value || "";
			write(v.length);
			r += v;
		}

		var writeValue = function (value) { //meaning ps <Value>, tut eshe dolzhna bytj realizacija base64, no mne lenj
			write(3);
			writeString(value);
		}

		var r = "@";
		//header
		write(0);
		write(0);

		//ps
		writePs(xml);

		return r;
	};

	this.escape = function (str) {
		try {
			str = str.replace(/(\r\n|\n|\r)/gm," ")
			//find all attributes
			var attrs = [];
			var processed = "";
			var match;
			var re = /=\".*\"/igm;

			while ((match = re.exec(str)) != null) {
				attrs.push({
					index: match.index,
					value: match[0]
				});
			}

			var pos = 0;

			//replace ampersant with "
			_.each(attrs, function (attr) {
				processed += str.substr(pos, attr.index - pos);
				processed += attr.value.replace(/\&(?!amp;)/gim, "&");
				pos = attr.index + attr.value.length;
			});

			processed += str.substr(pos);

		} catch (e) {
			throw e;
		}

		return processed;
	}
});<|endoftext|>