<commit_before>import React, { useState, useEffect } from 'react';
import { c } from 'ttag';
import {
    Button,
    Table,
    TableHeader,
    TableBody,
    TableRow,
    Time,
    Pagination,
    Alert,
    Block,
    SubTitle,
    ConfirmModal,
    useApi,
    useAuthentication,
    useModals,
    useLoading,
    usePagination,
    useNotifications
} from 'react-components';
import { querySessions, revokeOtherSessions, revokeSession } from 'proton-shared/lib/api/auth';
import { ELEMENTS_PER_PAGE } from 'proton-shared/lib/constants';

import SessionAction from './SessionAction';

const getClientsI18N = () => ({
    Web: c('Badge').t`ProtonMail for web`,
    VPN: c('Badge').t`ProtonVPN for Windows`,
    WebVPN: c('Badge').t`ProtonVPN for web`,
    Admin: c('Badge').t`Admin`,
    ImportExport: c('Badge').t`ProtonMail import-export`,
    Bridge: c('Badge').t`ProtonMail Bridge`,
    iOS: c('Badge').t`ProtonMail for iOS`,
    Android: c('Badge').t`ProtonMail for Android`,
    WebMail: c('Badge').t`ProtonMail for web`,
    WebMailSettings: c('Badge').t`ProtonMail settings for web`,
    WebContacts: c('Badge').t`ProtonContacts for web`,
    WebVPNSettings: c('Badge').t`ProtonVPN settings for web`,
    WebCalendar: c('Badge').t`ProtonCalendar for web`,
    WebDrive: c('Badge').t`ProtonDrive for web`,
    // WebWallet: c('Badge').t`ProtonWallet for web`,
    WebAdmin: c('Badge').t`Admin`,
    iOSMail: c('Badge').t`ProtonMail for iOS`,
    iOSVPN: c('Badge').t`ProtonVPN for iOS`,
    iOSCalendar: c('Badge').t`ProtonCalendar for iOS`,
    AndroidMail: c('Badge').t`ProtonMail for Android`,
    AndroidVPN: c('Badge').t`ProtonVPN for Android`,
    AndroidCalendar: c('Badge').t`ProtonCalendar for Android`,
    WindowsVPN: c('Badge').t`ProtonVPN for Windows`,
    WindowsImportExport: c('Badge').t`ProtonMail import-export for Windows`,
    WindowsBridge: c('Badge').t`ProtonMail Bridge for Windows`,
    macOSVPN: c('Badge').t`ProtonVPN for macOS`,
    macOSImportExport: c('Badge').t`ProtonMail import-export for macOS`,
    macOSBridge: c('Badge').t`ProtonMail Bridge for macOS`,
    LinuxImportExport: c('Badge').t`ProtonMail import-export for Linux`,
    LinuxBridge: c('Badge').t`ProtonMail Bridge for Linux`
});

const SessionsSection = () => {
    const { createNotification } = useNotifications();
    const api = useApi();
    const authentication = useAuthentication();
    const [loading, withLoading] = useLoading();
    const [table, setTable] = useState({ sessions: [], total: 0 });
    const { page, list, onNext, onPrevious, onSelect } = usePagination(table.sessions);
    const { createModal } = useModals();

    const fetchSessions = async () => {
        const { Sessions } = await api(querySessions());
        setTable({ sessions: Sessions.reverse(), total: Sessions.length }); // Most recent, first
    };

    const handleRevoke = async (UID) => {
        await api(UID ? revokeSession(UID) : revokeOtherSessions());
        await fetchSessions();
        createNotification({ text: UID ? c('Success').t`Session revoked` : c('Success').t`Sessions revoked` });
    };

    const handleOpenModal = () => {
        createModal(
            <ConfirmModal onConfirm={() => withLoading(handleRevoke())}>
                <Alert>{c('Info').t`Do you want to revoke all other sessions than the current one?`}</Alert>
            </ConfirmModal>
        );
    };

    useEffect(() => {
        withLoading(fetchSessions());
    }, []);

    const i18n = getClientsI18N();
    const currentUID = authentication.getUID();

    return (
        <>
            <SubTitle>{c('Title').t`Sessions`}</SubTitle>
            <Alert learnMore="https://protonmail.com/support/knowledge-base/log-out-all-other-sessions/">{c('Info')
                .t`Unless you explicitly logout or change your password, sessions can last for up to 6 months. Sessions expire after 2 weeks of inactivity.`}</Alert>
            <Block className="flex flex-spacebetween">

                    <Button onClick={handleOpenModal}>{c('Action').t`Revoke all other sessions`}</Button>

                <Pagination
                    page={page}
                    total={table.total}
                    limit={ELEMENTS_PER_PAGE}
                    onNext={onNext}
                    onPrevious={onPrevious}
                    onSelect={onSelect}
                />
            </Block>

                <TableHeader cells={[c('Title').t`App`, c('Title').t`Date`, c('Title').t`Action`]} />
                <TableBody loading={loading} colSpan={3}>
                    {list.map((session) => {
                        const key = session.UID;
                        return (
                            <TableRow
                                key={key}
                                cells={[
                                    i18n[session.ClientID],
                                    <Time format="PPp" key={1}>
                                        {session.CreateTime}
                                    </Time>,
                                    <SessionAction
                                        key={2}
                                        session={session}
                                        currentUID={currentUID}
                                        onRevoke={() => withLoading(handleRevoke(session.UID))}
                                    />
                                ]}
                            />
                        );
                    })}
                </TableBody>

        </>
    );
};

export default SessionsSection;
<commit_msg>Linux is not an OS<commit_after>import React, { useState, useEffect } from 'react';
import { c } from 'ttag';
import {
    Button,
    Table,
    TableHeader,
    TableBody,
    TableRow,
    Time,
    Pagination,
    Alert,
    Block,
    SubTitle,
    ConfirmModal,
    useApi,
    useAuthentication,
    useModals,
    useLoading,
    usePagination,
    useNotifications
} from 'react-components';
import { querySessions, revokeOtherSessions, revokeSession } from 'proton-shared/lib/api/auth';
import { ELEMENTS_PER_PAGE } from 'proton-shared/lib/constants';

import SessionAction from './SessionAction';

const getClientsI18N = () => ({
    Web: c('Badge').t`ProtonMail for web`,
    VPN: c('Badge').t`ProtonVPN for Windows`,
    WebVPN: c('Badge').t`ProtonVPN for web`,
    Admin: c('Badge').t`Admin`,
    ImportExport: c('Badge').t`ProtonMail import-export`,
    Bridge: c('Badge').t`ProtonMail Bridge`,
    iOS: c('Badge').t`ProtonMail for iOS`,
    Android: c('Badge').t`ProtonMail for Android`,
    WebMail: c('Badge').t`ProtonMail for web`,
    WebMailSettings: c('Badge').t`ProtonMail settings for web`,
    WebContacts: c('Badge').t`ProtonContacts for web`,
    WebVPNSettings: c('Badge').t`ProtonVPN settings for web`,
    WebCalendar: c('Badge').t`ProtonCalendar for web`,
    WebDrive: c('Badge').t`ProtonDrive for web`,
    // WebWallet: c('Badge').t`ProtonWallet for web`,
    WebAdmin: c('Badge').t`Admin`,
    iOSMail: c('Badge').t`ProtonMail for iOS`,
    iOSVPN: c('Badge').t`ProtonVPN for iOS`,
    iOSCalendar: c('Badge').t`ProtonCalendar for iOS`,
    AndroidMail: c('Badge').t`ProtonMail for Android`,
    AndroidVPN: c('Badge').t`ProtonVPN for Android`,
    AndroidCalendar: c('Badge').t`ProtonCalendar for Android`,
    WindowsVPN: c('Badge').t`ProtonVPN for Windows`,
    WindowsImportExport: c('Badge').t`ProtonMail import-export for Windows`,
    WindowsBridge: c('Badge').t`ProtonMail Bridge for Windows`,
    macOSVPN: c('Badge').t`ProtonVPN for macOS`,
    macOSImportExport: c('Badge').t`ProtonMail import-export for macOS`,
    macOSBridge: c('Badge').t`ProtonMail Bridge for macOS`,
    LinuxImportExport: c('Badge').t`ProtonMail import-export for GNU/Linux`,
    LinuxBridge: c('Badge').t`ProtonMail Bridge for GNU/Linux`
});

const SessionsSection = () => {
    const { createNotification } = useNotifications();
    const api = useApi();
    const authentication = useAuthentication();
    const [loading, withLoading] = useLoading();
    const [table, setTable] = useState({ sessions: [], total: 0 });
    const { page, list, onNext, onPrevious, onSelect } = usePagination(table.sessions);
    const { createModal } = useModals();

    const fetchSessions = async () => {
        const { Sessions } = await api(querySessions());
        setTable({ sessions: Sessions.reverse(), total: Sessions.length }); // Most recent, first
    };

    const handleRevoke = async (UID) => {
        await api(UID ? revokeSession(UID) : revokeOtherSessions());
        await fetchSessions();
        createNotification({ text: UID ? c('Success').t`Session revoked` : c('Success').t`Sessions revoked` });
    };

    const handleOpenModal = () => {
        createModal(
            <ConfirmModal onConfirm={() => withLoading(handleRevoke())}>
                <Alert>{c('Info').t`Do you want to revoke all other sessions than the current one?`}</Alert>
            </ConfirmModal>
        );
    };

    useEffect(() => {
        withLoading(fetchSessions());
    }, []);

    const i18n = getClientsI18N();
    const currentUID = authentication.getUID();

    return (
        <>
            <SubTitle>{c('Title').t`Sessions`}</SubTitle>
            <Alert learnMore="https://protonmail.com/support/knowledge-base/log-out-all-other-sessions/">{c('Info')
                .t`Unless you explicitly logout or change your password, sessions can last for up to 6 months. Sessions expire after 2 weeks of inactivity.`}</Alert>
            <Block className="flex flex-spacebetween">

                    <Button onClick={handleOpenModal}>{c('Action').t`Revoke all other sessions`}</Button>

                <Pagination
                    page={page}
                    total={table.total}
                    limit={ELEMENTS_PER_PAGE}
                    onNext={onNext}
                    onPrevious={onPrevious}
                    onSelect={onSelect}
                />
            </Block>

                <TableHeader cells={[c('Title').t`App`, c('Title').t`Date`, c('Title').t`Action`]} />
                <TableBody loading={loading} colSpan={3}>
                    {list.map((session) => {
                        const key = session.UID;
                        return (
                            <TableRow
                                key={key}
                                cells={[
                                    i18n[session.ClientID],
                                    <Time format="PPp" key={1}>
                                        {session.CreateTime}
                                    </Time>,
                                    <SessionAction
                                        key={2}
                                        session={session}
                                        currentUID={currentUID}
                                        onRevoke={() => withLoading(handleRevoke(session.UID))}
                                    />
                                ]}
                            />
                        );
                    })}
                </TableBody>

        </>
    );
};

export default SessionsSection;
<|endoftext|>


<commit_before>// Copyright (c) 2012 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details.
/*global desc, task, jake, fail, complete, directory*/

(function() {
	"use strict";

	var startTime = Date.now();

	if (!process.env.loose) console.log("For more forgiving test settings, use 'loose=true'");

	var fs = require("fs");
	var shell = require("shelljs");

	var browserify = require("browserify");
	var lint = require("./build/util/lint_runner.js");
	var nodeunit = require("./build/util/nodeunit_runner.js");
	var karma = require("./build/util/karma_runner.js");
	var versionChecker = require("./build/util/version_checker.js");
	var runServer = require("./src/_run_server.js");
	var path = require("path");

	var REQUIRED_BROWSERS = [
		"IE 8.0.0 (Windows 7)",
		"IE 9.0.0 (Windows 7)",
		"Firefox 35.0.0 (Mac OS X 10.10)",
		"Chrome 40.0.2214 (Mac OS X 10.10.1)",
		"Safari 8.0.2 (Mac OS X 10.10.1)",
		"Mobile Safari 7.0.0 (iOS 7.1)"
	];

	var GENERATED_DIR = "generated";
	var TEMP_TESTFILE_DIR = GENERATED_DIR + "/test";
	var BUILD_DIR = GENERATED_DIR + "/build";
	var BUILD_CLIENT_DIR = BUILD_DIR + "/client";

	directory(TEMP_TESTFILE_DIR);
	directory(BUILD_DIR);
	directory(BUILD_CLIENT_DIR);

	desc("Delete all generated files");
	task("clean", [], function() {
		jake.rmRf(GENERATED_DIR);
	});

	desc("Build and test");
	task("default", ["lint", "test"], function() {
		var elapsedSeconds = (Date.now() - startTime) / 1000;
		console.log("\n\nBUILD OK (" + elapsedSeconds.toFixed(2) + "s)");
	});

	desc("Build and test fast targets only");
	task("quick", function() {
		var elapsedSeconds = (Date.now() - startTime) / 1000;
		console.log("\n\nBUILD OK (" + elapsedSeconds.toFixed(2) + "s)");
	});

	desc("Start Karma server for testing");
	task("karma", function() {
		karma.serve("build/karma.conf.js", complete, fail);
	}, {async: true});

	desc("Start WeeWikiPaint server for manual testing");
	task("run", ["build"], function() {
		console.log("Running server. Press Ctrl-C to stop.");
		runServer.runInteractively();
		// We never call complete() because we want the task to hang until the user presses 'Ctrl-C'.
	}, {async: true});

	desc("Lint everything");
	task("lint", ["lintNode", "lintClient"]);

	task("lintNode", ["nodeVersion"], function() {
		var passed = lint.validateFileList(nodeLintFiles(), nodeLintOptions(), {});
		if (!passed) fail("Lint failed");
	});

	task("lintClient", function() {
		var passed = lint.validateFileList(clientLintFiles(), clientLintOptions(), clientGlobals());
		if (!passed) fail("Lint failed");
	});

	desc("Test everything");
	task("test", ["testServer", "testClient", "testSmoke"]);

	desc("Test server code");
	task("testServer", ["nodeVersion", TEMP_TESTFILE_DIR], function() {
		nodeunit.runTests(serverTestFiles(), complete, fail);
	}, {async: true});

	desc("Test client code");
	task("testClient", [], function() {
		karma.runTests(REQUIRED_BROWSERS, complete, fail);
	}, {async: true});

	desc("End-to-end smoke tests");
	task("testSmoke", ["build"], function() {
		nodeunit.runTests(smokeTestFiles(), complete, fail);
	}, {async: true});

	desc("Bundle and build code");
	task("build", [BUILD_CLIENT_DIR], function() {
		shell.rm("-rf", BUILD_CLIENT_DIR + "/*");
		shell.cp(
			"-R",
			"src/client/*.html", "src/client/*.css", "src/client/images", "src/client/vendor",
			BUILD_CLIENT_DIR
		);

		console.log("Bundling client files with Browserify...");
		var b = browserify({ debug: true });
		b.require("./src/client/client.js", {expose: "./client.js"} );
		b.require("./src/client/html_element.js", {expose: "./html_element.js"} );
		b.bundle(function(err, bundle) {
			if (err) fail(err);
			fs.writeFileSync(BUILD_CLIENT_DIR + "/bundle.js", bundle);
			complete();
		});
	}, {async: true});

	desc("Deploy to Heroku");
	task("deploy", function() {
		console.log("To deploy to production:");
		console.log("1. Integrate ('jake integrate')");

		// Correction: Use "git push heroku integration:master" to deploy from integration branch.
		// Thanks to Jüri A, http://www.letscodejavascript.com/v3/comments/live/32#comment-798947003 .
		console.log("2. 'git push heroku integration:master' (or 'git push staging integration:master')");
		console.log("3. 'jake test'");
		console.log();
		console.log("To deploy latest code to staging server:");
		console.log("1. Make sure 'git status' is clean.");
		console.log("2. 'git push staging master");
		console.log("3. Visit http://wwp-staging.herokuapp.com/");
	});

//	desc("Ensure correct version of Node is present.");
	task("nodeVersion", [], function() {
		var deployedVersion = "v" + require("./package.json").engines.node;
		versionChecker.check("Node", !process.env.loose, deployedVersion, process.version, fail);
	});

	desc("Integration checklist");
	task("integrate", ["default"], function() {
		console.log("1. Make sure 'git status' is clean.");
		console.log("2. Build on the integration box.");
		console.log("   a. Walk over to integration box.");
		console.log("   b. 'git pull'");
		console.log("   c. 'npm rebuild'");
		console.log("   d. Check status for files that need to be .gitignore'd");
		console.log("   e. 'jake'");
		console.log("   f. If jake fails, stop! Try again after fixing the issue.");
		console.log("3. 'git checkout integration'");
		console.log("4. 'git merge master --no-ff --log'");
		console.log("5. 'git checkout master'");
	});

	desc("End-of-episode checklist");
	task("episode", [], function() {
		console.log("1. Save recording.");
		console.log("2. Double-check sound and framing.");
		console.log("3. Commit source code.");
		console.log("4. Check Windows compatibility");
		console.log("   a. Switch to Windows VM");
		console.log("   b. 'git pull'");
		console.log("   c. 'npm rebuild'");
		console.log("   d. Check status for files that need to be .gitignore'd");
		console.log("   e. 'jake'");
		console.log("5. Tag episode: 'git tag -a episodeXX -m \"End of episode XX\"'");
	});

	function serverTestFiles() {
		var testFiles = new jake.FileList();
		testFiles.include("src/server/**/_*_test.js");
		testFiles = testFiles.toArray();
		return testFiles;
	}

	function smokeTestFiles() {
		var testFiles = new jake.FileList();
		testFiles.include("src/_*_test.js");
		testFiles = testFiles.toArray();
		return testFiles;
	}

	function nodeLintFiles() {
		var javascriptFiles = new jake.FileList();
		javascriptFiles.include("*.js");
		javascriptFiles.include("build/util/*.js");
		javascriptFiles.include("src/server/**/*.js");
		javascriptFiles.include("src/*.js");
		return javascriptFiles.toArray();
	}

	function clientLintFiles() {
		var javascriptFiles = new jake.FileList();
		javascriptFiles.include("src/client/*.js");
		return javascriptFiles.toArray();
	}

	function nodeLintOptions() {
		var options = sharedLintOptions();
		options.node = true;
		return options;
	}

	function clientLintOptions() {
		var options = sharedLintOptions();
		options.browser = true;
		return options;
	}

	function sharedLintOptions() {
		return {
			bitwise: true,
			curly: false,
			eqeqeq: true,
			forin: true,
			immed: true,
			latedef: false,
			newcap: true,
			noarg: true,
			noempty: true,
			nonew: true,
			regexp: true,
			undef: true,
			strict: true,
			trailing: true
		};
	}

	function clientGlobals() {
		return {
			// Browserify
			require: false,
			module: false,
			exports: false,

			// Mocha / expect.js
			describe: false,
			it: false,
			expect: false,
			dump: false,
			beforeEach: false,
			afterEach: false,
			before: false,
			after: false,

			// Browser
			console: false
		};
	}

}());
<commit_msg>Sped up build by lazy-loading modules<commit_after>// Copyright (c) 2012 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details.
/*global desc, task, jake, fail, complete, directory*/

(function() {
	"use strict";

	var startTime = Date.now();

	if (!process.env.loose) console.log("For more forgiving test settings, use 'loose=true'");

	var REQUIRED_BROWSERS = [
		"IE 8.0.0 (Windows 7)",
		"IE 9.0.0 (Windows 7)",
		"Firefox 35.0.0 (Mac OS X 10.10)",
		"Chrome 40.0.2214 (Mac OS X 10.10.1)",
		"Safari 8.0.2 (Mac OS X 10.10.1)",
		"Mobile Safari 7.0.0 (iOS 7.1)"
	];

	var GENERATED_DIR = "generated";
	var TEMP_TESTFILE_DIR = GENERATED_DIR + "/test";
	var BUILD_DIR = GENERATED_DIR + "/build";
	var BUILD_CLIENT_DIR = BUILD_DIR + "/client";

	directory(TEMP_TESTFILE_DIR);
	directory(BUILD_DIR);
	directory(BUILD_CLIENT_DIR);

	desc("Delete all generated files");
	task("clean", [], function() {
		jake.rmRf(GENERATED_DIR);
	});

	desc("Build and test");
	task("default", ["lint", "test"], function() {
		buildOk();
	});

	desc("Build and test fast targets only");
	task("quick", function() {
		buildOk();
	});

	desc("Start Karma server for testing");
	task("karma", function() {
		karma().serve("build/karma.conf.js", complete, fail);
	}, {async: true});

	desc("Start WeeWikiPaint server for manual testing");
	task("run", ["build"], function() {
		var runServer = require("./src/_run_server.js");

		console.log("Running server. Press Ctrl-C to stop.");
		runServer.runInteractively();
		// We never call complete() because we want the task to hang until the user presses 'Ctrl-C'.
	}, {async: true});

	desc("Lint everything");
	task("lint", ["lintNode", "lintClient"]);

	task("lintNode", ["nodeVersion"], function() {
		var passed = lint().validateFileList(nodeLintFiles(), nodeLintOptions(), {});
		if (!passed) fail("Lint failed");
	});

	task("lintClient", function() {
		var passed = lint().validateFileList(clientLintFiles(), clientLintOptions(), clientGlobals());
		if (!passed) fail("Lint failed");
	});

	desc("Test everything");
	task("test", ["testServer", "testClient", "testSmoke"]);

	desc("Test server code");
	task("testServer", ["nodeVersion", TEMP_TESTFILE_DIR], function() {
		nodeunit().runTests(serverTestFiles(), complete, fail);
	}, {async: true});

	desc("Test client code");
	task("testClient", [], function() {
		karma().runTests(REQUIRED_BROWSERS, complete, fail);
	}, {async: true});

	desc("End-to-end smoke tests");
	task("testSmoke", ["build"], function() {
		nodeunit().runTests(smokeTestFiles(), complete, fail);
	}, {async: true});

	desc("Bundle and build code");
	task("build", [BUILD_CLIENT_DIR], function() {
		var fs = require("fs");
		var shell = require("shelljs");
		var browserify = require("browserify");

		shell.rm("-rf", BUILD_CLIENT_DIR + "/*");
		shell.cp(
			"-R",
			"src/client/*.html", "src/client/*.css", "src/client/images", "src/client/vendor",
			BUILD_CLIENT_DIR
		);

		console.log("Bundling client files with Browserify...");
		var b = browserify({ debug: true });
		b.require("./src/client/client.js", {expose: "./client.js"} );
		b.require("./src/client/html_element.js", {expose: "./html_element.js"} );
		b.bundle(function(err, bundle) {
			if (err) fail(err);
			fs.writeFileSync(BUILD_CLIENT_DIR + "/bundle.js", bundle);
			complete();
		});
	}, {async: true});

	desc("Deploy to Heroku");
	task("deploy", function() {
		console.log("To deploy to production:");
		console.log("1. Integrate ('jake integrate')");

		// Correction: Use "git push heroku integration:master" to deploy from integration branch.
		// Thanks to Jüri A, http://www.letscodejavascript.com/v3/comments/live/32#comment-798947003 .
		console.log("2. 'git push heroku integration:master' (or 'git push staging integration:master')");
		console.log("3. 'jake test'");
		console.log();
		console.log("To deploy latest code to staging server:");
		console.log("1. Make sure 'git status' is clean.");
		console.log("2. 'git push staging master");
		console.log("3. Visit http://wwp-staging.herokuapp.com/");
	});

//	desc("Ensure correct version of Node is present.");
	task("nodeVersion", [], function() {
		var versionChecker = require("./build/util/version_checker.js");

		var deployedVersion = "v" + require("./package.json").engines.node;
		versionChecker.check("Node", !process.env.loose, deployedVersion, process.version, fail);
	});

	desc("Integration checklist");
	task("integrate", ["default"], function() {
		console.log("1. Make sure 'git status' is clean.");
		console.log("2. Build on the integration box.");
		console.log("   a. Walk over to integration box.");
		console.log("   b. 'git pull'");
		console.log("   c. 'npm rebuild'");
		console.log("   d. Check status for files that need to be .gitignore'd");
		console.log("   e. 'jake'");
		console.log("   f. If jake fails, stop! Try again after fixing the issue.");
		console.log("3. 'git checkout integration'");
		console.log("4. 'git merge master --no-ff --log'");
		console.log("5. 'git checkout master'");
	});

	desc("End-of-episode checklist");
	task("episode", [], function() {
		console.log("1. Save recording.");
		console.log("2. Double-check sound and framing.");
		console.log("3. Commit source code.");
		console.log("4. Check Windows compatibility");
		console.log("   a. Switch to Windows VM");
		console.log("   b. 'git pull'");
		console.log("   c. 'npm rebuild'");
		console.log("   d. Check status for files that need to be .gitignore'd");
		console.log("   e. 'jake'");
		console.log("5. Tag episode: 'git tag -a episodeXX -m \"End of episode XX\"'");
	});

	function serverTestFiles() {
		var testFiles = new jake.FileList();
		testFiles.include("src/server/**/_*_test.js");
		testFiles = testFiles.toArray();
		return testFiles;
	}

	function smokeTestFiles() {
		var testFiles = new jake.FileList();
		testFiles.include("src/_*_test.js");
		testFiles = testFiles.toArray();
		return testFiles;
	}

	function nodeLintFiles() {
		var javascriptFiles = new jake.FileList();
		javascriptFiles.include("*.js");
		javascriptFiles.include("build/util/*.js");
		javascriptFiles.include("src/server/**/*.js");
		javascriptFiles.include("src/*.js");
		return javascriptFiles.toArray();
	}

	function clientLintFiles() {
		var javascriptFiles = new jake.FileList();
		javascriptFiles.include("src/client/*.js");
		return javascriptFiles.toArray();
	}

	function nodeLintOptions() {
		var options = sharedLintOptions();
		options.node = true;
		return options;
	}

	function clientLintOptions() {
		var options = sharedLintOptions();
		options.browser = true;
		return options;
	}

	function sharedLintOptions() {
		return {
			bitwise: true,
			curly: false,
			eqeqeq: true,
			forin: true,
			immed: true,
			latedef: false,
			newcap: true,
			noarg: true,
			noempty: true,
			nonew: true,
			regexp: true,
			undef: true,
			strict: true,
			trailing: true
		};
	}

	function clientGlobals() {
		return {
			// Browserify
			require: false,
			module: false,
			exports: false,

			// Mocha / expect.js
			describe: false,
			it: false,
			expect: false,
			dump: false,
			beforeEach: false,
			afterEach: false,
			before: false,
			after: false,

			// Browser
			console: false
		};
	}

	function buildOk() {
		var elapsedSeconds = (Date.now() - startTime) / 1000;
		console.log("\n\nBUILD OK (" + elapsedSeconds.toFixed(2) + "s)");
	}

	// We've factored our require statements into functions so we don't have the overhead of loading
	// modules we don't need. At the time this refactoring was done, module loading took about half a
	// second, which was 10% of our desired maximum of five seconds for a quick build.

	function lint() {
		return require("./build/util/lint_runner.js");
	}

	function nodeunit() {
		return require("./build/util/nodeunit_runner.js");
	}

	function karma() {
		return require("./build/util/karma_runner.js");
	}

}());
<|endoftext|>


<commit_before>import React from 'react'
import {storeFactory} from 'tocco-util'
import asyncRoute from '../../util/asyncRoute'

export default store => props => {
  const Component = asyncRoute(() =>
    new Promise(resolve => {
      require.ensure([], require => {
        const EntityBrowserContainer = require('./containers/EntityBrowserContainer').default

        const reducer = require('./modules').default
        const toastrReducer = require('react-redux-toastr').reducer

        const sagas = require('./modules/sagas').default

        storeFactory.injectReducers(store, {
          entityBrowser: reducer,
          toastr: toastrReducer
        })

        storeFactory.injectSaga(store, sagas)

        resolve(EntityBrowser)
      })
    })
  )
  return <EntityBrowserContainer {...props}/>
}
<commit_msg>fix(entity-browser): fix merge bug<commit_after>import React from 'react'
import {storeFactory} from 'tocco-util'
import asyncRoute from '../../util/asyncRoute'

export default store => props => {
  const Component = asyncRoute(() =>
    new Promise(resolve => {
      require.ensure([], require => {
        const EntityBrowserContainer = require('./containers/EntityBrowserContainer').default

        const reducer = require('./modules').default
        const toastrReducer = require('react-redux-toastr').reducer

        const sagas = require('./modules/sagas').default

        storeFactory.injectReducers(store, {
          entityBrowser: reducer,
          toastr: toastrReducer
        })

        storeFactory.injectSaga(store, sagas)

        resolve(EntityBrowserContainer)
      })
    })
  )
  return <Component {...props}/>
}
<|endoftext|>


<commit_before>// PUBLIC FUNCTIONS

// http://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically
vistk.utils.merge = function(d, e) {

  var obj = {},
      i = 0,
      il = arguments.length,
      key;
  for (; i < il; i++) {
      for (key in arguments[i]) {
          if (arguments[i].hasOwnProperty(key)) {
              obj[key] = arguments[i][key];
          }
      }
  }
  return obj;
};

// TODO: add accessor as argument and var_time
vistk.utils.flatten_years = function(data) {
  var flat = [];

  //for each country
  data.forEach(function(root) {

      //for each year in each country
      root.years.forEach(function(year) {
          //extend the year object with the common properties stored just once in the country object

        var current_year = vistk.utils.merge(root, year);
        delete current_year.years;

          //add it to the final flat array
          flat.push(current_year);
      })
  });

  return flat;
}

vistk.utils.find_node_by_id = function(nodes, id) {
  var res = nodes.filter(function(d) {
    return d.id == id;
  })[0];

  return res;
}

vistk.utils.find_node_coordinates_by_id = function(nodes, var_id, data_id) {

  var res = nodes.filter(function(node) {
    return node[var_id] == data_id;
  })[0];

  return res;
}

vistk.utils.max = function(data) {
  var var_time = this.var_time;
  return d3.max(data, function(d) { return d[var_time] });
}

vistk.utils.min = function(data) {
  var var_time = this.var_time;
  return d3.min(data, function(d) { return d[var_time] });
}

vistk.utils.extent = function(data, var_data, time) {
  if(typeof time === 'undefined') {
    return d3.extent(data, function(d) { return d[var_data]; });
  } else {
    return d3.extent(data, function(d) {
      return d.values[time][var_data];
    });
  }

}

vistk.utils.time = {};

vistk.utils.time.current = function(data) {
  return vars.current_time;
}

vistk.utils.decode_url = function() {

  // Variables from query string
  var queryParameters = {}, queryString = location.search.substring(1),
      re = /([^&=]+)=([^&]*)/g, m, queryActivated = false;

  // Creates a map with the query string parameters
  while (m = re.exec(queryString)) {
    queryParameters[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
  }

  return queryParameters;
}

// Credits: http://bl.ocks.org/mbostock/1705868
vistk.utils.translate_along = function(path) {
  var l = path.node().getTotalLength();
  return function(d, i, a) {
    return function(t) {
      var p = path.node().getPointAtLength(t * l);
      return "translate(" + p.x + "," + p.y + ")";
    };
  };
}

vistk.utils.find_unique_values = function(data, key) {

  var unique = {};
  var result = [];

  for(var i in data){
    if(typeof(unique[data[i][key]]) == "undefined"){
      result.push(data[i][key]);
    }
    unique[data[i][key]] = 0;
  }

  return result;

}

vistk.utils.list = function(param) {
  return list[param];
}

vistk.utils.scale = {};

vistk.utils.scale.linear = function(vars) {

  return [{
    func: d3.scale.linear()
            .range([vars.margin.left, vars.width - vars.margin.left - vars.margin.right])
            .domain([vars.margin.left, vars.width - vars.margin.left - vars.margin.right])
  }];

};

vistk.utils.scale.none = function() {

  return [{
    func: d3.scale.linear()
            .range([0, 0])
  }];

};

vistk.utils.scale.x = {};

vistk.utils.scale.x.center = function() {

  return [{
    func: d3.scale.linear()
            .range([0, 0])
  }];

};

vistk.utils.init_item = function(d) {
  d.__aggregated = false;
  d.__selected = false;
  d.__selected__adjacent = false;
  d.__highlighted = false;
  d.__highlighted__adjacent = false;
  d.__missing = false;
  d.__redraw = false;
};

// Creates
vistk.utils.aggregate = function(data, vars, var_agg, type_agg) {

  var type_agg = type_agg || 'sum';

  return d3.nest()
        .key(function(d) {

          if(vars.time.current_time === null) {
            return d[var_agg];
          } else {
            return d.values[vars.time.current_time][var_agg];
          }

        })
        .rollup(function(leaves) {

          // Generates a new dataset with aggregated data
          var aggregation = {};

          if(vars.time.current_time === null) {
            aggregation[vars.var_id] = 'agg_' + leaves[0][var_agg];
          } else {
            aggregation[vars.var_id] = 'agg_' + leaves[0].values[vars.time.current_time][var_agg] + Math.random();
          }

          // Name and id values are

          aggregation[vars.var_text] = leaves[0][vars.var_group];
          aggregation[vars.var_group] = leaves[0][vars.var_group];

          // Quick fix in case var_x is an ordinal scale
          if(vars.var_x !== vars.var_id && vars.var_x !== vars.time.var_time && vars.var_x !== vars.var_group) {

            aggregation[vars.var_x] = d3.mean(leaves, function(d) {
              return d[vars.var_x];
            });

          } else {

            aggregation[vars.var_x] = leaves[0][vars.var_x];

          }

          if(vars.var_y !== vars.var_id && vars.var_y !== vars.time.var_time) {

            aggregation[vars.var_y] = d3.mean(leaves, function(d) {
              return d[vars.var_y];
            });

          } else {

            aggregation[vars.var_y] = leaves[0][vars.var_y];

          }

          aggregation[vars.var_r] = d3.sum(leaves, function(d) {
            return d[vars.var_r];
          });

          aggregation[vars.var_share] = d3.sum(leaves, function(d) {
            return d[vars.var_share];
          });

          aggregation[vars.var_color] = leaves[0][vars.var_color];

          // Init temporal values
          aggregation.values = [];

          // Assuming all the time values are present in all items
          vars.time.points.forEach(function(time, i) {

            var d = {};

           // if(vars.var_x === vars.time.var_time) {
           //   d[vars.var_x] = leaves[0].values[i][vars.var_x];
           // } else {
           //   d[vars.var_x] = 0;
           // }

            // Init values
            d[vars.var_x] = leaves[0].values[time][vars.var_x];
            d[vars.var_y] = leaves[0].values[time][vars.var_y];
            d[vars.var_r] = leaves[0].values[time][vars.var_r];

            d[vars.var_share] = leaves[0].values[time][vars.var_share];
            d[vars.var_color] = leaves[0].values[time][vars.var_color];
            d[vars.var_size] = leaves[0].values[time][vars.var_size];
            d[vars.var_text] = leaves[0].values[time][vars.var_text];

            // Time var
            d[vars.time.var_time] = leaves[0].values[time][vars.time.var_time];

            // Should stay at the bottom to make sure it's not overriden
            d[vars.var_id] = 'agg_' + leaves[0].values[time][vars.var_id];

            d.__index = 'agg_' + leaves[0].values[time].__index;

            aggregation.values[time] = d;

            //return d;
          });

          // Assuming we only aggregate var_x, var_y, var_r
          leaves.forEach(function(d, i) {

            vars.time.points.forEach(function(time, i) {

              if(vars.var_x !== vars.time.var_time && vars.var_x !== vars.var_group) {
                aggregation.values[time][vars.var_x] += d.values[time][vars.var_x];
              }

              if(vars.var_y !== vars.time.var_time && vars.var_y !== vars.var_group) {
                aggregation.values[time][vars.var_y] += d.values[time][vars.var_y];
              }

              aggregation.values[time][vars.var_r] += d.values[time][vars.var_r];

            });

          });

          vistk.utils.init_item(aggregation);

          // Set internal attributes specific to aggregate + force redraw
          aggregation.__aggregated = true;
          aggregation.__redraw = true;

          vars.columns.forEach(function(c) {

            // Do no aggreagete ordinal values
            if(c === vars.var_text || c === vars.var_group) {
              return;
            }

            aggregation[c] = d3.mean(leaves, function(d) {
              return d[c];
            });

          });

          return aggregation;
        })
        .entries(data);

}

vistk.utils.colors = {};

vistk.utils.colors.products_hs4 = d3.scale.ordinal().domain([0, 9]).range(["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"]);
vistk.utils.colors.world_regions = d3.scale.ordinal().domain(["Africa", "Americas", "Asia", "Europe", "Oceania"]).range(["#99237d", "#c72439", "#6bc145", "#88c7ed", "#dd9f98"]);

vistk.utils.generator = {};
vistk.utils.generator.random = function(nb_items, nb_time_points) {
  return Array.prototype.concat.apply([], d3.range(nb_items).map(function(item){
    return d3.range(nb_time_points).map(function(d, time){
      return {item: item, time: time, y: Math.random()*100 };
    });
  }));
}
<commit_msg>Flatten values function<commit_after>// PUBLIC FUNCTIONS

// http://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically
vistk.utils.merge = function(d, e) {

  var obj = {},
      i = 0,
      il = arguments.length,
      key;
  for (; i < il; i++) {
      for (key in arguments[i]) {
          if (arguments[i].hasOwnProperty(key)) {
              obj[key] = arguments[i][key];
          }
      }
  }
  return obj;
};

// TODO: add accessor as argument and var_time
vistk.utils.flatten_years = function(data) {
  var flat = [];

  //for each country
  data.forEach(function(root) {

      //for each year in each country
      root.years.forEach(function(year) {
          //extend the year object with the common properties stored just once in the country object

        var current_year = vistk.utils.merge(root, year);
        delete current_year.years;

          //add it to the final flat array
          flat.push(current_year);
      })
  });

  return flat;
}

vistk.utils.flatten_values = function(data, attr) {

  return Array.prototype.concat.apply([], data.map(function(d) {
              return d3.values(d.values);
            })).map(function(d) {
              return d[attr];
            });

}

vistk.utils.find_node_by_id = function(nodes, id) {
  var res = nodes.filter(function(d) {
    return d.id == id;
  })[0];

  return res;
}

vistk.utils.find_node_coordinates_by_id = function(nodes, var_id, data_id) {

  var res = nodes.filter(function(node) {
    return node[var_id] == data_id;
  })[0];

  return res;
}

vistk.utils.max = function(data) {
  var var_time = this.var_time;
  return d3.max(data, function(d) { return d[var_time] });
}

vistk.utils.min = function(data) {
  var var_time = this.var_time;
  return d3.min(data, function(d) { return d[var_time] });
}

vistk.utils.extent = function(data, var_data, time) {
  if(typeof time === 'undefined') {
    return d3.extent(data, function(d) { return d[var_data]; });
  } else {
    return d3.extent(data, function(d) {
      return d.values[time][var_data];
    });
  }

}

vistk.utils.time = {};

vistk.utils.time.current = function(data) {
  return vars.current_time;
}

vistk.utils.decode_url = function() {

  // Variables from query string
  var queryParameters = {}, queryString = location.search.substring(1),
      re = /([^&=]+)=([^&]*)/g, m, queryActivated = false;

  // Creates a map with the query string parameters
  while (m = re.exec(queryString)) {
    queryParameters[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
  }

  return queryParameters;
}

// Credits: http://bl.ocks.org/mbostock/1705868
vistk.utils.translate_along = function(path) {
  var l = path.node().getTotalLength();
  return function(d, i, a) {
    return function(t) {
      var p = path.node().getPointAtLength(t * l);
      return "translate(" + p.x + "," + p.y + ")";
    };
  };
}

vistk.utils.find_unique_values = function(data, key) {

  var unique = {};
  var result = [];

  for(var i in data){
    if(typeof(unique[data[i][key]]) == "undefined"){
      result.push(data[i][key]);
    }
    unique[data[i][key]] = 0;
  }

  return result;

}

vistk.utils.list = function(param) {
  return list[param];
}

vistk.utils.scale = {};

vistk.utils.scale.linear = function(vars) {

  return [{
    func: d3.scale.linear()
            .range([vars.margin.left, vars.width - vars.margin.left - vars.margin.right])
            .domain([vars.margin.left, vars.width - vars.margin.left - vars.margin.right])
  }];

};

vistk.utils.scale.none = function() {

  return [{
    func: d3.scale.linear()
            .range([0, 0])
  }];

};

vistk.utils.scale.x = {};

vistk.utils.scale.x.center = function() {

  return [{
    func: d3.scale.linear()
            .range([0, 0])
  }];

};

vistk.utils.init_item = function(d) {
  d.__aggregated = false;
  d.__selected = false;
  d.__selected__adjacent = false;
  d.__highlighted = false;
  d.__highlighted__adjacent = false;
  d.__missing = false;
  d.__redraw = false;
};

// Creates
vistk.utils.aggregate = function(data, vars, var_agg, type_agg) {

  var type_agg = type_agg || 'sum';

  return d3.nest()
        .key(function(d) {

          if(vars.time.current_time === null) {
            return d[var_agg];
          } else {
            return d.values[vars.time.current_time][var_agg];
          }

        })
        .rollup(function(leaves) {

          // Generates a new dataset with aggregated data
          var aggregation = {};

          if(vars.time.current_time === null) {
            aggregation[vars.var_id] = 'agg_' + leaves[0][var_agg];
          } else {
            aggregation[vars.var_id] = 'agg_' + leaves[0].values[vars.time.current_time][var_agg] + Math.random();
          }

          // Name and id values are

          aggregation[vars.var_text] = leaves[0][vars.var_group];
          aggregation[vars.var_group] = leaves[0][vars.var_group];

          // Quick fix in case var_x is an ordinal scale
          if(vars.var_x !== vars.var_id && vars.var_x !== vars.time.var_time && vars.var_x !== vars.var_group) {

            aggregation[vars.var_x] = d3.mean(leaves, function(d) {
              return d[vars.var_x];
            });

          } else {

            aggregation[vars.var_x] = leaves[0][vars.var_x];

          }

          if(vars.var_y !== vars.var_id && vars.var_y !== vars.time.var_time) {

            aggregation[vars.var_y] = d3.mean(leaves, function(d) {
              return d[vars.var_y];
            });

          } else {

            aggregation[vars.var_y] = leaves[0][vars.var_y];

          }

          aggregation[vars.var_r] = d3.sum(leaves, function(d) {
            return d[vars.var_r];
          });

          aggregation[vars.var_share] = d3.sum(leaves, function(d) {
            return d[vars.var_share];
          });

          aggregation[vars.var_color] = leaves[0][vars.var_color];

          // Init temporal values
          aggregation.values = [];

          // Assuming all the time values are present in all items
          vars.time.points.forEach(function(time, i) {

            var d = {};

           // if(vars.var_x === vars.time.var_time) {
           //   d[vars.var_x] = leaves[0].values[i][vars.var_x];
           // } else {
           //   d[vars.var_x] = 0;
           // }

            // Init values
            d[vars.var_x] = leaves[0].values[time][vars.var_x];
            d[vars.var_y] = leaves[0].values[time][vars.var_y];
            d[vars.var_r] = leaves[0].values[time][vars.var_r];

            d[vars.var_share] = leaves[0].values[time][vars.var_share];
            d[vars.var_color] = leaves[0].values[time][vars.var_color];
            d[vars.var_size] = leaves[0].values[time][vars.var_size];
            d[vars.var_text] = leaves[0].values[time][vars.var_text];

            // Time var
            d[vars.time.var_time] = leaves[0].values[time][vars.time.var_time];

            // Should stay at the bottom to make sure it's not overriden
            d[vars.var_id] = 'agg_' + leaves[0].values[time][vars.var_id];

            d.__index = 'agg_' + leaves[0].values[time].__index;

            aggregation.values[time] = d;

            //return d;
          });

          // Assuming we only aggregate var_x, var_y, var_r
          leaves.forEach(function(d, i) {

            vars.time.points.forEach(function(time, i) {

              if(vars.var_x !== vars.time.var_time && vars.var_x !== vars.var_group) {
                aggregation.values[time][vars.var_x] += d.values[time][vars.var_x];
              }

              if(vars.var_y !== vars.time.var_time && vars.var_y !== vars.var_group) {
                aggregation.values[time][vars.var_y] += d.values[time][vars.var_y];
              }

              aggregation.values[time][vars.var_r] += d.values[time][vars.var_r];

            });

          });

          vistk.utils.init_item(aggregation);

          // Set internal attributes specific to aggregate + force redraw
          aggregation.__aggregated = true;
          aggregation.__redraw = true;

          vars.columns.forEach(function(c) {

            // Do no aggreagete ordinal values
            if(c === vars.var_text || c === vars.var_group) {
              return;
            }

            aggregation[c] = d3.mean(leaves, function(d) {
              return d[c];
            });

          });

          return aggregation;
        })
        .entries(data);

}

vistk.utils.colors = {};

vistk.utils.colors.products_hs4 = d3.scale.ordinal().domain([0, 9]).range(["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"]);
vistk.utils.colors.world_regions = d3.scale.ordinal().domain(["Africa", "Americas", "Asia", "Europe", "Oceania"]).range(["#99237d", "#c72439", "#6bc145", "#88c7ed", "#dd9f98"]);

vistk.utils.generator = {};
vistk.utils.generator.random = function(nb_items, nb_time_points) {
  return Array.prototype.concat.apply([], d3.range(nb_items).map(function(item){
    return d3.range(nb_time_points).map(function(d, time){
      return {item: item, time: time, y: Math.random()*100 };
    });
  }));
}
<|endoftext|>


<commit_before>var eventHandling = require('./eventhandling');

var mode = typeof(window) !== 'undefined' ? 'browser' : 'node',
    env = {},
    isBackground = false;

var Env = function () {
  return env;
};

Env.isBrowser = function () {
  return mode === "browser";
};

Env.isNode = function () {
  return mode === "node";
};

Env.goBackground = function () {
  isBackground = true;
  Env._emit("background");
};

Env.goForeground = function () {
  isBackground = false;
  Env._emit("foreground");
};

Env._rs_init = function (remoteStorage) {
  eventHandling(Env, "background", "foreground");

  function visibility() {
    if (document[env.hiddenProperty]) {
      Env.goBackground();
    } else {
      Env.goForeground();
    }
  }

  if ( mode === 'browser') {
    if ( typeof(document.hidden) !== "undefined" ) {
      env.hiddenProperty = "hidden";
      env.visibilityChangeEvent = "visibilitychange";
    } else if ( typeof(document.mozHidden) !== "undefined" ) {
      env.hiddenProperty = "mozHidden";
      env.visibilityChangeEvent = "mozvisibilitychange";
    } else if ( typeof(document.msHidden) !== "undefined" ) {
      env.hiddenProperty = "msHidden";
      env.visibilityChangeEvent = "msvisibilitychange";
    } else if ( typeof(document.webkitHidden) !== "undefined" ) {
      env.hiddenProperty = "webkitHidden";
      env.visibilityChangeEvent = "webkitvisibilitychange";
    }
    document.addEventListener(env.visibilityChangeEvent, visibility, false);
    visibility();
  }
};

Env._rs_cleanup = function (remoteStorage) {
};

module.exports = Env;
<commit_msg>Remove unused variables and fix some formatting<commit_after>const eventHandling = require('./eventhandling');

const mode = typeof(window) !== 'undefined' ? 'browser' : 'node',
    env = {};

const Env = function () {
  return env;
};

Env.isBrowser = function () {
  return mode === "browser";
};

Env.isNode = function () {
  return mode === "node";
};

Env.goBackground = function () {
  Env._emit("background");
};

Env.goForeground = function () {
  Env._emit("foreground");
};

Env._rs_init = function (/* remoteStorage */) {
  eventHandling(Env, "background", "foreground");

  function visibility () {
    if (document[env.hiddenProperty]) {
      Env.goBackground();
    } else {
      Env.goForeground();
    }
  }

  if (mode === 'browser') {
    if (typeof(document.hidden) !== "undefined") {
      env.hiddenProperty = "hidden";
      env.visibilityChangeEvent = "visibilitychange";
    } else if (typeof(document.mozHidden) !== "undefined") {
      env.hiddenProperty = "mozHidden";
      env.visibilityChangeEvent = "mozvisibilitychange";
    } else if (typeof(document.msHidden) !== "undefined") {
      env.hiddenProperty = "msHidden";
      env.visibilityChangeEvent = "msvisibilitychange";
    } else if (typeof(document.webkitHidden) !== "undefined") {
      env.hiddenProperty = "webkitHidden";
      env.visibilityChangeEvent = "webkitvisibilitychange";
    }
    document.addEventListener(env.visibilityChangeEvent, visibility, false);
    visibility();
  }
};

Env._rs_cleanup = function (/* remoteStorage */) {
};

module.exports = Env;
<|endoftext|>


<commit_before>import {h} from 'preact';

export default function AboutHome() {
  return (

About Home

  );
}<commit_msg>About text rev 1.<commit_after>import {h} from 'preact';

export default function AboutHome() {
  return (

Simple Hacker News Clone

This is an example of a PWA built using Preact, and some opinionated tools.

Please do not build an application this way. Instead view this as an example of some concepts used in modern web applications (sw, h2, h2push).

Made with kindness in California.

  );
}<|endoftext|>


<commit_before>//an AMD template engine that uses the text plugin to pull templates
(function(ko, require) {
    //get a new native template engine to start with
    var engine = new ko.nativeTemplateEngine(),
        sources = {};

    engine.defaultPath = "templates";
    engine.defaultSuffix = ".tmpl.html";
    engine.defaultRequireTextPluginName = "text";

    //create a template source that loads its template using the require.js text plugin
    ko.templateSources.requireTemplate = function(key) {
        this.key = key;
        this.template = ko.observable(" "); //content has to be non-falsey to start with
        this.requested = false;
    };

    ko.templateSources.requireTemplate.prototype.text = function(value) {
        //when the template is retrieved, check if we need to load it
        if (!this.requested && this.key) {
            require([engine.defaultRequireTextPluginName + "!" + addTrailingSlash(engine.defaultPath) + this.key + engine.defaultSuffix], this.template);
            this.requested = true;
        }

        //if template is currently empty, then clear it
        if (!this.key) {
            this.template("");
        }

        //always return the current template
        if (arguments.length === 0) {
            return this.template();
        }
    };

    //our engine needs to understand when to create a "requireTemplate" template source
    engine.makeTemplateSource = function(template, doc) {
        var el;

        //if a name is specified, then use the
        if (typeof template === "string") {
            //if there is an element with this id and it is a script tag, then use it
            el = (doc || document).getElementById(template);

            if (el && el.tagName.toLowerCase() === "script") {
                return new ko.templateSources.domElement(el);
            }

            //otherwise pull the template in using the AMD loader's text plugin
            if (!(template in sources)) {
                sources[template] = new ko.templateSources.requireTemplate(template);
            }

            //keep a single template source instance for each key, so everyone depends on the same observable
            return sources[template];
        }
        //if there is no name (foreach/with) use the elements as the template, as normal
        else if (template && (template.nodeType === 1 || template.nodeType === 8)) {
            return new ko.templateSources.anonymousTemplate(template);
        }
    };

    //expose the template engine at least to be able to customize the path/suffix/plugin at run-time
    ko.amdTemplateEngine = engine;

    //make this new template engine our default engine
    ko.setTemplateEngine(engine);

})(ko, require);<commit_msg>wrap afterRender callback, so it is not called until template is ready<commit_after>//an AMD template engine that uses the text plugin to pull templates
(function(ko, require) {
    //get a new native template engine to start with
    var engine = new ko.nativeTemplateEngine(),
        existingRenderTemplate = engine.renderTemplate,
        sources = {};

    engine.defaultPath = "templates";
    engine.defaultSuffix = ".tmpl.html";
    engine.defaultRequireTextPluginName = "text";

    //create a template source that loads its template using the require.js text plugin
    ko.templateSources.requireTemplate = function(key) {
        this.key = key;
        this.template = ko.observable(" "); //content has to be non-falsey to start with
        this.requested = false;
        this.retrieved = false;
    };

    ko.templateSources.requireTemplate.prototype.text = function(value) {
        //when the template is retrieved, check if we need to load it
        if (!this.requested && this.key) {
            require([engine.defaultRequireTextPluginName + "!" + addTrailingSlash(engine.defaultPath) + this.key + engine.defaultSuffix], function(templateContent) {
                this.retrieved = true;
                this.template(templateContent);
            }.bind(this));

            this.requested = true;
        }

        //if template is currently empty, then clear it
        if (!this.key) {
            this.template("");
        }

        //always return the current template
        if (arguments.length === 0) {
            return this.template();
        }
    };

    //our engine needs to understand when to create a "requireTemplate" template source
    engine.makeTemplateSource = function(template, doc) {
        var el;

        //if a name is specified, then use the
        if (typeof template === "string") {
            //if there is an element with this id and it is a script tag, then use it
            el = (doc || document).getElementById(template);

            if (el && el.tagName.toLowerCase() === "script") {
                return new ko.templateSources.domElement(el);
            }

            //otherwise pull the template in using the AMD loader's text plugin
            if (!(template in sources)) {
                sources[template] = new ko.templateSources.requireTemplate(template);
            }

            //keep a single template source instance for each key, so everyone depends on the same observable
            return sources[template];
        }
        //if there is no name (foreach/with) use the elements as the template, as normal
        else if (template && (template.nodeType === 1 || template.nodeType === 8)) {
            return new ko.templateSources.anonymousTemplate(template);
        }
    };

    //override renderTemplate to properly handle afterRender prior to template being available
    engine.renderTemplate = function(template, bindingContext, options, templateDocument) {
        var templateSource = engine.makeTemplateSource(template, templateDocument),
            existingAfterRender = options && options.afterRender;

        //wrap the existing afterRender, so it is not called until template is actuall retrieved
        if (typeof existingAfterRender === "function" && templateSource instanceof ko.templateSources.requireTemplate) {
            options.afterRender = function() {
                if (templateSource.retrieved) {
                    existingAfterRender.apply(this, arguments);
                }
            };
        }

        return engine.renderTemplateSource(templateSource, bindingContext, options);
    };

    //expose the template engine at least to be able to customize the path/suffix/plugin at run-time
    ko.amdTemplateEngine = engine;

    //make this new template engine our default engine
    ko.setTemplateEngine(engine);

})(ko, require);<|endoftext|>


<commit_before>var util = require('./util');

var initial = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR';

var roles = {
  p: 'pawn',
  r: 'rook',
  n: 'knight',
  b: 'bishop',
  q: 'queen',
  k: 'king'
};

var letters = {
  pawn: 'p',
  rook: 'r',
  knight: 'n',
  bishop: 'b',
  queen: 'q',
  king: 'k'
};

function read(fen) {
  if (fen === 'start') fen = initial;
  var pieces = {};
  var space = fen.indexOf(' ');
  var first = space !== -1 ? fen.substr(0, space) : fen;
  var parts = first.split('/');
  for (var i = 0, len = parts.length; i < len; i++) {
    var row = parts[i];
    var x = 0;
    for (var j = 0, jlen = row.length; j < jlen; j++) {
      var v = row[j];
      if (v === '~') continue;
      var nb = parseInt(v, 10);
      if (nb) x += nb;
      else {
        x++;
        pieces[util.pos2key([x, 8 - i])] = {
          role: roles[v.toLowerCase()],
          color: v === v.toLowerCase() ? 'black' : 'white'
        };
      }
    }
  }

  return pieces;
}

function write(pieces) {
  return [8, 7, 6, 5, 4, 3, 2].reduce(
    function(str, nb) {
      return str.replace(new RegExp(Array(nb + 1).join('1'), 'g'), nb);
    },
    util.invRanks.map(function(y) {
      return util.ranks.map(function(x) {
        var piece = pieces[util.pos2key([x, y])];
        if (piece) {
          var letter = letters[piece.role];
          return piece.color === 'white' ? letter.toUpperCase() : letter;
        } else return '1';
      }).join('');
    }).join('/'));
}

module.exports = {
  initial: initial,
  read: read,
  write: write
};
<commit_msg>More performant fen reading<commit_after>var util = require('./util');

var initial = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR';

var roles = {
  p: 'pawn',
  r: 'rook',
  n: 'knight',
  b: 'bishop',
  q: 'queen',
  k: 'king',
  P: 'pawn',
  R: 'rook',
  N: 'knight',
  B: 'bishop',
  Q: 'queen',
  K: 'king'
};

var letters = {
  pawn: 'p',
  rook: 'r',
  knight: 'n',
  bishop: 'b',
  queen: 'q',
  king: 'k'
};

function read(fen) {
  if (fen === 'start') fen = initial;
  var pieces = {};
  var space = fen.indexOf(' ');
  var first = space !== -1 ? fen.substr(0, space) : fen;
  var parts = first.split('/');
  for (var i = 0, len = parts.length; i < len; i++) {
    var row = parts[i];
    var x = 0;
    for (var j = 0, jlen = row.length; j < jlen; j++) {
      var v = row[j];
      if (v === '~') continue;
      var nb = ~~v;
      if (nb) x += nb;
      else {
        x++;
        pieces[util.pos2key([x, 8 - i])] = {
          role: roles[v],
          color: v === v.toLowerCase() ? 'black' : 'white'
        };
      }
    }
  }

  return pieces;
}

function write(pieces) {
  return [8, 7, 6, 5, 4, 3, 2].reduce(
    function(str, nb) {
      return str.replace(new RegExp(Array(nb + 1).join('1'), 'g'), nb);
    },
    util.invRanks.map(function(y) {
      return util.ranks.map(function(x) {
        var piece = pieces[util.pos2key([x, y])];
        if (piece) {
          var letter = letters[piece.role];
          return piece.color === 'white' ? letter.toUpperCase() : letter;
        } else return '1';
      }).join('');
    }).join('/'));
}

module.exports = {
  initial: initial,
  read: read,
  write: write
};
<|endoftext|>


<commit_before>//custom code folding (aka "outlining") for CodeMirror
//The built-in implementation doesn't show continuation lines, it only shows [▼] or [▶] at fold start.
//https://codemirror.net/demo/folding.html
;(function(){'use strict'

CM.defineOption('foldGutter',false,function(cm,v,o){ //v:new value, o:old value
  if(o&&o!==CM.Init){cm.clearGutter('cm-foldgutter');cm.state.foldGutter=null;for(var k in eh)cm.off(k,eh[k])}
  if(v){cm.state.foldGutter={from:0,to:0};updInViewport(cm);for(var k in eh)cm.on(k,eh[k])}
})
function getFold(cm,l){var m=cm.findMarksAt(CM.Pos(l))
                       for(var i=0;i<m.length;i++)if(m[i].__isFold&&m[i].find().from.line===l)return m[i]}
function updMarkers(cm,a,b){ //a..b:from..to line - ignored, we always update the whole editor
  //there's room for optimisation here though performance seems good enough for now
  a=0;b=cm.lineCount();var f=cm.foldOption({},'rangeFinder'),e=[],i=a //e:ends, i:current line
  cm.eachLine(a,b,function(lh){var t='folded' //t:marker type (folded,open,end,cont with corresponding css classes)
    if(!getFold(cm,i)){var r=f&&f(cm,CM.Pos(i,0)) //r:range
                       if(r){var t='open';e.push(r.to.line)}
                       else if(e.indexOf(i)>=0){var t='end',j=0;while(j<e.length)e[j]>i?j++:e.splice(j,1)}
                       else if(e.length){var t='cont'}
                       else{t=''}}
    i++;if(t){var m=document.createElement('div') //marker
              m.className='cm-foldgutter-'+t+' CodeMirror-guttermarker-subtle';cm.setGutterMarker(lh,'cm-foldgutter',m)}
  })
}
var eh={} //event handlers
var updInViewport=eh.swapDoc=function(cm){
  var vp=cm.getViewport(),h=cm.state.foldGutter
  if(h){cm.operation(function(){updMarkers(cm,vp.from,vp.to)});h.from=vp.from;h.to=vp.to}
}
eh.gutterClick=function(cm,l,g){ //l:line,g:gutter
  if(cm.state.foldGutter&&g==='cm-foldgutter'){var u=getFold(cm,l);u?u.clear():cm.foldCode(CM.Pos(l,0))}
}
eh.change=function(cm){var h=cm.state.foldGutter;if(!h)return
                       h.from=h.to=0;clearTimeout(h.tid);h.tid=setTimeout(function(){updInViewport(cm)},600)}
eh.viewportChange=function(cm){
  var h=cm.state.foldGutter;if(!h)return
  clearTimeout(h.tid);h.tid=setTimeout(function(){
    var vp=cm.getViewport(), c=h.from===h.to||vp.from-h.to>20||h.from-vp.to>20
    c?updInViewport(cm):cm.operation(function(){if(vp.from<h.from){updMarkers(cm,vp.from,h.from);h.from=vp.from}
                                                if(vp.to  >h.to  ){updMarkers(cm,h.to   ,vp.to );h.to  =vp.to  }})
  },100)
}
eh.fold=eh.unfold=function(cm,lh){var h=cm.state.foldGutter;if(h){var l=lh.line;h.from<=l&&l<h.to&&updMarkers(cm,l,l+1)}}

}())
<commit_msg>shorter code in src/fld.js<commit_after>//custom code folding (aka "outlining") for CodeMirror
//The built-in implementation doesn't show continuation lines, it only shows [▼] or [▶] at fold start.
//https://codemirror.net/demo/folding.html
;(function(){'use strict'

CM.defineOption('foldGutter',false,function(cm,v,o){ //v:new value, o:old value
  if(o&&o!==CM.Init){cm.clearGutter('cm-foldgutter');cm.state.foldGutter=null;for(var k in eh)cm.off(k,eh[k])}
  if(v){cm.state.foldGutter={from:0,to:0};eh.swapDoc(cm);for(var k in eh)cm.on(k,eh[k])}
})
function getFold(cm,l){var m=cm.findMarksAt(CM.Pos(l))
                       for(var i=0;i<m.length;i++)if(m[i].__isFold&&m[i].find().from.line===l)return m[i]}
function updMarkers(cm,a,b){ //a..b:from..to line - ignored, we always update the whole editor
  //there's room for optimisation here though performance seems good enough for now
  a=0;b=cm.lineCount();var f=cm.foldOption({},'rangeFinder'),e=[],i=a //e:ends, i:current line
  cm.eachLine(a,b,function(lh){var t='folded' //t:marker type (folded,open,end,cont with corresponding css classes)
    if(!getFold(cm,i)){var r=f&&f(cm,CM.Pos(i,0)) //r:range
                       if(r){var t='open';e.push(r.to.line)}
                       else if(e.indexOf(i)>=0){var t='end',j=0;while(j<e.length)e[j]>i?j++:e.splice(j,1)}
                       else if(e.length){var t='cont'}
                       else{t=''}}
    i++;if(t){var m=document.createElement('div') //marker
              m.className='cm-foldgutter-'+t+' CodeMirror-guttermarker-subtle';cm.setGutterMarker(lh,'cm-foldgutter',m)}
  })
}
var eh={} //event handlers
eh.swapDoc=function(cm){
  var vp=cm.getViewport(),h=cm.state.foldGutter
  if(h){cm.operation(function(){updMarkers(cm,vp.from,vp.to)});h.from=vp.from;h.to=vp.to}
}
eh.gutterClick=function(cm,l,g){ //l:line,g:gutter
  if(cm.state.foldGutter&&g==='cm-foldgutter'){var u=getFold(cm,l);u?u.clear():cm.foldCode(CM.Pos(l,0))}
}
eh.change=function(cm){var h=cm.state.foldGutter;if(!h)return
                       h.from=h.to=0;clearTimeout(h.tid);h.tid=setTimeout(function(){eh.swapDoc(cm)},600)}
eh.viewportChange=function(cm){
  var h=cm.state.foldGutter;if(!h)return
  clearTimeout(h.tid);h.tid=setTimeout(function(){
    var vp=cm.getViewport(), c=h.from===h.to||vp.from-h.to>20||h.from-vp.to>20
    c?eh.swapDoc(cm):cm.operation(function(){if(vp.from<h.from){updMarkers(cm,vp.from,h.from);h.from=vp.from}
                                             if(vp.to  >h.to  ){updMarkers(cm,h.to   ,vp.to );h.to  =vp.to  }})
  },100)
}
eh.fold=eh.unfold=function(cm,lh){var h=cm.state.foldGutter;if(h){var l=lh.line;h.from<=l&&l<h.to&&updMarkers(cm,l,l+1)}}

}())
<|endoftext|>


<commit_before>;(function(angular){
  'use strict';
  var indexOf = [].indexOf;
  angular.module('iOffice.pickadate', [])
    .factory('pickadateUtils', ['dateFilter', function(dateFilter) {
      return {
        isDate: function(obj) {
          return Object.prototype.toString.call(obj) === '[object Date]';
        },

        stringToDate: function(dateString) {
          if (this.isDate(dateString)) return new Date(dateString);
          return this.stringToMoment(dateString).toDate();

        },

        stringToMoment: function(dateString) {
          return moment(dateString, 'YYYY-MM-DD').hour(3);
        }
      };
    }])

    .directive('pickadate', ['$locale', 'pickadateUtils', 'dateFilter', function($locale, dateUtils, dateFilter) {
      return {
        require: 'ngModel',
        scope: {
          selectedDates: '=ngModel',
          defaultDate: '=',
          minDate: '=',
          maxDate: '=',
          disabledDates: '=',
          range: '='
        },
        template:
          '
' +
            '
' +
              '
' +
                'prev' +
                'next' +
              '
'+
              '
' +
                '{{currentDate | date:"MMMM yyyy"}}' +
              '
' +
            '
' +
            '
' +
              '
' +
                '
' +
                  '
' +
                    '{{dayName}}' +
                  '
' +
                '
' +
                '
' +
                  '
' +
                    '{{d.date | date:"d"}}' +
                  '
' +
                '
' +
              '
' +
            '
' +
          '
',

        link: function(scope, element, attrs, ngModel)  {
          var minDate       = scope.minDate && dateUtils.stringToMoment(scope.minDate, 'YYYY-MM-DD'),
              maxDate       = scope.maxDate && dateUtils.stringToMoment(scope.maxDate, 'YYYY-MM-DD'),
              disabledDates = scope.disabledDates || [],
              currentDate   = (scope.defaultDate && dateUtils.stringToDate(scope.defaultDate)) || new Date(),
              range         = scope.range || false;

          scope.dayNames    = $locale.DATETIME_FORMATS.SHORTDAY;
          scope.currentDate = currentDate;

          scope.render = function() {
            var initialDate = currentDate;

            var startOfMonth = moment(initialDate).startOf('month');
            var endOfMonth = moment(initialDate).endOf('month');
            var calStart = moment(startOfMonth).startOf('week');
            var calEnd = moment(endOfMonth).endOf('week');

            var dates = [];

            if (calStart.twix(calEnd).count('days') / 7 < 6) {
              calEnd = moment(calEnd).add(1, 'week');
            }

            scope.allowPrevMonth = !minDate || minDate.isBefore(startOfMonth, 'd');
            scope.allowNextMonth = !maxDate || maxDate.isAfter(endOfMonth, 'd');

            var calendarRange = calStart.twix(calEnd);
            var iter = calendarRange.iterate('days');

            do {
              var className = "",
                date = iter.next(),
                formattedDate = date.format('YYYY-MM-DD');

              if ((minDate && date.isBefore(minDate, 'd')) || (maxDate && date.isAfter(maxDate, 'd'))) {
                className = 'pickadate-disabled';
              } else if (indexOf.call(disabledDates, formattedDate) >= 0) {
                className = 'pickadate-disabled pickadate-unavailable';
              } else {
                className = 'pickadate-enabled';
              }

              if (date.isSame(moment(), 'd')) {
                className += ' pickadate-today';
              }

              dates.push({
                date: formattedDate,
                className: className
              });
            } while(iter.hasNext());

            scope.dates = dates;
          };

          var assignDates = function(dates) {
            if (!dates || Object.keys(dates).length === 0) ngModel.$setViewValue({});
            ngModel.$setViewValue(dates);
          };

          scope.setDate = function(dateObj) {
            if (isDateDisabled(dateObj)) return;
             if (Object.keys(dateObj).length === 0) ngModel.$setViewValue({});

            var dates = scope.selectedDates;
            var start = dateUtils.stringToMoment(dates.start);
            var end = dateUtils.stringToMoment(dates.end);
            var selectedDate = dateUtils.stringToMoment(dateObj.date);
            var newSelection = {};

            var rangeContainsDisabledDate = function() {
              var selectedStart = dateUtils.stringToMoment(newSelection.start),
                selectedEnd = dateUtils.stringToMoment(newSelection.end);

              return !(disabledDates.every(function(date) {
                return !selectedStart.twix(selectedEnd).contains(moment(date, 'YYYY-MM-DD'));
              }));
            }

            // We already have a start date
            if (dates.start
              && (dates.start === dates.end)
              && selectedDate.isAfter(start, 'd')
              && !selectedDate.isSame(start, 'd')) {
              newSelection.start = dates.start;
              newSelection.end = dateObj.date;
            } else {
              newSelection.start = dateObj.date;
              newSelection.end = dateObj.date;
            }

            if (!rangeContainsDisabledDate()) {
              assignDates(newSelection);
            }
          };

          scope.getDateClasses = function(d) {
            var classesToApply = [];
            var selectedDates = scope.selectedDates;
            var start = dateUtils.stringToMoment(selectedDates.start);
            var end = dateUtils.stringToMoment(selectedDates.end);

            if (d.date === selectedDates.start || d.date === selectedDates.end) {
              classesToApply.push(' pickadate-active');
            } else if (start.twix(end).contains(moment(d.date, 'YYYY-MM-DD'))) {
              classesToApply.push(' pickadate-in-range');
            }

            return classesToApply;
          };

          ngModel.$render = function () {
            var dates = ngModel.$modelValue;

            if ((dates) && dates.start && (indexOf.call(disabledDates, dates.start) === -1)) {
              scope.currentDate = currentDate = dateUtils.stringToDate(dates.start);

              dates.end = dates.end || dates.start;

              if (indexOf.call(disabledDates, dates.end) >= 0) {
                dates.end = dates.start;
              }
            } else if (dates) {
              // if the initial date set by the user is in the disabled dates list, unset it
              scope.setDate({});
            }
            scope.render();
          };

          scope.changeMonth = function (offset) {
            var currentMonth = moment(currentDate).month();
            scope.currentDate = currentDate = moment(currentDate).month(currentMonth + offset).toDate();
            scope.render();
          };

          function isDateDisabled(dateObj) {
            return (/pickadate-disabled/.test(dateObj.className));
          }
        }
      };
    }]);
})(window.angular);
<commit_msg>fixed bug with $render and setDate"<commit_after>;(function(angular){
  'use strict';
  var indexOf = [].indexOf;
  angular.module('iOffice.pickadate', [])
    .factory('pickadateUtils', ['dateFilter', function(dateFilter) {
      return {
        isDate: function(obj) {
          return Object.prototype.toString.call(obj) === '[object Date]';
        },

        stringToDate: function(dateString) {
          if (this.isDate(dateString)) return new Date(dateString);
          return this.stringToMoment(dateString).toDate();

        },

        stringToMoment: function(dateString) {
          return moment(dateString, 'YYYY-MM-DD').hour(3);
        }
      };
    }])

    .directive('pickadate', ['$locale', 'pickadateUtils', 'dateFilter', function($locale, dateUtils, dateFilter) {
      return {
        require: 'ngModel',
        scope: {
          selectedDates: '=ngModel',
          defaultDate: '=',
          minDate: '=',
          maxDate: '=',
          disabledDates: '=',
          range: '='
        },
        template:
          '
' +
            '
' +
              '
' +
                'prev' +
                'next' +
              '
'+
              '
' +
                '{{currentDate | date:"MMMM yyyy"}}' +
              '
' +
            '
' +
            '
' +
              '
' +
                '
' +
                  '
' +
                    '{{dayName}}' +
                  '
' +
                '
' +
                '
' +
                  '
' +
                    '{{d.date | date:"d"}}' +
                  '
' +
                '
' +
              '
' +
            '
' +
          '
',

        link: function(scope, element, attrs, ngModel)  {
          var minDate       = scope.minDate && dateUtils.stringToMoment(scope.minDate, 'YYYY-MM-DD'),
              maxDate       = scope.maxDate && dateUtils.stringToMoment(scope.maxDate, 'YYYY-MM-DD'),
              disabledDates = scope.disabledDates || [],
              currentDate   = (scope.defaultDate && dateUtils.stringToDate(scope.defaultDate)) || new Date(),
              range         = scope.range || false;

          scope.dayNames    = $locale.DATETIME_FORMATS.SHORTDAY;
          scope.currentDate = currentDate;

          scope.render = function() {
            var initialDate = currentDate;

            var startOfMonth = moment(initialDate).startOf('month');
            var endOfMonth = moment(initialDate).endOf('month');
            var calStart = moment(startOfMonth).startOf('week');
            var calEnd = moment(endOfMonth).endOf('week');

            var dates = [];

            if (calStart.twix(calEnd).count('days') / 7 < 6) {
              calEnd = moment(calEnd).add(1, 'week');
            }

            scope.allowPrevMonth = !minDate || minDate.isBefore(startOfMonth, 'd');
            scope.allowNextMonth = !maxDate || maxDate.isAfter(endOfMonth, 'd');

            var calendarRange = calStart.twix(calEnd);
            var iter = calendarRange.iterate('days');

            do {
              var className = "",
                date = iter.next(),
                formattedDate = date.format('YYYY-MM-DD');

              if ((minDate && date.isBefore(minDate, 'd')) || (maxDate && date.isAfter(maxDate, 'd'))) {
                className = 'pickadate-disabled';
              } else if (indexOf.call(disabledDates, formattedDate) >= 0) {
                className = 'pickadate-disabled pickadate-unavailable';
              } else {
                className = 'pickadate-enabled';
              }

              if (date.isSame(moment(), 'd')) {
                className += ' pickadate-today';
              }

              dates.push({
                date: formattedDate,
                className: className
              });
            } while(iter.hasNext());

            scope.dates = dates;
          };

          var assignDates = function(dates) {
            if (!dates || Object.keys(dates).length === 0) ngModel.$setViewValue({});
            ngModel.$setViewValue(dates);
          };

          scope.setDate = function(dateObj) {
            if (isDateDisabled(dateObj)) return;
            if (!dateObj.date) {
              ngModel.$setViewValue({});
              return;
            }

            var dates = scope.selectedDates;
            var start = dateUtils.stringToMoment(dates.start);
            var end = dateUtils.stringToMoment(dates.end);
            var selectedDate = dateUtils.stringToMoment(dateObj.date);
            var newSelection = {};

            var rangeContainsDisabledDate = function() {
              var selectedStart = dateUtils.stringToMoment(newSelection.start),
                selectedEnd = dateUtils.stringToMoment(newSelection.end);

              return !(disabledDates.every(function(date) {
                return !selectedStart.twix(selectedEnd).contains(moment(date, 'YYYY-MM-DD'));
              }));
            }

            // We already have a start date
            if (dates.start
              && (dates.start === dates.end)
              && selectedDate.isAfter(start, 'd')
              && !selectedDate.isSame(start, 'd')) {
              newSelection.start = dates.start;
              newSelection.end = dateObj.date;
            } else {
              newSelection.start = dateObj.date;
              newSelection.end = dateObj.date;
            }

            if (!rangeContainsDisabledDate()) {
              assignDates(newSelection);
            }
          };

          scope.getDateClasses = function(d) {
            var classesToApply = [];
            var selectedDates = scope.selectedDates;
            var start = dateUtils.stringToMoment(selectedDates.start);
            var end = dateUtils.stringToMoment(selectedDates.end);

            if (d.date === selectedDates.start || d.date === selectedDates.end) {
              classesToApply.push(' pickadate-active');
            } else if (start.twix(end).contains(moment(d.date, 'YYYY-MM-DD'))) {
              classesToApply.push(' pickadate-in-range');
            }

            return classesToApply;
          };

          ngModel.$render = function () {
            var dates = ngModel.$modelValue;

            if ((dates) && dates.start && (indexOf.call(disabledDates, dates.start) === -1)) {
              scope.currentDate = currentDate = dateUtils.stringToDate(dates.start);

              dates.end = dates.end || dates.start;

              if (indexOf.call(disabledDates, dates.end) >= 0) {
                dates.end = dates.start;
              }
            } else if (dates) {
              // if the initial date set by the user is in the disabled dates list, unset it
              scope.setDate({});
            }
            scope.render();
          };

          scope.changeMonth = function (offset) {
            var currentMonth = moment(currentDate).month();
            scope.currentDate = currentDate = moment(currentDate).month(currentMonth + offset).toDate();
            scope.render();
          };

          function isDateDisabled(dateObj) {
            return (/pickadate-disabled/.test(dateObj.className));
          }
        }
      };
    }]);
})(window.angular);
<|endoftext|>


<commit_before>const nextStep = '.wiz-form__actions__next button.btn-primary';
const prevStep = 'button.wiz-form__actions__prev';

const testPage = (page, platform, json, nextInitiallyDisabled = true) => {
  page.expect.element(prevStep).to.be.visible;
  page.expect.element(prevStep).to.have.attribute('class').which.not.contains('disabled');

  page.expect.element(nextStep).to.be.visible;
  if (nextInitiallyDisabled) {
    page.expect.element(nextStep).to.have.attribute('class').which.contains('disabled');
  }

  // Sidebar link for the current page should be highlighted and enabled
  page.expect.element('.wiz-wizard__nav__step--active button').to.not.have.attribute('disabled');

  // The next sidebar links should be disabled if the next button is disabled
  const nextNavLink = page.expect.element('.wiz-wizard__nav__step--active + .wiz-wizard__nav__step button');
  if (nextInitiallyDisabled) {
    nextNavLink.to.have.attribute('disabled');
  } else {
    nextNavLink.to.not.have.attribute('disabled');
  }

  // Save progress link exists
  page.expect.element('.wiz-form__header a').text.which.contains('Save progress');

  if (page.test) {
    page.test(json, platform);
  }
  page.expect.element(nextStep).to.have.attribute('class').which.not.contains('disabled');
  return page.click(nextStep);
};

module.exports = {
  nextStep,
  prevStep,
  testPage,
};
<commit_msg>tests/frontend: Add some sidebar link tests<commit_after>const nextStep = '.wiz-form__actions__next button.btn-primary';
const prevStep = 'button.wiz-form__actions__prev';

const testPage = (page, platform, json, nextInitiallyDisabled = true) => {
  page.expect.element(prevStep).to.be.visible;
  page.expect.element(prevStep).to.have.attribute('class').which.not.contains('disabled');

  page.expect.element(nextStep).to.be.visible;
  if (nextInitiallyDisabled) {
    page.expect.element(nextStep).to.have.attribute('class').which.contains('disabled');
  }

  // Sidebar link for the current page should be highlighted and enabled
  page.expect.element('.wiz-wizard__nav__step--active button').to.not.have.attribute('disabled');

  // The next sidebar links should be disabled if the next button is disabled
  const nextNavLink = page.expect.element('.wiz-wizard__nav__step--active + .wiz-wizard__nav__step button');
  if (nextInitiallyDisabled) {
    nextNavLink.to.have.attribute('disabled');

    // If the next button is disabled, all sidebar links for later screens should be disabled
    page.expect.element('.wiz-wizard__nav__step--active ~ .wiz-wizard__nav__step button:not([disabled])').to.not.be.present;
  } else {
    nextNavLink.to.not.have.attribute('disabled');

    // If the next button is enabled, the next sidebar link should be enabled too
    page.expect.element('.wiz-wizard__nav__step--active + .wiz-wizard__nav__step button').to.not.have.attribute('disabled');
  }

  // Save progress link exists
  page.expect.element('.wiz-form__header a').text.which.contains('Save progress');

  if (page.test) {
    page.test(json, platform);
  }
  page.expect.element(nextStep).to.have.attribute('class').which.not.contains('disabled');
  return page.click(nextStep);
};

module.exports = {
  nextStep,
  prevStep,
  testPage,
};
<|endoftext|>


<commit_before>/**
 * @license Angulartics
 * (c) 2013 Luis Farzati http://luisfarzati.github.io/angulartics
 * Piwik 2.1.x update contributed by http://github.com/highskillz
 * License: MIT
 */

/* global _paq */

(function(angular) {
    'use strict';

    /**
     * @ngdoc overview
     * @name angulartics.piwik
     * Enables analytics support for Piwik (http://piwik.org/docs/tracking-api/)
     */
    angular.module('angulartics.piwik', ['angulartics'])
        .config(['$analyticsProvider', '$windowProvider',
            function($analyticsProvider, $windowProvider) {

                var $window = $windowProvider.$get();

                $analyticsProvider.settings.pageTracking.trackRelativePath = true;

                // Add piwik specific trackers to angulartics API

                // Requires the CustomDimensions plugin for Piwik.
                $analyticsProvider.api.setCustomDimension = function(dimensionId, value) {

                    if ($window._paq) {
                        $window._paq.push(['setCustomDimension', dimensionId, value]);
                    }
                };

                // Requires the CustomDimensions plugin for Piwik.
                $analyticsProvider.api.deleteCustomDimension = function(dimensionId) {

                    if ($window._paq) {
                        $window._paq.push(['deleteCustomDimension', dimensionId]);
                    }
                };

                // scope: visit or page. Defaults to 'page'
                $analyticsProvider.api.setCustomVariable = function(varIndex, varName, value, scope) {

                    if ($window._paq) {
                        scope = scope || 'page';
                        $window._paq.push(['setCustomVariable', varIndex, varName, value, scope]);
                    }
                };

                // scope: visit or page. Defaults to 'page'
                $analyticsProvider.api.deleteCustomVariable = function(varIndex, scope) {

                    if ($window._paq) {
                        scope = scope || 'page';
                        $window._paq.push(['deleteCustomVariable', varIndex, scope]);
                    }
                };

                // trackSiteSearch(keyword, category, [searchCount])
                $analyticsProvider.api.trackSiteSearch = function(keyword, category, searchCount) {

                    // keyword is required
                    if ($window._paq && keyword) {

                        var params = ['trackSiteSearch', keyword, category || false];

                        // searchCount is optional
                        if (angular.isDefined(searchCount)) {
                            params.push(searchCount);
                        }

                        $window._paq.push(params);
                    }
                };

                // logs a conversion for goal 1. revenue is optional
                // trackGoal(goalID, [revenue]);
                $analyticsProvider.api.trackGoal = function(goalID, revenue) {
                    if ($window._paq) {
                        _paq.push(['trackGoal', goalID, revenue || 0]);
                    }
                };

                // track outlink or download
                // linkType is 'link' or 'download', 'link' by default
                // trackLink(url, [linkType]);
                $analyticsProvider.api.trackLink = function(url, linkType) {
                    var type = linkType || 'link';
                    if ($window._paq) {
                        $window._paq.push(['trackLink', url, type]);
                    }
                };

                // Set default angulartics page and event tracking

                // $analytics.setUsername(username)
                $analyticsProvider.registerSetUsername(function(username) {
                    if ($window._paq) {
                        $window._paq.push(['setUserId', username]);
                    }
                });

                // $analytics.setAlias(alias)
                // $analyticsProvider.registerSetAlias(function(param) {
                //     // TODO: No piwik corresponding function found. Use setCustomVariable instead
                // });

                // $analytics.setUserProperties(properties)
                // $analyticsProvider.registerSetUserProperties(function(param) {
                //     // TODO: No piwik corresponding function found. Use setCustomVariable instead
                // });

                // locationObj is the angular $location object
                $analyticsProvider.registerPageTrack(function(path, locationObj) {

                    if ($window._paq) {
                        $window._paq.push(['setDocumentTitle', $window.document.title]);
                        $window._paq.push(['setCustomUrl', path]);
                        $window._paq.push(['trackPageView']);
                    }
                });

                // trackEvent(category, event, [name], [value])
                $analyticsProvider.registerEventTrack(function(action, properties) {

                    if ($window._paq) {

                        // PAQ requires that eventValue be an integer, see: http://piwik.org/docs/event-tracking/
                        if (properties.value) {
                            var parsed = parseInt(properties.value, 10);
                            properties.value = isNaN(parsed) ? 0 : parsed;
                        }

                        $window._paq.push(['trackEvent', properties.category, action, properties.label, properties.value]);
                    }
                });
            }
        ]);
})(angular);
<commit_msg>Update angulartics-piwik.js<commit_after>/**
 * @license Angulartics
 * (c) 2013 Luis Farzati http://luisfarzati.github.io/angulartics
 * Piwik 2.1.x update contributed by http://github.com/highskillz
 * License: MIT
 */

/* global _paq */

(function(angular) {
    'use strict';

    /**
     * @ngdoc overview
     * @name angulartics.piwik
     * Enables analytics support for Piwik (http://piwik.org/docs/tracking-api/)
     */
    angular.module('angulartics.piwik', ['angulartics'])
        .config(['$analyticsProvider', '$windowProvider',
            function($analyticsProvider, $windowProvider) {

                var $window = $windowProvider.$get();

                $analyticsProvider.settings.pageTracking.trackRelativePath = true;

                // Add piwik specific trackers to angulartics API

                // Requires the CustomDimensions plugin for Piwik.
                $analyticsProvider.api.setCustomDimension = function(dimensionId, value) {

                    if ($window._paq) {
                        $window._paq.push(['setCustomDimension', dimensionId, value]);
                    }
                };

                // Requires the CustomDimensions plugin for Piwik.
                $analyticsProvider.api.deleteCustomDimension = function(dimensionId) {

                    if ($window._paq) {
                        $window._paq.push(['deleteCustomDimension', dimensionId]);
                    }
                };

                // scope: visit or page. Defaults to 'page'
                $analyticsProvider.api.setCustomVariable = function(varIndex, varName, value, scope) {

                    if ($window._paq) {
                        scope = scope || 'page';
                        $window._paq.push(['setCustomVariable', varIndex, varName, value, scope]);
                    }
                };

                // scope: visit or page. Defaults to 'page'
                $analyticsProvider.api.deleteCustomVariable = function(varIndex, scope) {

                    if ($window._paq) {
                        scope = scope || 'page';
                        $window._paq.push(['deleteCustomVariable', varIndex, scope]);
                    }
                };

                // trackSiteSearch(keyword, category, [searchCount])
                $analyticsProvider.api.trackSiteSearch = function(keyword, category, searchCount) {

                    // keyword is required
                    if ($window._paq && keyword) {

                        var params = ['trackSiteSearch', keyword, category || false];

                        // searchCount is optional
                        if (angular.isDefined(searchCount)) {
                            params.push(searchCount);
                        }

                        $window._paq.push(params);
                    }
                };

                // logs a conversion for goal 1. revenue is optional
                // trackGoal(goalID, [revenue]);
                $analyticsProvider.api.trackGoal = function(goalID, revenue) {
                    if ($window._paq) {
                        _paq.push(['trackGoal', goalID, revenue || 0]);
                    }
                };

                // track outlink or download
                // linkType is 'link' or 'download', 'link' by default
                // trackLink(url, [linkType]);
                $analyticsProvider.api.trackLink = function(url, linkType) {
                    var type = linkType || 'link';
                    if ($window._paq) {
                        $window._paq.push(['trackLink', url, type]);
                    }
                };

                // Set default angulartics page and event tracking

                // $analytics.setUsername(username)
                $analyticsProvider.registerSetUsername(function(username) {
                    if ($window._paq) {
                        $window._paq.push(['setUserId', username]);
                    }
                });

                // $analytics.setAlias(alias)
                // $analyticsProvider.registerSetAlias(function(param) {
                //     // TODO: No piwik corresponding function found. Use setCustomVariable instead
                // });

                // $analytics.setUserProperties(properties)
                // $analyticsProvider.registerSetUserProperties(function(param) {
                //     // TODO: No piwik corresponding function found. Use setCustomVariable instead
                // });

                // locationObj is the angular $location object
                $analyticsProvider.registerPageTrack(function(path, locationObj) {

                    if ($window._paq) {
                        $window._paq.push(['setDocumentTitle', $window.document.title]);
                        $window._paq.push(['setCustomUrl', path]);
                        $window._paq.push(['trackPageView']);
                    }
                });

                // trackEvent(category, event, [name], [value])
                $analyticsProvider.registerEventTrack(function(action, properties) {

                    if ($window._paq) {

                        // PAQ requires that eventValue be an integer, see: http://piwik.org/docs/event-tracking/
                        if (properties.value) {
                            var parsed = parseInt(properties.value, 10);
                            properties.value = isNaN(parsed) ? 0 : parsed;
                        }

                        $window._paq.push(['trackEvent', properties.category, action, properties.label, properties.value]);
                    }
                });

              /**
               * Exception Track Event in GA
               * @name exceptionTrack
               * Sugar on top of the eventTrack method for easily handling errors
               *
               * @param {object} error An Error object to track: error.toString() used for event 'action', error.stack used for event 'label'.
               * @param {object} cause The cause of the error given from $exceptionHandler, not used.
               *
               * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/events
               */
              $analyticsProvider.registerExceptionTrack(function (error, cause) {
                  if ($window._paq) {
                      $window._paq.push(['trackEvent', 'Exceptions', error.toString(), error.stack, 0]);
                  }
              });
            }
        ]);
})(angular);
<|endoftext|>