<commit_before>import path from 'path';
import fs from 'fs';
import { promisify, promisifyAll, fromNode as promiseFromNode } from 'bluebird';
import nunjucks from 'nunjucks';
import permalinks from 'permalinks';
import MkMeta from 'marked-metadata';
import mkdirp from 'mkdirp';
import { ncp } from 'ncp';
import rimraf from 'rimraf';
import stylus from 'stylus';
import less from 'less';
import { rootdir, postspath, pagespath } from './config';
import { cliColor, getConfig } from './helpers';
import Theme from './theme';
promisifyAll(fs);
const mkdirpAsync = promisify(mkdirp);
const ncpAsync = promisify(ncp);
const rimrafAsync = promisify(rimraf);

const clc = cliColor();
const rMarkdownExt = /\.(?:md|markdown)$/;

const Helper = {

    sort(a, b) {
        return new Date(b.date) - new Date(a.date);
    },

    sortPosts: function(posts) {
        Object.values(posts).forEach((postsArray) => postsArray.sort(Helper.sort));
        return posts;
    },

    normalizeMetaData(data) {
        data.title = data.title.replace(/\"/g, '');
        return data;
    },

    normalizeContent(data) {
        return data;
    }
};

export default class Harmonic {
    /*eslint-disable camelcase*/

    constructor(sitePath, { quiet = true } = {}) {
        this.sitePath = path.resolve(sitePath);
        this.quiet = !!quiet;

        const config = Object.assign({
            index_posts: 10
        }, getConfig(this.sitePath));
        this.theme = new Theme(config.theme, this.sitePath);

        if (fs.existsSync(path.join(this.theme.themePath, 'config.json'))) {
            Object.assign(config, JSON.parse(this.theme.getFileContents('config.json')));
        }

        this.config = config;
        this.nunjucksEnv = nunjucks.configure(this.theme.themePath, { watch: false });
    }

    async clean() {
        console.log(clc.warn('Cleaning up...'));
        await rimrafAsync(path.join(this.sitePath, 'public'), { maxBusyTries: 20 });
    }

    async compileCSS() {
        const currentCSSCompiler = this.config.preprocessor;
        if (!currentCSSCompiler) {
            return;
        }

        const compiler = {

            less: async () => {
                const lessIndexPath = path.join(this.theme.themePath, 'resources/_less/index.less');
                const cssDir = path.join(this.sitePath, 'public/css');

                const lessInput = await fs.readFileAsync(lessIndexPath, { encoding: 'utf8' });
                const { css } = await less.render(lessInput, { filename: lessIndexPath });

                await mkdirpAsync(cssDir);
                await fs.writeFileAsync(path.join(cssDir, 'main.css'), css);

                console.log(clc.info('Successfully generated CSS with LESS preprocessor'));
            },

            stylus: async () => {
                const stylDir = path.join(this.theme.themePath, 'resources/_stylus');
                const stylIndexPath = path.join(stylDir, 'index.styl');
                const cssDir = path.join(this.sitePath, 'public/css');

                const stylInput = await fs.readFileAsync(stylIndexPath, { encoding: 'utf8' });
                const css = await promiseFromNode((cb) => {
                    stylus(stylInput)
                        .set('filename', stylIndexPath)
                        .set('paths', [path.join(stylDir, 'engine'), path.join(stylDir, 'partials')])
                        .render(cb);
                });

                await mkdirpAsync(cssDir);
                await fs.writeFileAsync(path.join(cssDir, 'main.css'), css);

                console.log(clc.info('Successfully generated CSS with Stylus preprocessor'));
            }
        };

        if (!compiler.hasOwnProperty(currentCSSCompiler)) {
            throw new Error(`Unsupported CSS preprocessor: ${currentCSSCompiler}`);
        }

        await compiler[currentCSSCompiler]();
    }

    async compileJS(postsMetadata, pagesMetadata) {
        const harmonicClient = (await fs.readFileAsync(`${rootdir}/bin/client/harmonic-client.js`, { encoding: 'utf8' }))
            .replace(/__HARMONIC\.POSTS__/g, JSON.stringify(postsMetadata))
            .replace(/__HARMONIC\.PAGES__/g, JSON.stringify(pagesMetadata))
            .replace(/__HARMONIC\.CONFIG__/g, JSON.stringify(this.config));

        await fs.writeFileAsync(path.join(this.sitePath, 'public/harmonic.js'), harmonicClient);
    }

    async generateTagsPages(postsMetadata) {
        const tagTemplateNJ = nunjucks.compile(this.theme.getFileContents('index.html'), this.nunjucksEnv);
        const config = this.config;

        await* [].concat(...Object.entries(postsMetadata).map(([lang, langPosts]) => {
            const postsByTag = {};
            langPosts.forEach((post) => {
                post.categories.forEach((category) => {
                    // TODO replace with kebabCase?
                    const tag = category.toLowerCase().trim().split(' ').join('-');
                    postsByTag[tag] = postsByTag[tag] || [];
                    postsByTag[tag].push(post);
                });
            });

            return Object.entries(postsByTag).map(async ([tag, tagPosts]) => {
                const tagContent = tagTemplateNJ.render({
                    posts: tagPosts,
                    config,
                    category: tag
                });

                const tagPath = path.join(this.sitePath, 'public/categories', ...(config.i18n.default === lang ? [] : [lang]), tag);
                await mkdirpAsync(tagPath);
                await fs.writeFileAsync(path.join(tagPath, 'index.html'), tagContent);
                console.log(clc.info(`Successfully generated tag[${tag}] archive html file`));
            });
        }));
    }

    async generateIndex(postsMetadata, pagesMetadata) {
        const indexTemplateNJ = nunjucks.compile(this.theme.getFileContents('index.html'), this.nunjucksEnv);
        const config = this.config;

        await* Object.entries(postsMetadata).map(async ([lang, langPosts]) => {
            const posts = langPosts.slice(0, config.index_posts);
            const pages = pagesMetadata[lang] || [];

            const indexContent = indexTemplateNJ.render({
                posts,
                config,
                pages
            });

            const indexPath = path.join(this.sitePath, 'public', ...(config.i18n.default === lang ? [] : [lang]));
            await mkdirpAsync(indexPath);
            await fs.writeFileAsync(path.join(indexPath, 'index.html'), indexContent);
            console.log(clc.info(`${lang}/index file successfully created`));
        });
    }

    async copyThemeResources() {
        await ncpAsync(path.join(this.theme.themePath, 'resources'), path.join(this.sitePath, 'public'), { stopOnErr: true });
        console.log(clc.info('Theme resources copied'));
    }

    async copyUserResources() {
        const userResourcesPath = path.join(this.sitePath, 'resources');
        await mkdirpAsync(userResourcesPath);
        await ncpAsync(userResourcesPath, path.join(this.sitePath, 'public'), { stopOnErr: true });
        console.log(clc.info(`User resources copied`));
    }

    async generatePosts(files) {
        const langs = Object.keys(files);
        const config = this.config;
        const posts = {};
        const currentDate = new Date();
        const nunjucksEnv = this.nunjucksEnv;
        const postsTemplate = this.theme.getFileContents('post.html');
        const postsTemplateNJ = nunjucks.compile(postsTemplate, nunjucksEnv);
        const tokens = [
            config.header_tokens ? config.header_tokens[0] : '<!--',
            config.header_tokens ? config.header_tokens[1] : '-->'
        ];

        await* [].concat(...langs.map((lang) => files[lang].map(async (file) => {
            // TODO this can most likely do with some refactoring and code style adjustments
            let metadata, post, postCropped, filename, checkDate, postPath, categories,
                _post, postHTMLFile, postDate, month, year, options,
                md = new MkMeta(path.join(this.sitePath, postspath, lang, file));

            md.defineTokens(tokens[0], tokens[1]);
            metadata = Helper.normalizeMetaData(md.metadata());
            post = Helper.normalizeContent(md.markdown());
            postCropped = md.markdown({
                crop: '<!--more-->'
            });

            filename = path.extname(file) === '.md' ?
                path.basename(file, '.md') :
                path.basename(file, '.markdown');

            checkDate = new Date(filename.substr(0, 10));

            filename = isNaN(checkDate.getDate()) ?
                filename :
                filename.substr(11, filename.length);

            postPath = null;
            categories = metadata.categories.split(',');
            postDate = new Date(metadata.date);
            year = postDate.getFullYear();
            month = ('0' + (postDate.getMonth() + 1)).slice(-2);

            // If is the default language, generate in the root path
            options = {
                replacements: [{
                    pattern: ':year',
                    replacement: year
                },
                {
                    pattern: ':month',
                    replacement: month
                },
                {
                    pattern: ':title',
                    replacement: filename
                },
                {
                    pattern: ':language',
                    replacement: lang
                }]
            };
            if (config.i18n.default === lang && config.posts_permalink.match(':language')) {
                options.structure = config.posts_permalink.split(':language/')[1];
                postPath = permalinks(options);
            } else {
                options.structure = config.posts_permalink;
                postPath = permalinks(options);
            }

            metadata.categories = categories;
            metadata.content = postCropped;
            metadata.file = postspath + file;
            metadata.filename = filename;
            metadata.link = postPath;
            metadata.lang = lang;
            metadata.default_lang = config.i18n.default === lang ? false : true;
            metadata.date = new Date(metadata.date);

            _post = {
                content: post,
                metadata: metadata
            };

            postHTMLFile = postsTemplateNJ
                .render({
                    post: _post,
                    config: config
                })
                .replace(/<!--[\s\S]*?-->/g, '');

            if (metadata.published && metadata.published === 'false') {
                return;
            }

            if (metadata.date && metadata.date > currentDate) {
                console.log(clc.info(`Skipping future post ${metadata.filename}`));
                return;
            }

            await mkdirpAsync(path.join(this.sitePath, 'public', postPath));

            // write post html file
            await fs.writeFileAsync(path.join(this.sitePath, 'public', postPath, 'index.html'), postHTMLFile);
            console.log(clc.info('Successfully generated post ' + postPath));

            posts[lang] = posts[lang] || [];
            posts[lang].push(metadata);
        })));
        return Helper.sortPosts(posts);
    }

    async generatePages(files) {
        const langs = Object.keys(files);
        const config = this.config;
        const tokens = [
            config.header_tokens ? config.header_tokens[0] : '<!--',
            config.header_tokens ? config.header_tokens[1] : '-->'
        ];
        const pages = {};

        await* [].concat(...langs.map((lang) => files[lang].map(async (file) => {
            // TODO this can most likely do with some refactoring and code style adjustments
            let metadata, pagePermalink, _page, pageHTMLFile, options,
                pagePath = path.join(this.sitePath, pagespath, lang, file),
                pageTpl = this.theme.getFileContents('page.html'),
                pageTplNJ = nunjucks.compile(pageTpl, this.nunjucksEnv),
                md = new MkMeta(pagePath),
                pageSrc = '',
                filename = path.extname(file) === '.md' ?
                    path.basename(file, '.md') :
                    path.basename(file, '.markdown');

            md.defineTokens(tokens[0], tokens[1]);

            // Markdown extra
            metadata = md.metadata();

            options = {
                replacements: [{
                    pattern: ':title',
                    replacement: filename
                },
                {
                    pattern: ':language',
                    replacement: lang
                }]
            };
            if (config.i18n.default === lang) {
                options.structure = config.pages_permalink.split(':language/')[1];
                pagePermalink = permalinks(options);
            } else {
                options.structure = config.pages_permalink;
                pagePermalink = permalinks(options);
            }

            _page = {
                content: md.markdown(),
                metadata: metadata
            };

            pageHTMLFile = pageTplNJ.render({
                page: _page,
                config: config
            });

            // Removing header metadata
            pageHTMLFile = pageHTMLFile.replace(/<!--[\s\S]*?-->/g, '');

            metadata.content = pageHTMLFile;
            metadata.file = postspath + file; // TODO check whether this needs sitePath
            metadata.filename = filename;
            metadata.link = pagePermalink;
            metadata.lang = lang;
            metadata.date = new Date(metadata.date);
            pageSrc = path.join(this.sitePath, 'public', pagePermalink, 'index.html');

            pages[lang] = pages[lang] || [];
            pages[lang].push(metadata);

            await mkdirpAsync(path.join(this.sitePath, 'public', pagePermalink));

            // write page html file
            await fs.writeFileAsync(pageSrc, pageHTMLFile);
            console.log(clc.info(`Successfully generated page ${pagePermalink}`));
        })));
        return pages;
    }

    async getPostFiles() {
        const files = {};

        await* this.config.i18n.languages.map(async (lang) => {
            files[lang] = (await fs.readdirAsync(path.join(this.sitePath, postspath, lang)))
                .filter((p) => rMarkdownExt.test(p));
        });

        return files;
    }

    async getPageFiles() {
        const files = {};

        await* this.config.i18n.languages.map(async (lang) => {
            const langPath = path.join(this.sitePath, pagespath, lang);
            await mkdirpAsync(langPath);
            files[lang] = (await fs.readdirAsync(langPath)).filter((p) => rMarkdownExt.test(p));
        });

        return files;
    }

    async generateRSS(postsMetadata, pagesMetadata) {
        const rssTemplate = await fs.readFileAsync(path.join(__dirname, 'resources/rss.xml'), { encoding: 'utf8' });
        const rssTemplateNJ = nunjucks.compile(rssTemplate, this.nunjucksEnv);
        const config = this.config;
        const rssAuthor = config.author_email ? `${config.author_email} ( ${config.author} )` : config.author;

        await* Object.entries(postsMetadata).map(async ([lang, langPosts]) => {
            const posts = langPosts.slice(0, config.index_posts);
            const isDefaultLang = config.i18n.default === lang;
            const rssPath = path.join(this.sitePath, 'public', ...(isDefaultLang ? [] : [lang]));
            const rssLink = `${config.domain}${isDefaultLang ? '' : '/' + lang}/rss.xml`;

            const rssContent = rssTemplateNJ.render({
                rss: {
                    date: new Date().toUTCString(),
                    link: rssLink,
                    author: rssAuthor,
                    lang: lang
                },
                posts,
                config,
                pages: pagesMetadata
            });

            await mkdirpAsync(rssPath);
            await fs.writeFileAsync(`${rssPath}/rss.xml`, rssContent);
            console.log(clc.info(`${lang}/rss.xml file successfully created`));
        });
    }
}
<commit_msg>make `generatePages` more alike `generatePosts` in order to facilitate future refactorings<commit_after>import path from 'path';
import fs from 'fs';
import { promisify, promisifyAll, fromNode as promiseFromNode } from 'bluebird';
import nunjucks from 'nunjucks';
import permalinks from 'permalinks';
import MkMeta from 'marked-metadata';
import mkdirp from 'mkdirp';
import { ncp } from 'ncp';
import rimraf from 'rimraf';
import stylus from 'stylus';
import less from 'less';
import { rootdir, postspath, pagespath } from './config';
import { cliColor, getConfig } from './helpers';
import Theme from './theme';
promisifyAll(fs);
const mkdirpAsync = promisify(mkdirp);
const ncpAsync = promisify(ncp);
const rimrafAsync = promisify(rimraf);

const clc = cliColor();
const rMarkdownExt = /\.(?:md|markdown)$/;

const Helper = {

    sort(a, b) {
        return new Date(b.date) - new Date(a.date);
    },

    sortPosts: function(posts) {
        Object.values(posts).forEach((postsArray) => postsArray.sort(Helper.sort));
        return posts;
    },

    normalizeMetaData(data) {
        data.title = data.title.replace(/\"/g, '');
        return data;
    },

    normalizeContent(data) {
        return data;
    }
};

export default class Harmonic {
    /*eslint-disable camelcase*/

    constructor(sitePath, { quiet = true } = {}) {
        this.sitePath = path.resolve(sitePath);
        this.quiet = !!quiet;

        const config = Object.assign({
            index_posts: 10
        }, getConfig(this.sitePath));
        this.theme = new Theme(config.theme, this.sitePath);

        if (fs.existsSync(path.join(this.theme.themePath, 'config.json'))) {
            Object.assign(config, JSON.parse(this.theme.getFileContents('config.json')));
        }

        this.config = config;
        this.nunjucksEnv = nunjucks.configure(this.theme.themePath, { watch: false });
    }

    async clean() {
        console.log(clc.warn('Cleaning up...'));
        await rimrafAsync(path.join(this.sitePath, 'public'), { maxBusyTries: 20 });
    }

    async compileCSS() {
        const currentCSSCompiler = this.config.preprocessor;
        if (!currentCSSCompiler) {
            return;
        }

        const compiler = {

            less: async () => {
                const lessIndexPath = path.join(this.theme.themePath, 'resources/_less/index.less');
                const cssDir = path.join(this.sitePath, 'public/css');

                const lessInput = await fs.readFileAsync(lessIndexPath, { encoding: 'utf8' });
                const { css } = await less.render(lessInput, { filename: lessIndexPath });

                await mkdirpAsync(cssDir);
                await fs.writeFileAsync(path.join(cssDir, 'main.css'), css);

                console.log(clc.info('Successfully generated CSS with LESS preprocessor'));
            },

            stylus: async () => {
                const stylDir = path.join(this.theme.themePath, 'resources/_stylus');
                const stylIndexPath = path.join(stylDir, 'index.styl');
                const cssDir = path.join(this.sitePath, 'public/css');

                const stylInput = await fs.readFileAsync(stylIndexPath, { encoding: 'utf8' });
                const css = await promiseFromNode((cb) => {
                    stylus(stylInput)
                        .set('filename', stylIndexPath)
                        .set('paths', [path.join(stylDir, 'engine'), path.join(stylDir, 'partials')])
                        .render(cb);
                });

                await mkdirpAsync(cssDir);
                await fs.writeFileAsync(path.join(cssDir, 'main.css'), css);

                console.log(clc.info('Successfully generated CSS with Stylus preprocessor'));
            }
        };

        if (!compiler.hasOwnProperty(currentCSSCompiler)) {
            throw new Error(`Unsupported CSS preprocessor: ${currentCSSCompiler}`);
        }

        await compiler[currentCSSCompiler]();
    }

    async compileJS(postsMetadata, pagesMetadata) {
        const harmonicClient = (await fs.readFileAsync(`${rootdir}/bin/client/harmonic-client.js`, { encoding: 'utf8' }))
            .replace(/__HARMONIC\.POSTS__/g, JSON.stringify(postsMetadata))
            .replace(/__HARMONIC\.PAGES__/g, JSON.stringify(pagesMetadata))
            .replace(/__HARMONIC\.CONFIG__/g, JSON.stringify(this.config));

        await fs.writeFileAsync(path.join(this.sitePath, 'public/harmonic.js'), harmonicClient);
    }

    async generateTagsPages(postsMetadata) {
        const tagTemplateNJ = nunjucks.compile(this.theme.getFileContents('index.html'), this.nunjucksEnv);
        const config = this.config;

        await* [].concat(...Object.entries(postsMetadata).map(([lang, langPosts]) => {
            const postsByTag = {};
            langPosts.forEach((post) => {
                post.categories.forEach((category) => {
                    // TODO replace with kebabCase?
                    const tag = category.toLowerCase().trim().split(' ').join('-');
                    postsByTag[tag] = postsByTag[tag] || [];
                    postsByTag[tag].push(post);
                });
            });

            return Object.entries(postsByTag).map(async ([tag, tagPosts]) => {
                const tagContent = tagTemplateNJ.render({
                    posts: tagPosts,
                    config,
                    category: tag
                });

                const tagPath = path.join(this.sitePath, 'public/categories', ...(config.i18n.default === lang ? [] : [lang]), tag);
                await mkdirpAsync(tagPath);
                await fs.writeFileAsync(path.join(tagPath, 'index.html'), tagContent);
                console.log(clc.info(`Successfully generated tag[${tag}] archive html file`));
            });
        }));
    }

    async generateIndex(postsMetadata, pagesMetadata) {
        const indexTemplateNJ = nunjucks.compile(this.theme.getFileContents('index.html'), this.nunjucksEnv);
        const config = this.config;

        await* Object.entries(postsMetadata).map(async ([lang, langPosts]) => {
            const posts = langPosts.slice(0, config.index_posts);
            const pages = pagesMetadata[lang] || [];

            const indexContent = indexTemplateNJ.render({
                posts,
                pages,
                config
            });

            const indexPath = path.join(this.sitePath, 'public', ...(config.i18n.default === lang ? [] : [lang]));
            await mkdirpAsync(indexPath);
            await fs.writeFileAsync(path.join(indexPath, 'index.html'), indexContent);
            console.log(clc.info(`${lang}/index file successfully created`));
        });
    }

    async copyThemeResources() {
        await ncpAsync(path.join(this.theme.themePath, 'resources'), path.join(this.sitePath, 'public'), { stopOnErr: true });
        console.log(clc.info('Theme resources copied'));
    }

    async copyUserResources() {
        const userResourcesPath = path.join(this.sitePath, 'resources');
        await mkdirpAsync(userResourcesPath);
        await ncpAsync(userResourcesPath, path.join(this.sitePath, 'public'), { stopOnErr: true });
        console.log(clc.info(`User resources copied`));
    }

    async generatePosts(files) {
        const langs = Object.keys(files);
        const config = this.config;
        const posts = {};
        const currentDate = new Date();
        const nunjucksEnv = this.nunjucksEnv;
        const postsTemplate = this.theme.getFileContents('post.html');
        const postsTemplateNJ = nunjucks.compile(postsTemplate, nunjucksEnv);
        const tokens = [
            config.header_tokens ? config.header_tokens[0] : '<!--',
            config.header_tokens ? config.header_tokens[1] : '-->'
        ];

        await* [].concat(...langs.map((lang) => files[lang].map(async (file) => {
            // TODO this can most likely do with some refactoring and code style adjustments
            let metadata, post, postCropped, filename, checkDate, postPath, categories,
                _post, postHTMLFile, postDate, month, year, options,
                md = new MkMeta(path.join(this.sitePath, postspath, lang, file));

            md.defineTokens(tokens[0], tokens[1]);
            metadata = Helper.normalizeMetaData(md.metadata());
            post = Helper.normalizeContent(md.markdown());
            postCropped = md.markdown({
                crop: '<!--more-->'
            });

            filename = path.extname(file) === '.md' ?
                path.basename(file, '.md') :
                path.basename(file, '.markdown');

            checkDate = new Date(filename.substr(0, 10));

            filename = isNaN(checkDate.getDate()) ?
                filename :
                filename.substr(11, filename.length);

            postPath = null;
            categories = metadata.categories.split(',');
            postDate = new Date(metadata.date);
            year = postDate.getFullYear();
            month = ('0' + (postDate.getMonth() + 1)).slice(-2);

            // If is the default language, generate in the root path
            options = {
                replacements: [{
                    pattern: ':year',
                    replacement: year
                },
                {
                    pattern: ':month',
                    replacement: month
                },
                {
                    pattern: ':title',
                    replacement: filename
                },
                {
                    pattern: ':language',
                    replacement: lang
                }]
            };
            if (config.i18n.default === lang && config.posts_permalink.match(':language')) {
                options.structure = config.posts_permalink.split(':language/')[1];
                postPath = permalinks(options);
            } else {
                options.structure = config.posts_permalink;
                postPath = permalinks(options);
            }

            metadata.categories = categories;
            metadata.content = postCropped;
            metadata.file = postspath + file;
            metadata.filename = filename;
            metadata.link = postPath;
            metadata.lang = lang;
            metadata.default_lang = config.i18n.default === lang ? false : true;
            metadata.date = new Date(metadata.date);

            _post = {
                content: post,
                metadata: metadata
            };

            postHTMLFile = postsTemplateNJ
                .render({
                    post: _post,
                    config: config
                })
                .replace(/<!--[\s\S]*?-->/g, '');

            if (metadata.published && metadata.published === 'false') {
                return;
            }

            if (metadata.date && metadata.date > currentDate) {
                console.log(clc.info(`Skipping future post ${metadata.filename}`));
                return;
            }

            await mkdirpAsync(path.join(this.sitePath, 'public', postPath));

            // write post html file
            await fs.writeFileAsync(path.join(this.sitePath, 'public', postPath, 'index.html'), postHTMLFile);
            console.log(clc.info('Successfully generated post ' + postPath));

            posts[lang] = posts[lang] || [];
            posts[lang].push(metadata);
        })));
        return Helper.sortPosts(posts);
    }

    async generatePages(files) {
        const langs = Object.keys(files);
        const config = this.config;
        const pages = {};
        const tokens = [
            config.header_tokens ? config.header_tokens[0] : '<!--',
            config.header_tokens ? config.header_tokens[1] : '-->'
        ];

        await* [].concat(...langs.map((lang) => files[lang].map(async (file) => {
            // TODO this can most likely do with some refactoring and code style adjustments
            let metadata, pagePermalink, _page, pageHTMLFile, options,
                pagePath = path.join(this.sitePath, pagespath, lang, file),
                pageTpl = this.theme.getFileContents('page.html'),
                pageTplNJ = nunjucks.compile(pageTpl, this.nunjucksEnv),
                md = new MkMeta(pagePath),
                pageSrc = '',
                filename = path.extname(file) === '.md' ?
                    path.basename(file, '.md') :
                    path.basename(file, '.markdown');

            md.defineTokens(tokens[0], tokens[1]);

            // Markdown extra
            metadata = md.metadata();

            options = {
                replacements: [{
                    pattern: ':title',
                    replacement: filename
                },
                {
                    pattern: ':language',
                    replacement: lang
                }]
            };
            if (config.i18n.default === lang) {
                options.structure = config.pages_permalink.split(':language/')[1];
                pagePermalink = permalinks(options);
            } else {
                options.structure = config.pages_permalink;
                pagePermalink = permalinks(options);
            }

            _page = {
                content: md.markdown(),
                metadata: metadata
            };

            pageHTMLFile = pageTplNJ.render({
                page: _page,
                config: config
            });

            // Removing header metadata
            pageHTMLFile = pageHTMLFile.replace(/<!--[\s\S]*?-->/g, '');

            metadata.content = pageHTMLFile;
            metadata.file = postspath + file; // TODO check whether this needs sitePath
            metadata.filename = filename;
            metadata.link = pagePermalink;
            metadata.lang = lang;
            metadata.date = new Date(metadata.date);
            pageSrc = path.join(this.sitePath, 'public', pagePermalink, 'index.html');

            await mkdirpAsync(path.join(this.sitePath, 'public', pagePermalink));

            // write page html file
            await fs.writeFileAsync(pageSrc, pageHTMLFile);
            console.log(clc.info(`Successfully generated page ${pagePermalink}`));

            pages[lang] = pages[lang] || [];
            pages[lang].push(metadata);
        })));
        return pages;
    }

    async getPostFiles() {
        const files = {};

        await* this.config.i18n.languages.map(async (lang) => {
            files[lang] = (await fs.readdirAsync(path.join(this.sitePath, postspath, lang)))
                .filter((p) => rMarkdownExt.test(p));
        });

        return files;
    }

    async getPageFiles() {
        const files = {};

        await* this.config.i18n.languages.map(async (lang) => {
            const langPath = path.join(this.sitePath, pagespath, lang);
            await mkdirpAsync(langPath);
            files[lang] = (await fs.readdirAsync(langPath)).filter((p) => rMarkdownExt.test(p));
        });

        return files;
    }

    async generateRSS(postsMetadata, pagesMetadata) {
        const rssTemplate = await fs.readFileAsync(path.join(__dirname, 'resources/rss.xml'), { encoding: 'utf8' });
        const rssTemplateNJ = nunjucks.compile(rssTemplate, this.nunjucksEnv);
        const config = this.config;
        const rssAuthor = config.author_email ? `${config.author_email} ( ${config.author} )` : config.author;

        await* Object.entries(postsMetadata).map(async ([lang, langPosts]) => {
            const posts = langPosts.slice(0, config.index_posts);
            const isDefaultLang = config.i18n.default === lang;
            const rssPath = path.join(this.sitePath, 'public', ...(isDefaultLang ? [] : [lang]));
            const rssLink = `${config.domain}${isDefaultLang ? '' : '/' + lang}/rss.xml`;

            const rssContent = rssTemplateNJ.render({
                rss: {
                    date: new Date().toUTCString(),
                    link: rssLink,
                    author: rssAuthor,
                    lang: lang
                },
                posts,
                config,
                pages: pagesMetadata
            });

            await mkdirpAsync(rssPath);
            await fs.writeFileAsync(`${rssPath}/rss.xml`, rssContent);
            console.log(clc.info(`${lang}/rss.xml file successfully created`));
        });
    }
}
<|endoftext|>


<commit_before>
    /**
     * world class which has all the bodies and behaviors to simulate.
     * @class
     */
    var world = function () {
        this.bodies = [];
        this.behaviors = [];
        this.renderer = undefined;
        this.paused = true;
        this.ratio = 100; // pixels/meter
        this.width = 600;
        this.height = 500;

        this.movingBody = null;
        this.lastStep = 0;
        this.lastMove = 0;
        this.uuid = 0;

        this.quadtree = new viva.Quadtree( new viva.AABB( 0, 0, 0, 0 ) );

        /* event handlers */
        this.onMove = this._onMove.bind( this );
        this.onEnd = this._onEnd.bind( this );
    };

    world.prototype.init = function () {

    };

    /**
     * start the world
     */
    world.prototype.start = function () {
        if ( !this.paused ) {
            return;
        }
        this.lastStep = now();

        raf( this._step.bind( this ) );

        this.paused = false;
    };

    /**
     * step function which is called by requestAnimationFrame at every frame
     * all the simulation is done in this function by time step of each step function
     * @private
     */
    world.prototype._step = function() {
        var i, body,
            thisStep = now(),
            dt = ( thisStep - this.lastStep ) / 1000; // in seconds

        this.lastStep = thisStep;

        for ( i = 0; i < this.behaviors.length; i++ ) {
            for ( var j = 0; j < this.bodies.length; j++ ) {
                body = this.bodies[ j ];
                this.behaviors[ i ].behave( body );
            }
        }

        this.quadtree.clear();
        for ( i = 0; i < this.bodies.length; i++ ) {
            body = this.bodies[ i ];
            body.step( dt );
            this.quadtree.insert( body );
        }

        if ( this.renderer ) {
            this.renderer.draw( this.bodies );
        }

        if ( !this.paused ) {
            raf( this._step.bind( this ) );
        }
    };

    /**
     * add a body to the world
     *
     * @param {Object} body
     */
    world.prototype.add = function( body ) {
        body.world = this;
        body.uuid = this.uuid++;
        this.bodies.push( body );
        this.quadtree.insert( body );
    };

    /**
     * apply a behavior to the world
     *
     * @param {Object} behavior
     */
    world.prototype.apply = function( behavior ) {
        this.behaviors.push( behavior );
    };

    /**
     * set renderer of the world
     *
     * @param {Object} renderer
     */
    world.prototype.setRenderer = function ( renderer ) {
        this.renderer = renderer;
        this.width = this.renderer.width;
        this.height = this.renderer.height;

        this.renderer.on( startEvent, this._onClick.bind( this ) );
        this.quadtree.bound.set( 0, 0, this.width, this.height );
    };

    /**
     * pause simulation
     */
    world.prototype.pause = function () {
        this.paused = true;
    };

    /**
     * resume paused simulation
     */
    world.prototype.resume = function () {
        this.start();
    };

    /**
     * mousedown/touchstart event handler.
     * if clicked on a body, the body stops simulation and stays at the mouse pointer
     * as if the user is holding it
     * @private
     */
    world.prototype._onClick = function ( e ) {
        var offset = this.renderer.el.getBoundingClientRect(),
            x = e.pageX || e.touches[0].pageX - offset.left,
            y = e.pageY || e.touches[0].pageY - offset.top,
            body,
            i;

        if ( this.paused ) {
            return;
        }

        // Consider the later a body is added to the world, the higher z-index it has.
        for ( i = this.bodies.length - 1; i >= 0; i-- ) {
            body = this.bodies[ i ];
            if ( body.contains( x, y ) ) {
                body.setStatus( "MOVING" );
                body.angularVelocity = 0;
                this.movingBody = body;

                this.renderer.on( moveEvent, this.onMove );
                this.renderer.on( endEvent, this.onEnd );

                return;
            }
        }
    };

    /**
     * mousemove/touchmove event handler.
     * there the user is holding a body, moves the body along the pointer
     * @private
     */
    world.prototype._onMove = function ( e ) {
        var offset = this.renderer.el.getBoundingClientRect(),
            x = isMobile ? e.touches[0].pageX - offset.left : e.pageX,
            y = isMobile ? e.touches[0].pageY - offset.top : e.pageY,
            moveTime = now(),
            dt = moveTime - this.lastMove,
            body = this.movingBody,
            velocity;

        e.preventDefault();
        e.stopPropagation();

        if ( !body ) {
            this.lastMove = 0;
            return;
        }

        if ( !this._isMovable( body, x, y ) ) {
            this.lastMove = 0;
            return;
        }

        // calculate the velocity of movement
        body.prevPosition.set( x, y );
        body.move( viva.vector.create( x, y ) );
        velocity = viva.vector.copy( body.position ).sub( body.prevPosition ).scale( 1000 / dt );

        viva.vector.release( body.velocity );
        body.velocity = velocity;

        this.lastMove = moveTime;
    };

    /**
     * check if the body is within the boundary
     * @private
     *
     * @param {Object} body
     * @param {Number} x x-coordinate of mouse pointer
     * @param {Number} y y-coordinate of mouse pointer
     */
    world.prototype._isMovable = function ( body, x, y ) {
        var horizontalDistance = body.radius || body.width/2,
            verticalDistance = body.radius || body.height/2,
            boundary = this.renderer.el.getBoundingClientRect();

        if ( x + horizontalDistance >= boundary.left + boundary.width ) {
            return false;
        }
        if ( x - horizontalDistance <= boundary.left  ) {
            return false;
        }
        if ( y + verticalDistance >= boundary.top + boundary.height ) {
            return false;
        }
        if ( y - verticalDistance <= boundary.top ) {
            return false;
        }

        return true;
    };

    /**
     * mouseend/touchend event handler.
     * let the body go
     * @private
     */
    world.prototype._onEnd = function () {
        if ( now() - this.lastMove > 300 ) {
            this.movingBody.velocity.reset();
        }

        this.movingBody.setStatus( "NORMAL" );
        this.movingBody = null;
        this.renderer.off( moveEvent, this.onMove );
        this.renderer.off( endEvent, this.onEnd );
    };

    viva.world = world;
<commit_msg>add threshold<commit_after>
    /**
     * world class which has all the bodies and behaviors to simulate.
     * @class
     */
    var world = function () {
        this.bodies = [];
        this.behaviors = [];
        this.renderer = undefined;
        this.paused = true;
        this.ratio = 100; // pixels/meter
        this.width = 600;
        this.height = 500;
        this.maxDt = 24/1000;

        this.movingBody = null;
        this.lastStep = 0;
        this.lastMove = 0;
        this.uuid = 0;

        this.quadtree = new viva.Quadtree( new viva.AABB( 0, 0, 0, 0 ) );

        /* event handlers */
        this.onMove = this._onMove.bind( this );
        this.onEnd = this._onEnd.bind( this );
    };

    world.prototype.init = function () {

    };

    /**
     * start the world
     */
    world.prototype.start = function () {
        if ( !this.paused ) {
            return;
        }
        this.lastStep = now();

        raf( this._step.bind( this ) );

        this.paused = false;
    };

    /**
     * step function which is called by requestAnimationFrame at every frame
     * all the simulation is done in this function by time step of each step function
     * @private
     */
    world.prototype._step = function() {
        var i, body,
            thisStep = now(),
            dt = ( thisStep - this.lastStep ) / 1000; // in seconds

        if ( dt > this.maxDt ) {
            dt = this.maxDt;
        }

        this.lastStep = thisStep;

        for ( i = 0; i < this.behaviors.length; i++ ) {
            for ( var j = 0; j < this.bodies.length; j++ ) {
                body = this.bodies[ j ];
                this.behaviors[ i ].behave( body );
            }
        }

        this.quadtree.clear();
        for ( i = 0; i < this.bodies.length; i++ ) {
            body = this.bodies[ i ];
            body.step( dt );
            this.quadtree.insert( body );
        }

        if ( this.renderer ) {
            this.renderer.draw( this.bodies );
        }

        if ( !this.paused ) {
            raf( this._step.bind( this ) );
        }
    };

    /**
     * add a body to the world
     *
     * @param {Object} body
     */
    world.prototype.add = function( body ) {
        body.world = this;
        body.uuid = this.uuid++;
        this.bodies.push( body );
        this.quadtree.insert( body );
    };

    /**
     * apply a behavior to the world
     *
     * @param {Object} behavior
     */
    world.prototype.apply = function( behavior ) {
        this.behaviors.push( behavior );
    };

    /**
     * set renderer of the world
     *
     * @param {Object} renderer
     */
    world.prototype.setRenderer = function ( renderer ) {
        this.renderer = renderer;
        this.width = this.renderer.width;
        this.height = this.renderer.height;

        this.renderer.on( startEvent, this._onClick.bind( this ) );
        this.quadtree.bound.set( 0, 0, this.width, this.height );
    };

    /**
     * pause simulation
     */
    world.prototype.pause = function () {
        this.paused = true;
    };

    /**
     * resume paused simulation
     */
    world.prototype.resume = function () {
        this.start();
    };

    /**
     * mousedown/touchstart event handler.
     * if clicked on a body, the body stops simulation and stays at the mouse pointer
     * as if the user is holding it
     * @private
     */
    world.prototype._onClick = function ( e ) {
        var offset = this.renderer.el.getBoundingClientRect(),
            x = e.pageX || e.touches[0].pageX - offset.left,
            y = e.pageY || e.touches[0].pageY - offset.top,
            body,
            i;

        if ( this.paused ) {
            return;
        }

        // Consider the later a body is added to the world, the higher z-index it has.
        for ( i = this.bodies.length - 1; i >= 0; i-- ) {
            body = this.bodies[ i ];
            if ( body.contains( x, y ) ) {
                body.setStatus( "MOVING" );
                body.angularVelocity = 0;
                this.movingBody = body;

                this.renderer.on( moveEvent, this.onMove );
                this.renderer.on( endEvent, this.onEnd );

                return;
            }
        }
    };

    /**
     * mousemove/touchmove event handler.
     * there the user is holding a body, moves the body along the pointer
     * @private
     */
    world.prototype._onMove = function ( e ) {
        var offset = this.renderer.el.getBoundingClientRect(),
            x = isMobile ? e.touches[0].pageX - offset.left : e.pageX,
            y = isMobile ? e.touches[0].pageY - offset.top : e.pageY,
            moveTime = now(),
            dt = moveTime - this.lastMove,
            body = this.movingBody,
            velocity;

        e.preventDefault();
        e.stopPropagation();

        if ( !body ) {
            this.lastMove = 0;
            return;
        }

        if ( !this._isMovable( body, x, y ) ) {
            this.lastMove = 0;
            return;
        }

        // calculate the velocity of movement
        body.prevPosition.set( x, y );
        body.move( viva.vector.create( x, y ) );
        velocity = viva.vector.copy( body.position ).sub( body.prevPosition ).scale( 1000 / dt );

        viva.vector.release( body.velocity );
        body.velocity = velocity;

        this.lastMove = moveTime;
    };

    /**
     * check if the body is within the boundary
     * @private
     *
     * @param {Object} body
     * @param {Number} x x-coordinate of mouse pointer
     * @param {Number} y y-coordinate of mouse pointer
     */
    world.prototype._isMovable = function ( body, x, y ) {
        var horizontalDistance = body.radius || body.width/2,
            verticalDistance = body.radius || body.height/2,
            boundary = this.renderer.el.getBoundingClientRect();

        if ( x + horizontalDistance >= boundary.left + boundary.width ) {
            return false;
        }
        if ( x - horizontalDistance <= boundary.left  ) {
            return false;
        }
        if ( y + verticalDistance >= boundary.top + boundary.height ) {
            return false;
        }
        if ( y - verticalDistance <= boundary.top ) {
            return false;
        }

        return true;
    };

    /**
     * mouseend/touchend event handler.
     * let the body go
     * @private
     */
    world.prototype._onEnd = function () {
        if ( now() - this.lastMove > 300 ) {
            this.movingBody.velocity.reset();
        }

        this.movingBody.setStatus( "NORMAL" );
        this.movingBody = null;
        this.renderer.off( moveEvent, this.onMove );
        this.renderer.off( endEvent, this.onEnd );
    };

    viva.world = world;
<|endoftext|>


<commit_before>console.log(`Running in ${process.env.NODE_ENV} mode.`)

if (__PRODUCTION__) {
    module.exports = require('./boot.prod')
} else {
    module.exports = require('./boot.dev')
}
<commit_msg>boot : load styles.<commit_after>console.log(`Running in ${process.env.NODE_ENV} mode.`)

// load styles
import 'styles'

if (__PRODUCTION__) {
    module.exports = require('./boot.prod')
} else {
    module.exports = require('./boot.dev')
}
<|endoftext|>


<commit_before>const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Options = imports.options;
const Runtime = imports.runtime;
const Utils = imports.utils;

const CmdOptions = [
    {
        name: 'component',
        shortName: 'c',
        requireArgument: true,
        defaultValue: [],
        allowMultiple: true,
        help: 'Add a component to the manifest'
    },
    {
        name: 'dbus',
        shortName: 'd',
        requireArgument: true,
        defaultValue: [],
        allowMultiple: true,
        help: 'Add a DBus interface description file to the manifest'
    },
    {
        name: 'ui',
        shortName: 'u',
        requireArgument: true,
        defaultValue: [],
        allowMultiple: true,
        help: 'Add a UI file to the manifest'
    },
    {
        name: 'help',
        shortName: 'h',
        requireArgument: false,
        defaultValue: false,
        help: 'Print this screen'
    },
];

// relative: GFile
// path: string
let normalizePath = function(relative, path) {
    return relative.get_relative_path(Gio.File.new_for_path(path));
};

//
let exec = function(args) {
    let options = Options.parseArguments(CmdOptions, args);

    if (options.options.help) {
        Options.printHelp('noflo-gnome add', CmdOptions);
        return 0;
    }

    let appDirectory = Gio.File.new_for_path(GLib.getenv('PWD'));

    // Load content
    let manifestPath = Runtime.resolvePath('local://manifest.json');
    if (!Utils.isPathRegular(manifestPath))
        throw new Error('Cannot find repository manifest');

    // Parse content
    let manifest = Utils.parseJSON(Utils.loadTextFileContent(manifestPath));

    // Add components
    for (let i in options.options.component) {
        if (!manifest.noflo.components)
            manifest.noflo.components = {};
        let path = options.options.component[i];
        manifest.noflo.components[Utils.getFileName(path)] =
            normalizePath(appDirectory, path);
    }

    // Add ui files
    for (let i in options.options.ui) {
        if (!manifest.ui)
            manifest.ui = [];
        manifest.ui.push({
            file: normalizePath(appDirectory, options.options.ui[i]),
            additionals: [],
        });
    }

    // Add DBus files
    for (let i in options.options.dbus) {
        if (!manifest.dbus)
            manifest.dbus = [];
        manifest.dbus.push({
            file: normalizePath(appDirectory, options.options.dbus[i]),
        });
    }

    // Save the whole thing
    Utils.saveTextFileContent(manifestPath, JSON.stringify(manifest, null, "  "));

    return 0;
};
<commit_msg>runtime: add: add ability to add graphs<commit_after>const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Options = imports.options;
const Runtime = imports.runtime;
const Utils = imports.utils;

const CmdOptions = [
    {
        name: 'component',
        shortName: 'c',
        requireArgument: true,
        defaultValue: [],
        allowMultiple: true,
        help: 'Add a component to the manifest'
    },
    {
        name: 'dbus',
        shortName: 'd',
        requireArgument: true,
        defaultValue: [],
        allowMultiple: true,
        help: 'Add a DBus interface description file to the manifest'
    },
    {
        name: 'graph',
        shortName: 'g',
        requireArgument: true,
        defaultValue: [],
        allowMultiple: true,
        help: 'Add a graph to the manifest'
    },
    {
        name: 'ui',
        shortName: 'u',
        requireArgument: true,
        defaultValue: [],
        allowMultiple: true,
        help: 'Add a UI file to the manifest'
    },
    {
        name: 'help',
        shortName: 'h',
        requireArgument: false,
        defaultValue: false,
        help: 'Print this screen'
    },
];

// relative: GFile
// path: string
let normalizePath = function(relative, path) {
    return relative.get_relative_path(Gio.File.new_for_path(path));
};

//
let exec = function(args) {
    let options = Options.parseArguments(CmdOptions, args);

    if (options.options.help) {
        Options.printHelp('noflo-gnome add', CmdOptions);
        return 0;
    }

    let appDirectory = Gio.File.new_for_path(GLib.getenv('PWD'));

    // Load content
    let manifestPath = Runtime.resolvePath('local://manifest.json');
    if (!Utils.isPathRegular(manifestPath))
        throw new Error('Cannot find repository manifest');

    // Parse content
    let manifest = Utils.parseJSON(Utils.loadTextFileContent(manifestPath));

    // Add components
    for (let i in options.options.component) {
        if (!manifest.noflo.components)
            manifest.noflo.components = {};
        let path = options.options.component[i];
        manifest.noflo.components[Utils.getFileName(path)] =
            normalizePath(appDirectory, path);
    }

    // Add graphs
    for (let i in options.options.graph) {
        if (!manifest.noflo.graphs)
            manifest.noflo.graphs = {};
        let path = options.options.graph[i];
        manifest.noflo.graphs[Utils.getFileName(path)] =
            normalizePath(appDirectory, path);
    }

    // Add ui files
    for (let i in options.options.ui) {
        if (!manifest.ui)
            manifest.ui = [];
        manifest.ui.push({
            file: normalizePath(appDirectory, options.options.ui[i]),
            additionals: [],
        });
    }

    // Add DBus files
    for (let i in options.options.dbus) {
        if (!manifest.dbus)
            manifest.dbus = [];
        manifest.dbus.push({
            file: normalizePath(appDirectory, options.options.dbus[i]),
        });
    }

    // Save the whole thing
    Utils.saveTextFileContent(manifestPath, JSON.stringify(manifest, null, "  "));

    return 0;
};
<|endoftext|>


<commit_before>/*
 * Calendarth
 * Fetch a Public Google Calendar with AJAX
 * https://github.com/thanpolas/calendarth
 *
 * Copyright (c) 2014 Thanasis Polychronakis
 * Licensed under the MIT license.
 */
/**
 * @fileOverview Google Calendar client, fetches and parses calendar items.
 */

var Item = require('./item');

/**
 * The base class.
 *
 * @param {Object} options A dict with options to configure the Calendarth:
 *    @param {string} calendarId REQUIRED the calendar Id, looks like:
 *        djasldj23ljd23dj23ldj2%40group.calendar.google.com
 *    @param {string} apiKey A google API v3 key.
 * @constructor
 */
var Calendarth = module.exports = function(options) {
  this.options = options || {};
  this.apiKey = this.options.apiKey || null;
  this.calendarId = this.options.calendarId || null;
  this.maxResults = this.options.maxResults || 20;
};

/**
 * Fetch Calendar items.
 *
 * @param {Function(Error, Array.<Calendarth.Item>)} cb Node style callback.
 * @see https://github.com/thanpolas/calendarth/wiki/Google_Calendar_Object_v3
 */
Calendarth.prototype.fetch = function(cb) {
  var calendarUrl = 'https://www.googleapis.com/calendar/v3/calendars/';
  calendarUrl += this.calendarId + '/events?key=';
  calendarUrl += this.apiKey;

  var dt = new Date();
  calendarUrl += '&orderBy=startTime';
  calendarUrl += '&singleEvents=true';
  calendarUrl += '&timeMin=' + dt.toISOString();
  calendarUrl += '&maxResults=' + this.maxResults;
  $.ajax({
    type: 'GET',
    url: calendarUrl,
    crossDomain: true,
    dataType: 'json'
  }).done(function(data) {
    var item = new Item(data);
    cb(null, item);
  }).fail(function(jqXHR, textStatus, errorThrown) {
    cb(errorThrown);
  });
};
<commit_msg>add getEventLink() feature<commit_after>/*
 * Calendarth
 * Fetch a Public Google Calendar with AJAX
 * https://github.com/thanpolas/calendarth
 *
 * Copyright (c) 2014 Thanasis Polychronakis
 * Licensed under the MIT license.
 */
/**
 * @fileOverview Google Calendar client, fetches and parses calendar items.
 */

var Item = require('./item');

/**
 * The base class.
 *
 * @param {Object} options A dict with options to configure the Calendarth:
 *    @param {string} calendarId REQUIRED the calendar Id, looks like:
 *        djasldj23ljd23dj23ldj2%40group.calendar.google.com
 *    @param {string} apiKey A google API v3 key.
 * @constructor
 */
var Calendarth = module.exports = function(options) {
  this.options = options || {};
  this.apiKey = this.options.apiKey || null;
  this.calendarId = this.options.calendarId || null;
  this.maxResults = this.options.maxResults || 20;
};

/**
 * Fetch Calendar items.
 *
 * @param {Function(Error, Array.<Calendarth.Item>)} cb Node style callback.
 * @see https://github.com/thanpolas/calendarth/wiki/Google_Calendar_Object_v3
 */
Calendarth.prototype.fetch = function(cb) {
  var calendarUrl = 'https://www.googleapis.com/calendar/v3/calendars/';
  calendarUrl += this.calendarId + '/events?key=';
  calendarUrl += this.apiKey;

  var dt = new Date();
  calendarUrl += '&orderBy=startTime';
  calendarUrl += '&singleEvents=true';
  calendarUrl += '&timeMin=' + dt.toISOString();
  calendarUrl += '&maxResults=' + this.maxResults;
  $.ajax({
    type: 'GET',
    url: calendarUrl,
    crossDomain: true,
    dataType: 'json'
  }).done(function(data) {
    var item = new Item(data);
    cb(null, item);
  }).fail(function(jqXHR, textStatus, errorThrown) {
    cb(errorThrown);
  });
};

/**
 * Return a url that will perform the "Add Event to Google Calendar" action.
 *
 * @param {Object} eventItem The event data object.
 * @return {string} The event link to use in the anchor element.
 */
Calendarth.prototype.getEventLink = function(eventItem) {
  var out = 'http://www.google.com/calendar/event?action=TEMPLATE&text=';
  out += encodeURIComponent(eventItem.summary);
  out += '&dates=';
  out += encodeURIComponent(eventItem.start.dateTime ?
    eventItem.start.dateTime : eventItem.start.date);
  out += '/';
  out += encodeURIComponent(eventItem.end.dateTime ?
    eventItem.end.dateTime : eventItem.end.date);
  out += '&details=';
  out += encodeURIComponent(eventItem.description);
  out += '&location=';
  out += encodeURIComponent(eventItem.location);
  out += '&trp=false&sprop=name:';
};
<|endoftext|>


<commit_before>export function init(data){
    if(d3.select(this.div).select('.loader').empty()){
        d3.select(this.div).insert('div', ':first-child')
          .attr('class', 'loader')
          .selectAll('.blockG').data(d3.range(8))
            .enter().append('div')
            .attr('class', d => 'blockG rotate'+(d+1) );
    }

    this.wrap.attr('class', 'wc-chart');

    this.setDefaults();

    this.raw_data = data;

    let startup = (data => {
      //connect this chart and its controls, if any
      if(this.controls){
          this.controls.targets.push(this);
          if(!this.controls.ready){
            this.controls.init(data);
          }
          else{
            this.controls.layout();
          }
      }

      //make sure container is visible (has height and width) before trying to initialize
      var visible = d3.select(this.div).property('offsetWidth') > 0 && d3.select(this.div).property('offsetHeight') > 0;
      if(!visible){
          var onVisible = setInterval(i => {
              let visible_now = d3.select(this.div).property('offsetWidth') > 0 && d3.select(this.div).property('offsetHeight') > 0;
              if(visible_now){
                this.layout();
                this.wrap.datum(this);
                this.draw();
                clearInterval(onVisible);
              }
         }, 500);
      }
      else{
        this.layout();
        this.wrap.datum(this);
        this.draw();
      }
    });

    this.events.onInit.call(this);
    if(data.length){
      this.checkRequired(data);
    }
    startup(data);

    return this;
}
<commit_msg>Tweaks to .init()<commit_after>export function init(data){
    if(d3.select(this.div).select('.loader').empty()){
        d3.select(this.div).insert('div', ':first-child')
          .attr('class', 'loader')
          .selectAll('.blockG').data(d3.range(8))
            .enter().append('div')
            .attr('class', d => 'blockG rotate'+(d+1) );
    }

    this.wrap.attr('class', 'wc-chart');

    this.setDefaults();

    this.raw_data = data;

    let startup = (data => {
      //connect this chart and its controls, if any
      if(this.controls){
          this.controls.targets.push(this);
          if(!this.controls.ready){
            this.controls.init(this.raw_data);
          }
          else{
            this.controls.layout();
          }
      }

      //make sure container is visible (has height and width) before trying to initialize
      var visible = d3.select(this.div).property('offsetWidth') > 0 && d3.select(this.div).property('offsetHeight') > 0;
      if(!visible){
          var onVisible = setInterval(i => {
              let visible_now = d3.select(this.div).property('offsetWidth') > 0 && d3.select(this.div).property('offsetHeight') > 0;
              if(visible_now){
                this.layout();
                this.wrap.datum(this);
                this.draw();
                clearInterval(onVisible);
              }
         }, 500);
      }
      else{
        this.layout();
        this.wrap.datum(this);
        this.draw();
      }
    });

    this.events.onInit.call(this);
    if(this.raw_data.length){
      this.checkRequired(this.raw_data);
    }
    startup(data);

    return this;
}
<|endoftext|>


<commit_before>/* @flow */

import zygon from 'zygon'
import chalk from 'chalk'
import {getLaunchedNavies, Navy} from '../../'
import {getConfig} from '../config'

export async function printPS(env: Navy, json: boolean): Promise<boolean> {
  const ps = await env.ps()

  if (json) {
    console.log(JSON.stringify(ps, null, 2))
    return true
  }

  if (ps.length === 0) {
    return false
  }

  const defaultStatus = getConfig().defaultNavy === env.name
    ? chalk.blue(' (default navy)')
    : ''

  console.log()
  console.log('  ' + chalk.underline(env.name) + defaultStatus)

  zygon([
    { name: 'ID' },
    { name: 'Name' },
    { name: 'Image' },
    { name: 'Status' },
  ], ps.map(service => [
    service.id,
    service.name,
    service.image,
    service.status,
  ]))

  return true
}

export default async function (opts: Object): Promise<void> {
  const navies = await getLaunchedNavies()
  navies.forEach(navy => printPS(navy, opts.json))
}
<commit_msg>Improve output of status command<commit_after>/* @flow */

import zygon from 'zygon'
import chalk from 'chalk'
import {getLaunchedNavies, Navy} from '../../'
import {getConfig} from '../config'

export async function printPS(env: Navy, json: boolean): Promise<boolean> {
  const ps = await env.ps()

  if (json) {
    console.log(JSON.stringify(ps, null, 2))
    return true
  }

  if (ps.length === 0) {
    return false
  }

  const defaultStatus = getConfig().defaultNavy === env.name
    ? chalk.blue(' (default navy)')
    : ''

  console.log()
  console.log('  ' + chalk.underline(env.name) + defaultStatus)

  zygon([
    { name: 'ID', size: 13 },
    { name: 'Name', size: 15 },
    { name: 'Image', size: 25 },
    { name: 'Status' },
  ], ps.map(service => [
    service.id,
    service.name,
    service.image,
    service.status,
  ]))

  return true
}

export default async function (opts: Object): Promise<void> {
  const navies = await getLaunchedNavies()

  if (navies.length === 0) {
    console.log(chalk.dim('There are no launched navies'))
    return
  }

  navies.forEach(navy => printPS(navy, opts.json))
}
<|endoftext|>


<commit_before>'use strict';
var querystring = require('querystring');
var Promise     = require('bluebird');
var Request     = require('request2');

var internals = {};

internals.querystring = function () {
  return Object.keys(this.attributes).length? '?' + querystring.stringify(this.attributes) : '';
};

internals.cast = function (attributes) {
  return new this(attributes);
};

internals.find = function (model) {
  return this.filter(function (model) {
    return model.id === this.id;
  }, model)[0];
};

var Collection = function (attributes) {
  Array.call(this);
  this.attributes = attributes || {};
};

Collection.prototype = Object.create(Array.prototype);

Collection.prototype.reset = function () {
  while (this.length > 0) {
    this.pop();
  }
  this.attributes = {};
  return this;
};

Collection.prototype.fetch = function () {
  return Promise
    .bind(this)
    .then(function () {
      return this.model.prototype.url() + internals.querystring.call(this);
    })
    .then(function (url) {
      return new Request('GET', url, null, {
        errorProperty: this.model.prototype.errorProperty,
        dataProperty: this.model.prototype.dataProperty
      }).send();
    })
    .bind(this.model)
    .map(internals.cast)
    .bind(this)
    .reduce(function (newModels, model) {
      var existing = internals.find.call(this, model);
      if (existing) {
        existing.set(model);
      } else {
        newModels.push(model);
      }
      return newModels;
    }, [])
    .then(function (models) {
      this.push.apply(this, models);
    });
};

module.exports = Collection;<commit_msg>eavesdrop for collection#fetch<commit_after>'use strict';

var querystring = require('querystring');
var Promise     = require('bluebird');
var Request     = require('request2');
var eavesdrop   = require('eavesdrop');

var internals = {};

internals.querystring = function () {
  return Object.keys(this.attributes).length? '?' + querystring.stringify(this.attributes) : '';
};

internals.eavesdrop = function (request) {
  eavesdrop.call(this, request, [
    'preRequest',
    'postRequest',
    'preResponse',
    'postResponse'
  ]);
};

internals.cast = function (attributes) {
  return new this(attributes);
};

internals.find = function (model) {
  return this.filter(function (model) {
    return model.id === this.id;
  }, model)[0];
};

var Collection = function (attributes) {
  Array.call(this);
  this.attributes = attributes || {};
};

Collection.prototype = Object.create(Array.prototype);

Collection.prototype.reset = function () {
  while (this.length > 0) {
    this.pop();
  }
  this.attributes = {};
  return this;
};

Collection.prototype.fetch = function () {
  return Promise
    .bind(this)
    .then(function () {
      return this.model.prototype.url() + internals.querystring.call(this);
    })
    .then(function (url) {
      return new Request('GET', url, null, {
        errorProperty: this.model.prototype.errorProperty,
        dataProperty: this.model.prototype.dataProperty
      }).send();
    })
    .tap(internals.eavesdrop)
    .bind(this.model)
    .map(internals.cast)
    .bind(this)
    .reduce(function (newModels, model) {
      var existing = internals.find.call(this, model);
      if (existing) {
        existing.set(model);
      } else {
        newModels.push(model);
      }
      return newModels;
    }, [])
    .then(function (models) {
      this.push.apply(this, models);
    });
};

module.exports = Collection;<|endoftext|>


<commit_before>import tag from './tag.js'

export default [
  {
    id: 'a013bff1-8912-406f-ae9c-0875a8529d17',
    title: 'eslint静态检查',
    tags: [tag['eslint']],
    content: `在大家用vue-cli创建工程的时候，会有一项，使用使用eslint，如果选择了y，那么工程就会安装并启用eslint。

这里列举一下常见的错误：

1.多余的分号
![图片描述][1]

2.定义了却未使用的变量
![图片描述][2]

3.结尾多余空格
![图片描述][3]

4.超过一行的空行
![图片描述][4]

5.代码尾行应该有空行
![图片描述][5]

错误肯定是列举不完的，那么提示错误的时候，我们应该先去看提示信息（翻译），如果发现没有错误，可以对照[eslint的官方文档][6]

在大家适应了eslint的写法后，效率和正确率会直线上升

[1]: https://segmentfault.com/img/bVvhFl
  [2]: https://segmentfault.com/img/bVvhFs
  [3]: https://segmentfault.com/img/bVvhFE
  [4]: https://segmentfault.com/img/bVvhFN
  [5]: https://segmentfault.com/img/bVvhFR
  [6]: http://eslint.org/`
  },
  {
    id: '77c55630-2fbb-11e6-ac64-b50848dab1ca',
    title: 'this指向',
    tags: [tag['this'], tag['箭头符号']],
    content: `经常会有朋友问一些undifined的错误，比如：

这是刚创建的工程，我们定义了list，idx和current，在执行iter方法时，我们就给current赋值以idx为下标的值，当我们执行后会发现，浏览器报了一个错误

![图片描述][8]

这么回事，我们不是定义了idx了吗？

其实是因为我们在map里的this是指向当前map的迭代对象，而非我们vue的实例，所以this里没有我们需要的idx。

解决方式有两种，其一是通过保存this

    let _this = this

其二是使用es6箭头函数

    methods: {
        iter () {
          this.list.map((v, k) => {
            if (k === this.idx) {
              this.current = v

              console.log(this.current)
            }
          })
        }
      },

现在再看我们的浏览器

![图片描述][9]

已经可以达到我们预期的效果了！

[8]: https://segmentfault.com/img/bVvhHk
  [9]: https://segmentfault.com/img/bVvhHG`
  },
  {
    id: '83ea9ec0-2fbb-11e6-9485-6f1a608fe2fc',
    title: '根据$index判断当前值',
    tags: [tag['$index']],
    content: `比如我们有一个列表，我们希望能显示我们当前选中的那一个，如何实现呢？

基本思路是通过$index来判断是否是当前迭代对象，然后去增减class或者style来实现不同的样式

{{item}}

{{item}}

    data () {
      return {
        list: ['a', 'b', 'c'],
        activeId: 0
      }
    },`
  },
  {
    id: '8f67e780-2fbb-11e6-87d4-93f110a8c904',
    title: '传值与传字面量',
    tags: [tag['字面量']],
    content: `在vue的组件中传递数据，如果是单纯传递字面量，如

    <hello result="success"></hello>

那么在hello中获取的props result的值就是“success”，如果希望进行值传递，需要在指令前加 ':' 冒号，这样，父层的success的值改变，hello的值也会跟着改变。`
  },
  {
    id: '9649af20-2fbb-11e6-8f62-b3fe7c319399',
    title: '转场动画transition',
    tags: [tag['transition']],
    content: `在vue中有个很好用的指令，transition，通过它我们可以实现自定义的router切换中的动画

方法就是在

    <router-view transition="fade"></router-view>

加入自定义的class fade-transition , fade-leave , fade-enter即可。`
  },
  {
    id: 'b4bf6b31-4f86-4411-83c8-a72d590a5c71',
    title: 'v-cloak',
    tags: [tag['v-cloak']],
    content: `如果出现{{}}的短暂出现的情况，可以通过添加v-cloak来处理。

这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none } 一起用时，这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。`
  },
  {
    id: 'fbb9fe7a-0748-45c1-b9fe-fc8d05beaf72',
    title: '使用sass',
    tags: [tag['sass']],
    content: `首先安装依赖

    npm i node-sass sass-loader -D

然后在vue的style里添加

也可以使用其他css预编译，如sass

需要自行安装node-sass和sass-loader

    npm i node-sass sass-loader -S

`
  }
]
<|endoftext|>


<commit_before>//Copyright 2012 Telefonica Investigación y Desarrollo, S.A.U
//
//This file is part of RUSH.
//
//  RUSH is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
//  RUSH is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License along with RUSH
//  . If not, seehttp://www.gnu.org/licenses/.
//
//For those usages not covered by the GNU Affero General Public License please contact with::dtc_support@tid.es

var dir_prefix = process.env.RUSH_DIR_PREFIX || './';

// redis host
exports.queue = {};
exports.queue.redisHost = 'localhost';
exports.queue.redisPort = 6379;

exports.dbrelayer = {};
exports.dbrelayer.keyPrefix = 'wrH:';
exports.dbrelayer.redisHost = 'localhost';
exports.expireTime = 60 * 60;

exports.consumer_id = 'consumerA:';

//exports.evLsnr = {};
//exports.evLsnr.mongoHost = 'localhost';
//exports.evLsnr.mongoPort = 27017;
//exports.evLsnr.mongoDB = 'rush';
//exports.evLsnr.collectionState = 'RushState';
//exports.evLsnr.collectionError = 'RushError';

exports.listener = {};
exports.listener.port = 3001;

exports.consumer = {};
exports.consumer.maxPoppers = 30;
// agent: undefined -> globalAgent | false -> no agent
exports.consumer.agent = undefined;
exports.consumer.maxSockets = 100;

//exports.consumer.proxy =  "http://proxy:port";

/**
 * Level for logger
 * debug
 * warning
 * error
 *
 * @type {String}
 */

exports.consumer.logger = {};
exports.consumer.logger.logLevel = 'debug';
exports.consumer.logger.inspectDepth = 1;
exports.consumer.logger.Console = {
  level: 'debug', timestamp: true
};

exports.consumer.logger.File = {
  level: 'debug', filename: dir_prefix + '/consumer.log',
  timestamp: true, json: false,
  maxsize: 1024 * 1024, maxFiles: 3
};

exports.listener.logger = {};
exports.listener.logger.logLevel = 'debug';
exports.listener.logger.inspectDepth = 1;
exports.listener.logger.Console = {
  level: 'debug', timestamp: true
};
exports.listener.logger.File = {
  level: 'debug', filename: dir_prefix + '/listener.log',
  timestamp: true, json: false,
  maxsize: 1024 * 1024,
  maxFiles: 3
};

/* generic event listener */
var gevlsnr_mongo = 'localhost';
if (process.env.RUSH_GEN_MONGO) {
    gevlsnr_mongo = process.env.RUSH_GEN_MONGO;
}

//var gevLsnr = {};
//gevLsnr.name = 'gevLsnr';
//gevLsnr.mongoHost = gevlsnr_mongo;
//gevLsnr.mongoPort = 27017;
//gevLsnr.mongoDB = 'rush';
//gevLsnr.collection = 'RushGeneric';
//gevLsnr.filter = { state: 'error'};
//gevLsnr.take = {id: 'id', topic: 'topic', body: 'task.body',
//  statusCode: 'result.statusCode'};

exports.consumer.evModules = [
    {module: './evCallback'},
    {module: './evPersistence'},
    {module: './evPopbox'}
//    ,{module: './gevLsnr', config: gevLsnr}
];

exports.listener.evModules = [];

<commit_msg>Change default port to 5001<commit_after>//Copyright 2012 Telefonica Investigación y Desarrollo, S.A.U
//
//This file is part of RUSH.
//
//  RUSH is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
//  RUSH is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License along with RUSH
//  . If not, seehttp://www.gnu.org/licenses/.
//
//For those usages not covered by the GNU Affero General Public License please contact with::dtc_support@tid.es

var dir_prefix = process.env.RUSH_DIR_PREFIX || './';

// redis host
exports.queue = {};
exports.queue.redisHost = 'localhost';
exports.queue.redisPort = 6379;

exports.dbrelayer = {};
exports.dbrelayer.keyPrefix = 'wrH:';
exports.dbrelayer.redisHost = 'localhost';
exports.expireTime = 60 * 60;

exports.consumer_id = 'consumerA:';

//exports.evLsnr = {};
//exports.evLsnr.mongoHost = 'localhost';
//exports.evLsnr.mongoPort = 27017;
//exports.evLsnr.mongoDB = 'rush';
//exports.evLsnr.collectionState = 'RushState';
//exports.evLsnr.collectionError = 'RushError';

exports.listener = {};
exports.listener.port = 5001;

exports.consumer = {};
exports.consumer.maxPoppers = 30;
// agent: undefined -> globalAgent | false -> no agent
exports.consumer.agent = undefined;
exports.consumer.maxSockets = 100;

//exports.consumer.proxy =  "http://proxy:port";

/**
 * Level for logger
 * debug
 * warning
 * error
 *
 * @type {String}
 */

exports.consumer.logger = {};
exports.consumer.logger.logLevel = 'debug';
exports.consumer.logger.inspectDepth = 1;
exports.consumer.logger.Console = {
  level: 'debug', timestamp: true
};

exports.consumer.logger.File = {
  level: 'debug', filename: dir_prefix + '/consumer.log',
  timestamp: true, json: false,
  maxsize: 1024 * 1024, maxFiles: 3
};

exports.listener.logger = {};
exports.listener.logger.logLevel = 'debug';
exports.listener.logger.inspectDepth = 1;
exports.listener.logger.Console = {
  level: 'debug', timestamp: true
};
exports.listener.logger.File = {
  level: 'debug', filename: dir_prefix + '/listener.log',
  timestamp: true, json: false,
  maxsize: 1024 * 1024,
  maxFiles: 3
};

/* generic event listener */
var gevlsnr_mongo = 'localhost';
if (process.env.RUSH_GEN_MONGO) {
    gevlsnr_mongo = process.env.RUSH_GEN_MONGO;
}

//var gevLsnr = {};
//gevLsnr.name = 'gevLsnr';
//gevLsnr.mongoHost = gevlsnr_mongo;
//gevLsnr.mongoPort = 27017;
//gevLsnr.mongoDB = 'rush';
//gevLsnr.collection = 'RushGeneric';
//gevLsnr.filter = { state: 'error'};
//gevLsnr.take = {id: 'id', topic: 'topic', body: 'task.body',
//  statusCode: 'result.statusCode'};

exports.consumer.evModules = [
    {module: './evCallback'},
    {module: './evPersistence'},
//    ,{module: './gevLsnr', config: gevLsnr}
];

exports.listener.evModules = [];

<|endoftext|>


<commit_before>/* eslint-env browser */
'use strict'

let d3 = require('d3')
let phylogician = require('./phylogician.js')

let NavBarShow = false,
	maxWidth = document.body.clientWidth,
	barWidthOffset = 50,
	barWidth = maxWidth - barWidthOffset

let navBar = d3.select('body').append('div')
	.attr('id', 'controlBar')
	.attr('class', 'navbar')
	.attr('width', barWidth + 'px')

navBar.style('right', function() {
	let navBarPosition = barWidth + 'px'
	if (NavBarShow)
		navBarPosition = barWidthOffset + 'px'
	return navBarPosition
})

d3.xml('./src/navbrand.svg')
	.mimeType('image/svg+xml')
	.get(function(error, xml) {
		if (error) throw error
		xml.documentElement.id = 'brand'
		xml.documentElement.style.width = '30px'
		xml.documentElement.style.height = '30px'
		xml.documentElement.addEventListener('click', () => {
			toggleNavBar()
		})
		document.getElementById('controlBar').appendChild(xml.documentElement)
	})

function toggleNavBar() {
	if (navBar.node().getBoundingClientRect().right === barWidth)
		retractNavBar()
	else
		expandNavBar()
}

function expandNavBar() {
	let duration = 1000
	if (navBar.node().getBoundingClientRect().right !== barWidth) {
		let navBarPosition = barWidthOffset + 'px'
		navBar.transition()
			.duration(duration)
			.style('right', navBarPosition)

		d3.select('#brand').selectAll('path, polygon')
			.transition()
			.duration(duration)
			.attr('fill', '#FF6A13')
	}
}

function retractNavBar() {
	let duration = 1000
	if (navBar.node().getBoundingClientRect().right !== barWidthOffset) {
		let navBarPosition = barWidth + 'px'
		navBar.transition()
			.duration(duration)
			.style('right', navBarPosition)

		d3.select('#brand').selectAll('path, polygon')
			.transition()
			.duration(duration)
			.attr('fill', '#91DC5A')
	}
}

function popFormString() {
	if (document.getElementById('fileFormLabel'))
		document.getElementById('fileFormLabel').style.display = 'none'
	if (document.getElementById('stringInput')) {
		document.getElementById('stringInput').style.display = 'block'
	}
	else {
		let myStringForm = document.createElement('input')
		myStringForm.classList.add('form-control')
		myStringForm.id = 'stringInput'
		myStringForm.style.display = 'block'
		myStringForm.addEventListener('keydown', function(e) {
			let enterKeyCode = 13
			if (e.keyCode === enterKeyCode) {
				let newick = document.getElementById('stringInput').value
				myStringForm.style.display = 'none'
				if (newick !== '') {
					phylogician.makeTree(newick)
					retractNavBar()
				}
			}
		})
		document.body.appendChild(myStringForm)
	}
}

function popFormFile() {
	if (document.getElementById('stringInput'))
		document.getElementById('stringInput').style.display = 'none'
	if (document.getElementById('fileFormLabel')) {
		document.getElementById('fileFormLabel').style.display = 'block'
	}
	else {
		let fileFormLabel = document.createElement('label')
		fileFormLabel.classList.add('btn', 'btn-primary')
		fileFormLabel.id = 'fileFormLabel'
		fileFormLabel.setAttribute('for', 'fileInput')

		let fileForm = document.createElement('input')
		fileForm.id = 'fileInput'
		fileForm.type = 'file'
		fileForm.style.display = 'none'
		fileForm.addEventListener('change', function() {
			let fileInput = document.getElementById('fileInput'),
				newick = '',
				file = fileInput.files[0]
			let reader = new FileReader()

			reader.onload = function(err) {
				newick = reader.result
				fileFormLabel.style.display = 'none'
				phylogician.makeTree(newick)
				fileInput.value = null
				retractNavBar()
			}
			reader.readAsText(file)
		})
		fileFormLabel.innerHTML = 'Browse'
		fileFormLabel.appendChild(fileForm)
		document.body.appendChild(fileFormLabel)
	}
}

let navBarDOM = document.getElementById('controlBar')

let buttonGroup = document.createElement('div')
buttonGroup.classList.add('btn-group')
buttonGroup.setAttribute('data-toggle', 'buttons')
buttonGroup.style = 'margin-left: 60px;'

// input string or file via menu bar
let inputDiv = document.createElement('div')
inputDiv.classList.add('dropdown')
buttonGroup.appendChild(inputDiv)

let inputMenu = document.createElement('button')
inputMenu.classList.add('btn', 'dropdown-toggle')
inputMenu.setAttribute('data-toggle', 'dropdown')
inputMenu.type = 'button'
inputMenu.innerHTML = 'Load Data'
inputDiv.appendChild(inputMenu)

let inputOptions = document.createElement('div')
inputOptions.classList.add('dropdown-menu')
inputDiv.appendChild(inputOptions)

let submitNwkString = document.createElement('a')
submitNwkString.classList.add('dropdown-item')
submitNwkString.innerHTML = 'Input Text'
submitNwkString.addEventListener('click', popFormString)
inputOptions.appendChild(submitNwkString)

let submitNwkFile = document.createElement('a')
submitNwkFile.classList.add('dropdown-item')
submitNwkFile.innerHTML = 'Upload File'
submitNwkFile.addEventListener('click', popFormFile)
inputOptions.appendChild(submitNwkFile)

// change tree layout via menu bar
let treeLayoutDiv = document.createElement('div')
treeLayoutDiv.classList.add('dropdown')
buttonGroup.appendChild(treeLayoutDiv)

let treeLayoutMenu = document.createElement('button')
treeLayoutMenu.classList.add('btn', 'dropdown-toggle')
treeLayoutMenu.setAttribute('data-toggle', 'dropdown')
treeLayoutMenu.type = 'button'
treeLayoutMenu.innerHTML = 'Tree Layout'
treeLayoutMenu.style = 'left: 100px;'
treeLayoutDiv.appendChild(treeLayoutMenu)

let displayOptions = document.createElement('div')
displayOptions.classList.add('dropdown-menu')
treeLayoutDiv.appendChild(displayOptions)

let makeVertical = document.createElement('a')
makeVertical.classList.add('dropdown-item')
makeVertical.innerHTML = 'Vertical'
makeVertical.addEventListener('click', (e) => {
	phylogician.updateVertical(e)
	retractNavBar()
})
displayOptions.appendChild(makeVertical)

let makeRadial = document.createElement('a')
makeRadial.classList.add('dropdown-item')
makeRadial.innerHTML = 'Radial'
makeRadial.addEventListener('click', (e) => {
	phylogician.updateRadial(e)
	retractNavBar()
})
displayOptions.appendChild(makeRadial)

// conduct tree operations via menu bar
let operationsDiv = document.createElement('div')
operationsDiv.classList.add('dropdown')
buttonGroup.appendChild(operationsDiv)

let operationsMenu = document.createElement('button')
operationsMenu.classList.add('btn', 'dropdown-toggle')
operationsMenu.setAttribute('data-toggle', 'dropdown')
operationsMenu.type = 'button'
operationsMenu.innerHTML = 'Tree Operations'
operationsMenu.style = 'left: 100px;'
operationsDiv.appendChild(operationsMenu)

let operationsOptions = document.createElement('div')
operationsOptions.classList.add('dropdown-menu')
operationsDiv.appendChild(operationsOptions)

let fit2screen = document.createElement('a')
fit2screen.classList.add('dropdown-item')
fit2screen.innerHTML = 'Fit to screen'
fit2screen.addEventListener('click', (e) => {
	phylogician.fitScreen(e)
	retractNavBar()
})
operationsOptions.appendChild(fit2screen)

navBarDOM.appendChild(buttonGroup)
<commit_msg>adding short-cut<commit_after>/* eslint-env browser */
'use strict'

let d3 = require('d3')
let phylogician = require('./phylogician.js')

let NavBarShow = false,
	maxWidth = document.body.clientWidth,
	barWidthOffset = 50,
	barWidth = maxWidth - barWidthOffset

let navBar = d3.select('body').append('div')
	.attr('id', 'controlBar')
	.attr('class', 'navbar')
	.attr('width', barWidth + 'px')

navBar.style('right', function() {
	let navBarPosition = barWidth + 'px'
	if (NavBarShow)
		navBarPosition = barWidthOffset + 'px'
	return navBarPosition
})

d3.xml('./src/navbrand.svg')
	.mimeType('image/svg+xml')
	.get(function(error, xml) {
		if (error) throw error
		xml.documentElement.id = 'brand'
		xml.documentElement.style.width = '30px'
		xml.documentElement.style.height = '30px'
		xml.documentElement.addEventListener('click', () => {
			toggleNavBar()
		})
		document.getElementById('controlBar').appendChild(xml.documentElement)
	})

function toggleNavBar() {
	if (navBar.node().getBoundingClientRect().right === barWidth)
		retractNavBar()
	else
		expandNavBar()
}

function expandNavBar() {
	let duration = 1000
	if (navBar.node().getBoundingClientRect().right !== barWidth) {
		let navBarPosition = barWidthOffset + 'px'
		navBar.transition()
			.duration(duration)
			.style('right', navBarPosition)

		d3.select('#brand').selectAll('path, polygon')
			.transition()
			.duration(duration)
			.attr('fill', '#FF6A13')
	}
}

function retractNavBar() {
	let duration = 1000
	if (navBar.node().getBoundingClientRect().right !== barWidthOffset) {
		let navBarPosition = barWidth + 'px'
		navBar.transition()
			.duration(duration)
			.style('right', navBarPosition)

		d3.select('#brand').selectAll('path, polygon')
			.transition()
			.duration(duration)
			.attr('fill', '#91DC5A')
	}
}

function popFormString() {
	if (document.getElementById('fileFormLabel'))
		document.getElementById('fileFormLabel').style.display = 'none'
	if (document.getElementById('stringInput')) {
		document.getElementById('stringInput').style.display = 'block'
	}
	else {
		let myStringForm = document.createElement('input')
		myStringForm.classList.add('form-control')
		myStringForm.id = 'stringInput'
		myStringForm.style.display = 'block'
		myStringForm.addEventListener('keydown', function(e) {
			let enterKeyCode = 13
			if (e.keyCode === enterKeyCode) {
				let newick = document.getElementById('stringInput').value
				myStringForm.style.display = 'none'
				if (newick !== '') {
					phylogician.makeTree(newick)
					retractNavBar()
				}
			}
		})
		document.body.appendChild(myStringForm)
	}
}

function popFormFile() {
	if (document.getElementById('stringInput'))
		document.getElementById('stringInput').style.display = 'none'
	if (document.getElementById('fileFormLabel')) {
		document.getElementById('fileFormLabel').style.display = 'block'
	}
	else {
		let fileFormLabel = document.createElement('label')
		fileFormLabel.classList.add('btn', 'btn-primary')
		fileFormLabel.id = 'fileFormLabel'
		fileFormLabel.setAttribute('for', 'fileInput')

		let fileForm = document.createElement('input')
		fileForm.id = 'fileInput'
		fileForm.type = 'file'
		fileForm.style.display = 'none'
		fileForm.addEventListener('change', function() {
			let fileInput = document.getElementById('fileInput'),
				newick = '',
				file = fileInput.files[0]
			let reader = new FileReader()

			reader.onload = function(err) {
				newick = reader.result
				fileFormLabel.style.display = 'none'
				phylogician.makeTree(newick)
				fileInput.value = null
				retractNavBar()
			}
			reader.readAsText(file)
		})
		fileFormLabel.innerHTML = 'Browse'
		fileFormLabel.appendChild(fileForm)
		document.body.appendChild(fileFormLabel)
	}
}

let navBarDOM = document.getElementById('controlBar')

let buttonGroup = document.createElement('div')
buttonGroup.classList.add('btn-group')
buttonGroup.setAttribute('data-toggle', 'buttons')
buttonGroup.style = 'margin-left: 60px;'

// input string or file via menu bar
let inputDiv = document.createElement('div')
inputDiv.classList.add('dropdown')
buttonGroup.appendChild(inputDiv)

let inputMenu = document.createElement('button')
inputMenu.classList.add('btn', 'dropdown-toggle')
inputMenu.setAttribute('data-toggle', 'dropdown')
inputMenu.type = 'button'
inputMenu.innerHTML = 'Load Data'
inputDiv.appendChild(inputMenu)

let inputOptions = document.createElement('div')
inputOptions.classList.add('dropdown-menu')
inputDiv.appendChild(inputOptions)

let submitNwkString = document.createElement('a')
submitNwkString.classList.add('dropdown-item')
submitNwkString.innerHTML = 'Input Text'
submitNwkString.addEventListener('click', popFormString)
inputOptions.appendChild(submitNwkString)

let submitNwkFile = document.createElement('a')
submitNwkFile.classList.add('dropdown-item')
submitNwkFile.innerHTML = 'Upload File'
submitNwkFile.addEventListener('click', popFormFile)
inputOptions.appendChild(submitNwkFile)

// change tree layout via menu bar
let treeLayoutDiv = document.createElement('div')
treeLayoutDiv.classList.add('dropdown')
buttonGroup.appendChild(treeLayoutDiv)

let treeLayoutMenu = document.createElement('button')
treeLayoutMenu.classList.add('btn', 'dropdown-toggle')
treeLayoutMenu.setAttribute('data-toggle', 'dropdown')
treeLayoutMenu.type = 'button'
treeLayoutMenu.innerHTML = 'Tree Layout'
treeLayoutMenu.style = 'left: 100px;'
treeLayoutDiv.appendChild(treeLayoutMenu)

let displayOptions = document.createElement('div')
displayOptions.classList.add('dropdown-menu')
treeLayoutDiv.appendChild(displayOptions)

let makeVertical = document.createElement('a')
makeVertical.classList.add('dropdown-item')
makeVertical.innerHTML = 'Vertical'
makeVertical.addEventListener('click', (e) => {
	phylogician.updateVertical(e)
	retractNavBar()
})
displayOptions.appendChild(makeVertical)

let makeRadial = document.createElement('a')
makeRadial.classList.add('dropdown-item')
makeRadial.innerHTML = 'Radial'
makeRadial.addEventListener('click', (e) => {
	phylogician.updateRadial(e)
	retractNavBar()
})
displayOptions.appendChild(makeRadial)

// conduct tree operations via menu bar
let operationsDiv = document.createElement('div')
operationsDiv.classList.add('dropdown')
buttonGroup.appendChild(operationsDiv)

let operationsMenu = document.createElement('button')
operationsMenu.classList.add('btn', 'dropdown-toggle')
operationsMenu.setAttribute('data-toggle', 'dropdown')
operationsMenu.type = 'button'
operationsMenu.innerHTML = 'Tree Operations'
operationsMenu.style = 'left: 100px;'
operationsDiv.appendChild(operationsMenu)

let operationsOptions = document.createElement('div')
operationsOptions.classList.add('dropdown-menu')
operationsDiv.appendChild(operationsOptions)

let fit2screen = document.createElement('a')
fit2screen.classList.add('dropdown-item')
fit2screen.innerHTML = 'Fit to screen'
fit2screen.addEventListener('click', (e) => {
	phylogician.fitScreen(e)
	retractNavBar()
})
operationsOptions.appendChild(fit2screen)

navBarDOM.appendChild(buttonGroup)

function navBarShortcut(e) {
	let x = 88
	if (e.ctrlKey && e.keyCode === x)
		toggleNavBar()
}
document.addEventListener('keyup', navBarShortcut, false)
<|endoftext|>


<commit_before>var soap = require("soap");

var CustomError = function(msg, httpStatus) {
   this.message = msg || "Unexpected error!";
   this.httpStatus = httpStatus || 500;
};
CustomError.prototype = Error.prototype;

var resolveWsdl = function(soapServerUrl) {
   return function(req, res, next) {
      var serviceName = req.params.service,
          wsdlUrl = soapServerUrl + serviceName + "?wsdl";

      soap.createClient(wsdlUrl, function(err, client) {
         if (err) {
            next(new CustomError(err.message));
            return;
         }

         if (typeof client === "undefined") {
            next(new CustomError("Unknown WSDL: " + serviceName, 400));
            return;
         }

         req._client = client;
         next();
      });
   };
};

var resolveService = function(req, res, next) {
   var client = req._client;

   var description = client.describe(),
       availableMethods = description[req.params.service][req.params.service+"HttpPort"],
       msg = "Unknown method: " + req.params.method + "\nUse one of: _describe, " + Object.keys(availableMethods).join(", ");

   if (req.params.method === "_describe") {
      res.send( JSON.stringify(description), {"Content-Type": "application/json"} );
      return;
   }

   if (typeof client[req.params.method] !== "function") {
      next(new CustomError(msg, 400));
      return;
   }

   req._spec = availableMethods[req.params.method].input;
   req._service = client[req.params.method];
   next();
};

var normalizeQuerystring = function(req, res, next) {
   // validate there are no alien parameter names
   // in queryString
   Object.keys(req.query).forEach(function (key) {
      if (typeof req._spec[key] === "undefined") {
         var msg = "Unexpected parameter: "+key+"\nUse one of: "+Object.keys(req._spec).join(", ");
         throw new CustomError(msg, 400);
      }
   });

   // populate soap query parameters from queryString
   // taking into account 'array' typed fields
   req._query = Object.keys(req._spec).reduce(function (query, key) {
      var spec = req._spec[key],
          querystringValue = req.query[key];
      if (typeof spec === 'object' && typeof querystringValue !== "undefined") {
         query[key] = {};
         var specKeys = Object.keys(spec),
             specKey = specKeys[0];
         // behaviour for array fields
         if (specKeys.length === 1 && specKey.substr(-2) === "[]")
            query[key][specKey.substr(0, specKey.length-2)] = querystringValue.split(",");
         //TODO implement behaviour for complex input objects
      } else {
         query[key] = querystringValue;
      }
      return query;
   }, {});

   next();
};

exports.configure = function(express, soapServerUrl, prefix) {
   prefix = prefix || "";

   express.error(function(err, req, res, next) {
      if (err instanceof CustomError) {
         res.send(err.message, err.httpStatus);
      } else {
         next(err);
      }
   });

   if (soapServerUrl.substr(-1) !== "/") soapServerUrl += "/";
   if (prefix.substr(-1) !== "/") prefix += "/";
   express.get( (prefix || "") + ":service/:method", [resolveWsdl(soapServerUrl), resolveService, normalizeQuerystring], function(req, res, next) {
      req._service( req._query, function(err, result) {
         if (err) {
            res.send(err.message, 500);
            return;
         }

         var json = JSON.stringify(result);
         res.send(json, {"Content-Type": "application/json"});
      } );
   });

   return this;
};
<commit_msg>Cleansifsy controller<commit_after>var soap = require("soap");

var CustomError = function(msg, httpStatus) {
   this.message = msg || "Unexpected error!";
   this.httpStatus = httpStatus || 500;
};
CustomError.prototype = Error.prototype;

var resolveWsdl = function(soapServerUrl) {
   return function(req, res, next) {
      var serviceName = req.params.service,
          wsdlUrl = soapServerUrl + serviceName + "?wsdl";

      soap.createClient(wsdlUrl, function(err, client) {
         if (err) {
            next(new CustomError(err.message));
            return;
         }

         if (typeof client === "undefined") {
            next(new CustomError("Unknown WSDL: " + serviceName, 400));
            return;
         }

         req._client = client;
         next();
      });
   };
};

var resolveService = function(req, res, next) {
   var client = req._client;

   var description = client.describe(),
       availableMethods = description[req.params.service][req.params.service+"HttpPort"],
       msg = "Unknown method: " + req.params.method + "\n"
           + "Use one of: _describe, " + Object.keys(availableMethods).join(", ");

   if (req.params.method === "_describe") {
      res.send( JSON.stringify(description), {"Content-Type": "application/json"} );
      return;
   }

   if (typeof client[req.params.method] !== "function") {
      next(new CustomError(msg, 400));
      return;
   }

   req._spec = availableMethods[req.params.method].input;
   req._service = client[req.params.method];
   next();
};

var normalizeQuerystring = function(req, res, next) {
   // validate there are no alien parameter names
   // in queryString
   for (var querystringKey in req.query) {
      if ( req.query.hasOwnProperty(querystringKey)
          && typeof req._spec[querystringKey] === "undefined" ) {
         var msg = "Unexpected parameter: " + querystringKey + "\n"
                 + "Use one of: " + Object.keys(req._spec).join(", ");
         next(new CustomError(msg, 400));
         return;
      }
   }

   // populate soap query parameters from queryString
   // taking into account 'array' typed fields
   req._query = Object.keys(req._spec).reduce(function (query, key) {
      var spec = req._spec[key],
          querystringValue = req.query[key];
      if (typeof spec === 'object' && typeof querystringValue !== "undefined") {
         query[key] = {};
         var specKeys = Object.keys(spec),
             specKey = specKeys[0];

         // behaviour for array fields
         if (specKeys.length === 1 && specKey.substr(-2) === "[]")
            query[key][specKey.substr(0, specKey.length-2)] = querystringValue.split(",");

         //TODO implement behaviour for complex input objects
      } else {
         query[key] = querystringValue;
      }

      return query;
   }, {});

   next();
};

exports.configure = function(express, soapServerUrl, prefix) {
   prefix = prefix || "";
   if (soapServerUrl.substr(-1) !== "/") soapServerUrl += "/";
   if (prefix.substr(-1) !== "/") prefix += "/";

   express.error(function(err, req, res, next) {
      if (err instanceof CustomError) {
         res.send(err.message, err.httpStatus);
      } else {
         next(err);
      }
   });

   express.get( prefix+":service/:method",
      [resolveWsdl(soapServerUrl), resolveService, normalizeQuerystring],
      function(req, res, next) {
         req._service( req._query, function(err, result) {
            if (err) {
               res.send(err.message, 500);
               return;
            }

            var json = JSON.stringify(result);
            res.send(json, {"Content-Type": "application/json"});
         } );
      }
   );

   return express;
};
<|endoftext|>