<commit_before>/**
 * @flow
 */

import chalk from 'chalk';
import simpleSpinner from '@expo/simple-spinner';

import { Project } from 'xdl';

import log from '../log';
import sendTo from '../sendTo';
import { installExitHooks } from '../exit';

type Options = {
  clear?: boolean,
  sendTo?: string,
  quiet?: boolean,
  releaseChannel?: string,
};

export async function action(projectDir: string, options: Options = {}) {
  let channelRe = new RegExp(/^[a-z\d][a-z\d._-]*$/);
  if (options.releaseChannel && !channelRe.test(options.releaseChannel)) {
    log.error(
      'Release channel name can only contain lowercase letters, numbers and special characters . _ and -'
    );
    process.exit(1);
  }
  const status = await Project.currentStatus(projectDir);

  let startedOurOwn = false;
  if (status !== 'running') {
    log('Unable to find an existing exp instance for this directory, starting a new one...');
    installExitHooks(projectDir);
    await Project.startAsync(
      projectDir,
      { reset: options.clear, nonPersistent: true },
      !options.quiet
    );
    startedOurOwn = true;
  }

  let recipient = await sendTo.getRecipient(options.sendTo);
  log('Publishing...');

  if (options.quiet) {
    simpleSpinner.start();
  }

  let result = await Project.publishAsync(projectDir, {
    releaseChannel: options.releaseChannel,
  });

  let url = result.url;

  // Append the query param for the release channel to the URL.
  // When we integrate release channels into XDE, we can revisit this and
  // perhaps push the logic for this into xdl
  if (options.releaseChannel && options.releaseChannel !== 'default') {
    url = `${url}?release-channel=${options.releaseChannel}`;
  }

  if (options.quiet) {
    simpleSpinner.stop();
  }

  log('Published');
  log('Your URL is\n\n' + chalk.underline(url) + '\n');
  log.raw(url);

  if (recipient) {
    await sendTo.sendUrlAsync(url, recipient);
  }

  if (startedOurOwn) {
    await Project.stopAsync(projectDir);
  }

  return result;
}

export default (program: any) => {
  program
    .command('publish [project-dir]')
    .alias('p')
    .description('Publishes your project to exp.host')
    .option('-q, --quiet', 'Suppress verbose output from the React Native packager.')
    .option('-s, --send-to [dest]', 'A phone number or e-mail address to send a link to')
    .option('-c, --clear', 'Clear the React Native packager cache')
    .option(
      '--release-channel <release channel>',
      "The release channel to publish to. Default is 'default'.",
      'default'
    )
    .allowNonInteractive()
    .asyncActionProjectDir(action, true);
};
<commit_msg>Add channel to the publishing log<commit_after>/**
 * @flow
 */

import chalk from 'chalk';
import simpleSpinner from '@expo/simple-spinner';

import { Project } from 'xdl';

import log from '../log';
import sendTo from '../sendTo';
import { installExitHooks } from '../exit';

type Options = {
  clear?: boolean,
  sendTo?: string,
  quiet?: boolean,
  releaseChannel?: string,
};

export async function action(projectDir: string, options: Options = {}) {
  let channelRe = new RegExp(/^[a-z\d][a-z\d._-]*$/);
  if (options.releaseChannel && !channelRe.test(options.releaseChannel)) {
    log.error(
      'Release channel name can only contain lowercase letters, numbers and special characters . _ and -'
    );
    process.exit(1);
  }
  const status = await Project.currentStatus(projectDir);

  let startedOurOwn = false;
  if (status !== 'running') {
    log('Unable to find an existing exp instance for this directory, starting a new one...');
    installExitHooks(projectDir);
    await Project.startAsync(
      projectDir,
      { reset: options.clear, nonPersistent: true },
      !options.quiet
    );
    startedOurOwn = true;
  }

  let recipient = await sendTo.getRecipient(options.sendTo);
  log(`Publishing to channel '${options.releaseChannel}'...`);

  if (options.quiet) {
    simpleSpinner.start();
  }

  let result = await Project.publishAsync(projectDir, {
    releaseChannel: options.releaseChannel,
  });

  let url = result.url;

  // Append the query param for the release channel to the URL.
  // When we integrate release channels into XDE, we can revisit this and
  // perhaps push the logic for this into xdl
  if (options.releaseChannel && options.releaseChannel !== 'default') {
    url = `${url}?release-channel=${options.releaseChannel}`;
  }

  if (options.quiet) {
    simpleSpinner.stop();
  }

  log('Published');
  log('Your URL is\n\n' + chalk.underline(url) + '\n');
  log.raw(url);

  if (recipient) {
    await sendTo.sendUrlAsync(url, recipient);
  }

  if (startedOurOwn) {
    await Project.stopAsync(projectDir);
  }

  return result;
}

export default (program: any) => {
  program
    .command('publish [project-dir]')
    .alias('p')
    .description('Publishes your project to exp.host')
    .option('-q, --quiet', 'Suppress verbose output from the React Native packager.')
    .option('-s, --send-to [dest]', 'A phone number or e-mail address to send a link to')
    .option('-c, --clear', 'Clear the React Native packager cache')
    .option(
      '--release-channel <release channel>',
      "The release channel to publish to. Default is 'default'.",
      'default'
    )
    .allowNonInteractive()
    .asyncActionProjectDir(action, true);
};
<|endoftext|>


<commit_before>/* (c) Copyright 2002-2015 by authors of the Tiki Wiki CMS Groupware Project
 *
 * All Rights Reserved. See copyright.txt for details and a complete list of authors.
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
 * $Id$
 *
 * Tiki plugin integration plugin.
 *
 * Based on ckeditor flash plugin
 */

(function() {

	var protectTikiPluginsRegexp = /<(?:div|span)[^>]*data-syntax="([^"]*)"[\s\S]*?end tiki_plugin -->\s*?<\/(?:div|span)>/mig;

	CKEDITOR.plugins.add( 'tikiplugin', {

		init : function(editor) {

			var asplugin = this;

			editor.config.protectedSource.push( protectTikiPluginsRegexp );

			this.command = new CKEDITOR.command( editor , {
				modes: { wysiwyg:1 },
				exec: function(editor, data) {	// odd? elem param disappears from doubleclick
					var sel = editor.getSelection(), r;
					// Selection will be unavailable after context menu shows up - in IE, lock it now.
					if ( CKEDITOR.env.ie ) {
						if (sel) { sel.lock(); }
					}
					if (data === editor) {
						r = sel.getRanges();
						data = r[0].getEnclosedNode();
						if (!data) {
							data = r[0].getCommonAncestor();
						}
					}
					if (!data.hasClass("tiki_plugin")) {
						var pluginTemp = $(".tiki_plugin:first", data.$);
						if (pluginTemp.length) {
							data = new CKEDITOR.dom.element(pluginTemp[0]);
						}
					}
					while (!data.hasClass("tiki_plugin") && data.getParent()) {	// try parents?
						data = data.getParent();
						if (data.hasClass("tiki_plugin")) {
							break;
						}
					}
					if (!data.hasClass("tiki_plugin")) {
						// problem here - wrong element sent in?
						debugger;		// intentionally left in to catch occasional IE edge cases
						return;
					}
					var args = {};
					var str = data.data("args");

					var pairs = str.split("&");
					for (var i = 0; i < pairs.length; i++) {
						if (pairs[i].indexOf("=") > -1) {
							var val = pairs[i].substring((pairs[i].indexOf("=") + 1));
							if (val.match(/^".*"$/)) {								// strip off extra quotes
								val = val.substring(1, val.length - 1)
							}
							args[pairs[i].substring(0, pairs[i].indexOf("="))] = $('
').html(val).text();	// decode html entities;
						}
					}
					sel.selectElement( data );
					popupPluginForm( editor.name, data.data("plugin"), 0, '', args, data.data("body"), '');
				},
				canUndo: false
			});

			editor.addCommand('tikiplugin', this.command);

			// override plugin and icon class (make visible and grey)
			CKEDITOR.addCss('.tiki_plugin .plugin_icon { display: block;}' +
							'.tiki_plugin { background-color: #eee; border: 1px solid #666;}');

			// If the "menu" plugin is loaded, register the menu items.
			if (editor.addMenuItems) {
                editor.addMenuGroup("tiki", 999);
				editor.addMenuItems( {
					tikiplugin : {
						label : 'Tiki Plugin',
						command : 'tikiplugin',
						group : 'tiki',
						icon : CKEDITOR.config._TikiRoot + 'img/icons/plugin_edit.png'
					}
				});
				// remove the built in image menu item
				editor.removeMenuItem('image');
			}

			editor.on('contentDom', function() {	// re-apply the events etc when wysiwyg mode happens

				$(".tiki_plugin", editor.document.$).each(function() {
					var parentPlugin = new CKEDITOR.dom.element(this);
					$(this).find("*").addBack().each(function(){
						$(this).mousedown(function(){
							var sel = editor.getSelection();
							if (sel) {
								sel.selectElement(parentPlugin);
							}
							return false;
						}); //.attr("contenteditable", "false");
					});
				});

				editor.on( 'doubleclick', function(evt) {
					var element = evt.data.element;

					element = asplugin.getPluginParent( element );

					if (element) {
						evt.cancel();
						evt.stop();
						evt.data.dialog = null;
						//this.plugins.tikiplugin.command.exec(element);
                        this.execCommand('tikiplugin', element);
					}
				});
			});

			// If the "contextmenu" plugin is loaded, register the listeners.
			if (editor.contextMenu) {
				editor.contextMenu.addListener(function(element, selection) {

					element = asplugin.getPluginParent( element );

					if (element) {
						return {
							tikiplugin : CKEDITOR.TRISTATE_OFF
						};
					}
				});
			}
			if (jqueryTiki.autosave) {	// pref check
				// changed for ckeditor4 - to check?
				if (typeof editor.plugins["tikiwiki"] === "undefined") {	// also defined in tikiwiki plugin
					this.ckToHtml = editor.dataProcessor.toHtml;		// reference to original ckeditor dataProcessor
					editor.dataProcessor.toHtml			= function ( data, fixForBody ) { return asplugin.toHtmlFormat( editor, data, fixForBody ); };
					this.ckToData = editor.dataProcessor.toDataFormat;
					editor.dataProcessor.toDataFormat 	= function ( html, fixForBody ) { return asplugin.toHTMLSource( editor, html, fixForBody ); };
				}
			}
		},			// end of init()

		getPluginParent: function ( element ) {
			while (element && element.getParent()) {
				if (element.data('plugin')) {
					return element;
				}
				element = element.getParent();
			}
			return null;
		},

		toHTMLSource: function( editor, html, fixForBody ) {
			// de-protect ck_protected comments
			var output = html;

			output = this.ckToData.call( editor.dataProcessor, output, fixForBody );

			if (typeof editor.plugins["tikiwiki"] === "undefined") {
				output = output.replace(/
([\s\S]*?)<\/pre>/ig, "$1");
			}
			output = output.replace(//ig, "");
			// replace visual plugins with syntax
			output = output.replace( protectTikiPluginsRegexp, function() {
				if (arguments.length > 0) {
					var plugCode = $("").html(arguments[1]).text();
					if (typeof editor.plugins["tikiwiki"] !== "undefined") {	// also defined in tikiwiki plugin
						plugCode = plugCode.replace(/\n/g, "
");
					}
					return plugCode;	// decode html entities
				} else {
					alert("ckeditor: error parsing to html source");
					return "";
				}
			});
			output = output.replace(/\s*
[\s]*<\/p>\s*/mgi, "");	// strip out empty
 tags

			return output;
		},

		toHtmlFormat: function( editor, data, fixForBody ) {
			if (!jqueryTiki.autosave) {
				alert(tr("AJAX Autosave preference not enabled. Please go to admin/features/interface to enable it."));
			}

			if (typeof editor.pluginReparsing !== "undefined" && editor.pluginReparsing) {
				editor.pluginReparsing = false;
				return data;
			}

			var output = "";
			var asplugin = this;
			var orig_data = $("#editwiki").val();	// just in case
			var isHtml = auto_save_allowHtml(editor.element.$.form);

			ajaxLoadingShow( "cke_contents_" + editor.name);
			$("#ajaxLoading").show();		// FIXME safari/chrome refuse to show until ajax finished
			jQuery.ajax({
				async: false, // wait for this one
				url: $.service("edit", "tohtml"),
				type: "POST",
				dataType: "json",
				data: {
					referer: editor.config.autoSaveSelf,
					editor_id: editor.name,
					data: data,
					allowhtml: isHtml
				},
				// good callback
				success: function(data) {
					ajaxLoadingHide();
					output = data.data;

					output = asplugin.ckToHtml.call(editor.dataProcessor, output, fixForBody);
					if (output.indexOf("") === -1 && output.indexOf(" -1) {
						alert("Is this code still needed? (tikiplugin/plugin.js line 223)");
						output += ""; 	// workaround for cke 3.4 / tiki 6.0 FIXME
					}
				},
				// bad callback - no good info in the params :(
				error: function(req, status, error) {
					ajaxLoadingHide();
					output = orig_data;	//"ajax error";
					alert(tr("AJAX Error") + "\n" + tr("It may not be possible to edit this page reliably in WYSIWYG mode in this browser.") + "\n\n" + error.message);
				}
			});
			return output;
		},

		requires : [ '' ]	// TODO check req - autosave? (really)
	});

	if (typeof CKEDITOR.editor.prototype.reParse != 'function') {
		CKEDITOR.editor.prototype.pluginReparsing = false;

		CKEDITOR.editor.prototype.reParse = function() {

			// send the whole source off to the server to get reparsed?
			var myoutput = "";
			var mydata = this.getData();
			myoutput = this.dataProcessor.toHtml(mydata);

			this.pluginReparsing = true;
			this.setData(myoutput);
		};
	}
})();
<commit_msg>[FIX] jslint: Malformed regular expression wasn't needed any more, so removed the whole line (committed separately just in case)<commit_after>/* (c) Copyright 2002-2015 by authors of the Tiki Wiki CMS Groupware Project
 *
 * All Rights Reserved. See copyright.txt for details and a complete list of authors.
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
 * $Id$
 *
 * Tiki plugin integration plugin.
 *
 * Based on ckeditor flash plugin
 */

(function() {

	var protectTikiPluginsRegexp = /<(?:div|span)[^>]*data-syntax="([^"]*)"[\s\S]*?end tiki_plugin -->\s*?<\/(?:div|span)>/mig;

	CKEDITOR.plugins.add( 'tikiplugin', {

		init : function(editor) {

			var asplugin = this;

			editor.config.protectedSource.push( protectTikiPluginsRegexp );

			this.command = new CKEDITOR.command( editor , {
				modes: { wysiwyg:1 },
				exec: function(editor, data) {	// odd? elem param disappears from doubleclick
					var sel = editor.getSelection(), r;
					// Selection will be unavailable after context menu shows up - in IE, lock it now.
					if ( CKEDITOR.env.ie ) {
						if (sel) { sel.lock(); }
					}
					if (data === editor) {
						r = sel.getRanges();
						data = r[0].getEnclosedNode();
						if (!data) {
							data = r[0].getCommonAncestor();
						}
					}
					if (!data.hasClass("tiki_plugin")) {
						var pluginTemp = $(".tiki_plugin:first", data.$);
						if (pluginTemp.length) {
							data = new CKEDITOR.dom.element(pluginTemp[0]);
						}
					}
					while (!data.hasClass("tiki_plugin") && data.getParent()) {	// try parents?
						data = data.getParent();
						if (data.hasClass("tiki_plugin")) {
							break;
						}
					}
					if (!data.hasClass("tiki_plugin")) {
						// problem here - wrong element sent in?
						debugger;		// intentionally left in to catch occasional IE edge cases
						return;
					}
					var args = {};
					var str = data.data("args");

					var pairs = str.split("&");
					for (var i = 0; i < pairs.length; i++) {
						if (pairs[i].indexOf("=") > -1) {
							var val = pairs[i].substring((pairs[i].indexOf("=") + 1));
							if (val.match(/^".*"$/)) {								// strip off extra quotes
								val = val.substring(1, val.length - 1)
							}
							args[pairs[i].substring(0, pairs[i].indexOf("="))] = $('
').html(val).text();	// decode html entities;
						}
					}
					sel.selectElement( data );
					popupPluginForm( editor.name, data.data("plugin"), 0, '', args, data.data("body"), '');
				},
				canUndo: false
			});

			editor.addCommand('tikiplugin', this.command);

			// override plugin and icon class (make visible and grey)
			CKEDITOR.addCss('.tiki_plugin .plugin_icon { display: block;}' +
							'.tiki_plugin { background-color: #eee; border: 1px solid #666;}');

			// If the "menu" plugin is loaded, register the menu items.
			if (editor.addMenuItems) {
                editor.addMenuGroup("tiki", 999);
				editor.addMenuItems( {
					tikiplugin : {
						label : 'Tiki Plugin',
						command : 'tikiplugin',
						group : 'tiki',
						icon : CKEDITOR.config._TikiRoot + 'img/icons/plugin_edit.png'
					}
				});
				// remove the built in image menu item
				editor.removeMenuItem('image');
			}

			editor.on('contentDom', function() {	// re-apply the events etc when wysiwyg mode happens

				$(".tiki_plugin", editor.document.$).each(function() {
					var parentPlugin = new CKEDITOR.dom.element(this);
					$(this).find("*").addBack().each(function(){
						$(this).mousedown(function(){
							var sel = editor.getSelection();
							if (sel) {
								sel.selectElement(parentPlugin);
							}
							return false;
						}); //.attr("contenteditable", "false");
					});
				});

				editor.on( 'doubleclick', function(evt) {
					var element = evt.data.element;

					element = asplugin.getPluginParent( element );

					if (element) {
						evt.cancel();
						evt.stop();
						evt.data.dialog = null;
						//this.plugins.tikiplugin.command.exec(element);
                        this.execCommand('tikiplugin', element);
					}
				});
			});

			// If the "contextmenu" plugin is loaded, register the listeners.
			if (editor.contextMenu) {
				editor.contextMenu.addListener(function(element, selection) {

					element = asplugin.getPluginParent( element );

					if (element) {
						return {
							tikiplugin : CKEDITOR.TRISTATE_OFF
						};
					}
				});
			}
			if (jqueryTiki.autosave) {	// pref check
				// changed for ckeditor4 - to check?
				if (typeof editor.plugins["tikiwiki"] === "undefined") {	// also defined in tikiwiki plugin
					this.ckToHtml = editor.dataProcessor.toHtml;		// reference to original ckeditor dataProcessor
					editor.dataProcessor.toHtml			= function ( data, fixForBody ) { return asplugin.toHtmlFormat( editor, data, fixForBody ); };
					this.ckToData = editor.dataProcessor.toDataFormat;
					editor.dataProcessor.toDataFormat 	= function ( html, fixForBody ) { return asplugin.toHTMLSource( editor, html, fixForBody ); };
				}
			}
		},			// end of init()

		getPluginParent: function ( element ) {
			while (element && element.getParent()) {
				if (element.data('plugin')) {
					return element;
				}
				element = element.getParent();
			}
			return null;
		},

		toHTMLSource: function( editor, html, fixForBody ) {
			// de-protect ck_protected comments
			var output = html;

			output = this.ckToData.call( editor.dataProcessor, output, fixForBody );

			if (typeof editor.plugins["tikiwiki"] === "undefined") {
				output = output.replace(/
([\s\S]*?)<\/pre>/ig, "$1");
			}
			// replace visual plugins with syntax
			output = output.replace( protectTikiPluginsRegexp, function() {
				if (arguments.length > 0) {
					var plugCode = $("").html(arguments[1]).text();
					if (typeof editor.plugins["tikiwiki"] !== "undefined") {	// also defined in tikiwiki plugin
						plugCode = plugCode.replace(/\n/g, "
");
					}
					return plugCode;	// decode html entities
				} else {
					alert("ckeditor: error parsing to html source");
					return "";
				}
			});
			output = output.replace(/\s*
[\s]*<\/p>\s*/mgi, "");	// strip out empty
 tags

			return output;
		},

		toHtmlFormat: function( editor, data, fixForBody ) {
			if (!jqueryTiki.autosave) {
				alert(tr("AJAX Autosave preference not enabled. Please go to admin/features/interface to enable it."));
			}

			if (typeof editor.pluginReparsing !== "undefined" && editor.pluginReparsing) {
				editor.pluginReparsing = false;
				return data;
			}

			var output = "";
			var asplugin = this;
			var orig_data = $("#editwiki").val();	// just in case
			var isHtml = auto_save_allowHtml(editor.element.$.form);

			ajaxLoadingShow( "cke_contents_" + editor.name);
			$("#ajaxLoading").show();		// FIXME safari/chrome refuse to show until ajax finished
			jQuery.ajax({
				async: false, // wait for this one
				url: $.service("edit", "tohtml"),
				type: "POST",
				dataType: "json",
				data: {
					referer: editor.config.autoSaveSelf,
					editor_id: editor.name,
					data: data,
					allowhtml: isHtml
				},
				// good callback
				success: function(data) {
					ajaxLoadingHide();
					output = data.data;

					output = asplugin.ckToHtml.call(editor.dataProcessor, output, fixForBody);
					if (output.indexOf("") === -1 && output.indexOf(" -1) {
						alert("Is this code still needed? (tikiplugin/plugin.js line 223)");
						output += ""; 	// workaround for cke 3.4 / tiki 6.0 FIXME
					}
				},
				// bad callback - no good info in the params :(
				error: function(req, status, error) {
					ajaxLoadingHide();
					output = orig_data;	//"ajax error";
					alert(tr("AJAX Error") + "\n" + tr("It may not be possible to edit this page reliably in WYSIWYG mode in this browser.") + "\n\n" + error.message);
				}
			});
			return output;
		},

		requires : [ '' ]	// TODO check req - autosave? (really)
	});

	if (typeof CKEDITOR.editor.prototype.reParse != 'function') {
		CKEDITOR.editor.prototype.pluginReparsing = false;

		CKEDITOR.editor.prototype.reParse = function() {

			// send the whole source off to the server to get reparsed?
			var myoutput = "";
			var mydata = this.getData();
			myoutput = this.dataProcessor.toHtml(mydata);

			this.pluginReparsing = true;
			this.setData(myoutput);
		};
	}
})();
<|endoftext|>


<commit_before>import i18n from 'util/i18n';
import { quoteattr } from 'util/xss';

function checked(val) {
    return val ? 'checked="checked"' : '';
}

function value(val) {
    return `value="${quoteattr(val)}"`;
}

module.exports = ({
    id = 'intro', className = '', instruction = '', user,
}) => `
  <form id="${id}" class="screen intro ${quoteattr(className)}" method="POST">
    <fieldset>
      <legend class="intro-legend">${i18n('INFO_ABOUT_YOU')}</legend>

${i18n('WE_NEED_YOUR_DATA')}

                ${i18n('FULL_NAME')}

            <input type="text" id="${id}_name"
                   name="name" ${value(user.name)}
                   placeholder="${i18n('FULL_NAME_PLACEHOLDER')}"
                   required />

            ${i18n('AGE')}
            <input type="number"
                   id="${id}_age"
                   name="age" ${value(user.age)}
                   pattern="[0-9]*" placeholder="18" min="18" max="99" required />

            ${i18n('GENDER')}
            <input type="radio" id="${id}_male"
                   name="gender" ${value(0)} ${checked(+user.gender === 0)} required />
            ${i18n('GENDER_NAME', 0)}
            <input type="radio" id="${id}_female"
                   name="gender" ${value(1)} ${checked(+user.gender === 1)} required />
            ${i18n('GENDER_NAME', 1)}

          <input type="checkbox" id="${id}_agree" name="agree" ${checked(user.agree)} required />
          ${i18n('TERMS_OF_USE')}

${i18n('DISCLAIMER')}

          ${i18n('INSTRUCTION')}:
          ${instruction}

            <button type="submit" class="intro-submit">${i18n('START_SURVEY')}</button>

    </fieldset>
  </form>
`;
<commit_msg>fix: min and max age<commit_after>import i18n from 'util/i18n';
import { quoteattr } from 'util/xss';

function checked(val) {
    return val ? 'checked="checked"' : '';
}

function value(val) {
    return `value="${quoteattr(val)}"`;
}

module.exports = ({
    id = 'intro', className = '', instruction = '', user,
}) => `
  <form id="${id}" class="screen intro ${quoteattr(className)}" method="POST">
    <fieldset>
      <legend class="intro-legend">${i18n('INFO_ABOUT_YOU')}</legend>

${i18n('WE_NEED_YOUR_DATA')}

                ${i18n('FULL_NAME')}

            <input type="text" id="${id}_name"
                   name="name" ${value(user.name)}
                   placeholder="${i18n('FULL_NAME_PLACEHOLDER')}"
                   required />

            ${i18n('AGE')}
            <input type="number"
                   id="${id}_age"
                   name="age" ${value(user.age)}
                   pattern="[0-9]*" placeholder="18" min="16" max="70" required />

            ${i18n('GENDER')}
            <input type="radio" id="${id}_male"
                   name="gender" ${value(0)} ${checked(+user.gender === 0)} required />
            ${i18n('GENDER_NAME', 0)}
            <input type="radio" id="${id}_female"
                   name="gender" ${value(1)} ${checked(+user.gender === 1)} required />
            ${i18n('GENDER_NAME', 1)}

          <input type="checkbox" id="${id}_agree" name="agree" ${checked(user.agree)} required />
          ${i18n('TERMS_OF_USE')}

${i18n('DISCLAIMER')}

          ${i18n('INSTRUCTION')}:
          ${instruction}

            <button type="submit" class="intro-submit">${i18n('START_SURVEY')}</button>

    </fieldset>
  </form>
`;
<|endoftext|>


<commit_before>/**
  Overly tolerant url parser
 */

var Url = module.exports = {
  PROTOCOL: '://',
  COLON: ':',
  AT: '@',
  parse: function(url){
    var parsed = {
      protocol: null, // string
      auth: null, // string
      hostname: null, // string
      host: null, // string
      port: null, // string
      href: url // string
    };

    // check protocol
    var protocolIdx = url.indexOf(this.PROTOCOL);
    if(protocolIdx === -1){
      // protocol is not present add one
    } else {
      parsed.protocol = url.substring(0, protocolIdx+1);
      protocolIdx += this.PROTOCOL.length;
      // remove the protocol part from the url
      url = url.substring(protocolIdx);
    }

    var atCount        = strCount(url, this.AT);
    var colonCount     = strCount(url, this.COLON);
    var atIsPresent    = atCount > 0;
    var colonIsPresent = colonCount > 0;

    if(!atIsPresent && !colonIsPresent){
      parsed.host     = url;
      parsed.hostname = url;
      // parse is done, there are no port and auth
      return parsed;
    }

    if(colonCount === 1 && !atIsPresent){ // it can only be the port number
      extractPortAndHostnameFromHost(parsed, url);
      return parsed;
    }

    var parts   = url.split(this.AT);
    var host    = parts.pop();
    extractPortAndHostnameFromHost(parsed, host);
    parsed.auth = parts.join(this.AT);
    return parsed;
  }
};

function strCount(str, substr){
  if (str === null || substr === null) return 0;
  str    = String(str);
  substr = String(substr);

  var count  = 0,
      pos    = 0,
      length = substr.length;

  while (true) {
    pos = str.indexOf(substr, pos);
    if (pos === -1) break;
    count++;
    pos += length;
  }

  return count;
}

function extractPortAndHostnameFromHost(parsed, host){
  var portIdx = host.indexOf(Url.COLON);
  parsed.host = host;

  if(portIdx === -1){
    parsed.hostname = host;
    // no colon is present inside host
    return;
  }
  parsed.port = host.substring(portIdx + Url.COLON.length);
  parsed.hostname = host.substring(0, portIdx);
}
<commit_msg>fix(lint)<commit_after>'use strict';
/**
  Overly tolerant url parser
 */

var Url = module.exports = {
  PROTOCOL: '://',
  COLON: ':',
  AT: '@',
  parse: function (url) {
    var parsed = {
      protocol: null, // string
      auth: null, // string
      hostname: null, // string
      host: null, // string
      port: null, // string
      href: url, // string
      path: null
    };

    // check protocol
    var protocolIdx = url.indexOf(this.PROTOCOL);
    if (protocolIdx !== -1) {
      parsed.protocol = url.substring(0, protocolIdx + 1);
      protocolIdx += this.PROTOCOL.length;
      // remove the protocol part from the url
      url = url.substring(protocolIdx);
    }

    var atCount = strCount(url, this.AT);
    var colonCount = strCount(url, this.COLON);
    var atIsPresent = atCount > 0;
    var colonIsPresent = colonCount > 0;

    if (!atIsPresent && !colonIsPresent) {
      parsed.host = url;
      parsed.hostname = url;
      // parse is done, there are no port and auth
      return parsed;
    }

    if (colonCount === 1 && !atIsPresent) { // it can only be the port number
      extractPortAndHostnameFromHost(parsed, url);
      return parsed;
    }

    var parts = url.split(this.AT);
    var host = parts.pop();
    extractPortAndHostnameFromHost(parsed, host);
    parsed.auth = parts.join(this.AT);
    return parsed;
  }
};

function strCount(str, substr) {
  if (str === null || substr === null) {
    return 0;
  }
  str = String(str);
  substr = String(substr);

  var count = 0,
    pos = 0,
    length = substr.length;

  while (true) {
    pos = str.indexOf(substr, pos);
    if (pos === -1) {
      break;
    }
    count++;
    pos += length;
  }

  return count;
}

function extractPortAndHostnameFromHost(parsed, host) {
  var portIdx = host.indexOf(Url.COLON);
  parsed.host = host;

  if (portIdx === -1) {
    parsed.hostname = host;
    // no colon is present inside host
    return;
  }
  parsed.port = host.substring(portIdx + Url.COLON.length);
  parsed.hostname = host.substring(0, portIdx);
}
<|endoftext|>


<commit_before>// npm modules
import moment from 'moment';
import momentParseformat from 'moment-parseformat';
import chalk from 'chalk';
import currencyFormatter from 'currency-formatter';
// our modules
import {api, config} from '../utils';

const periods = [];

function definePeriods(args, options) {
  // Add one hour to the default end time to take inconsistencies into account.
  const defaultEndTime = moment.utc().add(1, 'h');
  const defaultStartTime = moment.utc('2014-01-01');
  const currentMonth = moment.utc().format('YYYY-MM');

  const logErrorAndExit = (arg, reason) => {
    console.error(chalk.red(`\nYou have entered an invalid date: "${arg}": ${reason}`));
    console.error('\nPlease enter dates in the following format: "YYYY-MM-DD" or "YYYY-MM"');
    console.error('You can also use the special shortcuts "today" and "yesterday" (ex.: urcli revenue today)');
    process.exit(0);
  };

  function validateDate(date) {
    if (!moment(date).isValid()) {
      const errorReason = `The date "${date}" doesn't exist.`;
      logErrorAndExit(date, errorReason);
    }
    return true;
  }

  function createOptionsPeriod() {
    const start = options.from ? moment.utc(options.from) : defaultStartTime;
    const end = options.to ? moment.utc(options.to) : defaultEndTime;
    periods.push([start, end]);
  }

  function createMonthPeriod(month) {
    const start = moment(month).utc();
    // If month is the current month the end should be today, otherwise it should be the end of the month.
    const end = month === currentMonth ? defaultEndTime : moment(month).utc().add(1, 'M');
    periods.push([start, end]);
  }

  function createDayPeriod(day) {
    const start = moment(day).startOf('day');
    const end = moment(day).endOf('day');
    periods.push([start, end]);
  }

  function createDefaultPeriods() {
    periods.push([defaultStartTime, defaultEndTime]);
    createMonthPeriod(currentMonth);
  }

  if (!args.length && !options.to && !options.from) {
    createDefaultPeriods();
  }
  if (options.from || options.to) {
    if (options.from) validateDate(options.from);
    if (options.to) validateDate(options.to);
    createOptionsPeriod();
  }

  args.forEach((arg) => {
    // Regex expressions to match user input. Note that MM and YYYY-MM inputs
    // are validated by the regex itself, while YYYY-MM-DD has to be validated
    // seperately by the moment library because of leap years and such nonsense.
    const matchMonth = /^\d{1}$|^[01]{1}[012]{1}$/; // 1-9, 01-09 and 10-12.
    const matchYearMonth = /^201\d{1}-\d{2}$|^201\d{1}-1{1}[012]{1}$/; // YYYY-MM.
    const matchYearMonthDay = /^201\d{1}-\d{2}-\d{2}$|^201\d{1}-1{1}[012]{1}-\d{2}$/; // YYYY-MM-DD

    if (matchYearMonthDay.test(arg)) {
      validateDate(arg);
      createDayPeriod(arg);
    } else if (matchYearMonth.test(arg)) {
      createMonthPeriod(arg);
    } else if (matchMonth.test(arg)) {
      const year = moment().month() + 1 < arg ? moment().year() - 1 : moment().year();
      createMonthPeriod(moment().year(year).month(arg - 1).format('YYYY-MM'));
    } else if (arg === 'today') {
      createDayPeriod(moment.utc());
    } else if (arg === 'yesterday') {
      createDayPeriod(moment.utc().subtract(1, 'd'));
    } else {
      if (validateDate(arg)) {
        const errorReason = `The format "${momentParseformat(arg)}" is not supported.`;
        logErrorAndExit(arg, errorReason);
      }
      throw new Error('validateDate failed to throw an error.');
    }
  });
}

class Report {
  constructor(reviews, period) {
    this.startDate = period[0].format('YYYY-MM-DD');
    this.endDate = period[1].format('YYYY-MM-DD');
    // We need to check if we're using the default startDate, so that we can
    // find the earliest date a submission was assigned. This date is needed to
    // get the correct number of days of the period, so we can calculate the
    // average daily earnings for the period.
    if (this.startDate === '2014-01-01') {
      const firstDate = reviews
        .map(review => moment(review.assigned_at)) // returns date of review
        .map(date => date.valueOf()) // returns date in Unix Time (milliseconds from 1970)
        .reduce((acc, val) => {
          if (acc < val) {
            return acc;
          }
          return val;
        }); // returns the smallest number
      this.startDate = moment(firstDate).format('YYYY-MM-DD');
    }
    this.reviews = reviews;
    this.projects = {};
    this.totalEarned = 0;
    this.totalAssigned = 0;
  }

  create() {
    this.reviews.forEach((review) => {
      this.countReview(review);
    });
  }

  countReview(review) {
    const id = review.project_id;
    const price = parseInt(review.price, 10);
    const assignedAt = moment(review.assigned_at);
    const completedAt = moment(review.completed_at);
    const turnaroundTime = completedAt.diff(assignedAt);

    // If the report does not yet contain an entry for the project type, create
    // the entry and try counting the review again.
    if (!Object.prototype.hasOwnProperty.call(this.projects, id)) {
      this.projects[id] = {
        id,
        name: review.project.name,
        passed: 0,
        failed: 0,
        ungradeable: 0,
        earned: 0,
        turnaroundTime: 0,
      };
      this.countReview(review);
    } else {
      this.projects[id][review.result] += 1;
      this.projects[id].earned += price;
      this.projects[id].turnaroundTime += turnaroundTime;
      this.totalEarned += price;
      this.totalAssigned += 1;
    }
  }

  print() {
    console.log(chalk.blue(`\nEarnings Report for ${this.startDate} to ${this.endDate}:`));
    console.log('=============================================');
    console.log(chalk.bgBlack.white(`Total Projects Assigned: ${this.totalAssigned}`));

    Object.keys(this.projects).forEach((key) => {
      const {name, id, ungradeable, passed, failed, earned, turnaroundTime} = this.projects[key];
      const totalAssigned = passed + failed + ungradeable;
      const avgTurnaroundTime = moment.utc(turnaroundTime / (totalAssigned));
      const projectInfo = `Project: ${name} (${id}):`;

      console.log(`
      ${chalk.white(projectInfo)}
      ${chalk.white(new Array(projectInfo.length + 1).join('-'))}
          ${chalk.white(`Total Assigned: ${totalAssigned}`)}
              ${chalk.white(`Reviewed: ${passed + failed}`)}
              ${chalk.white(`Ungradeable: ${ungradeable}`)}
          ${chalk.white(`Earned: ${currencyFormatter.format(earned, {code: 'USD'})}`)}
          ${chalk.white(`Average Turnaround Time: ${avgTurnaroundTime.format('HH:mm')}`)}
      `);
    });

    console.log(chalk.white(`Total Earned: ${currencyFormatter.format(this.totalEarned, {code: 'USD'})}`));

    // Only print Daily Average if we have more than one day to average
    let numberOfDays = moment(this.endDate).diff(this.startDate, 'days');
    // console.log(moment.utc().diff(this.startDate, 'days', true));
    if (numberOfDays > 1) {
      // If current month, add hours of current day to numberOfDays
      const month = moment(this.startDate).format('YYYY-MM');
      const isCurrentMonth = () => month === moment.utc().format('YYYY-MM');
      if (isCurrentMonth()) {
        numberOfDays = moment.utc().diff(this.startDate, 'days', true);
      }
      // Print Daily Average
      console.log(chalk.white(`Daily Average: ${
        currencyFormatter.format(this.totalEarned / numberOfDays, {code: 'USD'})}`));
    }
    console.log('=============================================');
  }
}

function printReports() {
  const resolvedReports = periods.map(async (period) => {
    const task = 'completed';
    const token = config.token;
    const body = {
      start_date: period[0].format('YYYY-MM-DDTHH:MM:ssZ'),
      end_date: period[1].format('YYYY-MM-DDTHH:MM:ssZ'),
    };

    const completed = await api({token, task, body});
    const report = new Report(completed.body, period);
    report.create();
    report.print();
    return Promise.resolve();
  });
  Promise.all(resolvedReports).then(() => process.exit(0));
}

export const revenueCmd = (args, options) => {
  definePeriods(args, options);
  printReports();
};
<commit_msg>Certify that numberOfDays are in UTC<commit_after>// npm modules
import moment from 'moment';
import momentParseformat from 'moment-parseformat';
import chalk from 'chalk';
import currencyFormatter from 'currency-formatter';
// our modules
import {api, config} from '../utils';

const periods = [];

function definePeriods(args, options) {
  // Add one hour to the default end time to take inconsistencies into account.
  const defaultEndTime = moment.utc().add(1, 'h');
  const defaultStartTime = moment.utc('2014-01-01');
  const currentMonth = moment.utc().format('YYYY-MM');

  const logErrorAndExit = (arg, reason) => {
    console.error(chalk.red(`\nYou have entered an invalid date: "${arg}": ${reason}`));
    console.error('\nPlease enter dates in the following format: "YYYY-MM-DD" or "YYYY-MM"');
    console.error('You can also use the special shortcuts "today" and "yesterday" (ex.: urcli revenue today)');
    process.exit(0);
  };

  function validateDate(date) {
    if (!moment(date).isValid()) {
      const errorReason = `The date "${date}" doesn't exist.`;
      logErrorAndExit(date, errorReason);
    }
    return true;
  }

  function createOptionsPeriod() {
    const start = options.from ? moment.utc(options.from) : defaultStartTime;
    const end = options.to ? moment.utc(options.to) : defaultEndTime;
    periods.push([start, end]);
  }

  function createMonthPeriod(month) {
    const start = moment(month).utc();
    // If month is the current month the end should be today, otherwise it should be the end of the month.
    const end = month === currentMonth ? defaultEndTime : moment(month).utc().add(1, 'M');
    periods.push([start, end]);
  }

  function createDayPeriod(day) {
    const start = moment(day).startOf('day');
    const end = moment(day).endOf('day');
    periods.push([start, end]);
  }

  function createDefaultPeriods() {
    periods.push([defaultStartTime, defaultEndTime]);
    createMonthPeriod(currentMonth);
  }

  if (!args.length && !options.to && !options.from) {
    createDefaultPeriods();
  }
  if (options.from || options.to) {
    if (options.from) validateDate(options.from);
    if (options.to) validateDate(options.to);
    createOptionsPeriod();
  }

  args.forEach((arg) => {
    // Regex expressions to match user input. Note that MM and YYYY-MM inputs
    // are validated by the regex itself, while YYYY-MM-DD has to be validated
    // seperately by the moment library because of leap years and such nonsense.
    const matchMonth = /^\d{1}$|^[01]{1}[012]{1}$/; // 1-9, 01-09 and 10-12.
    const matchYearMonth = /^201\d{1}-\d{2}$|^201\d{1}-1{1}[012]{1}$/; // YYYY-MM.
    const matchYearMonthDay = /^201\d{1}-\d{2}-\d{2}$|^201\d{1}-1{1}[012]{1}-\d{2}$/; // YYYY-MM-DD

    if (matchYearMonthDay.test(arg)) {
      validateDate(arg);
      createDayPeriod(arg);
    } else if (matchYearMonth.test(arg)) {
      createMonthPeriod(arg);
    } else if (matchMonth.test(arg)) {
      const year = moment().month() + 1 < arg ? moment().year() - 1 : moment().year();
      createMonthPeriod(moment().year(year).month(arg - 1).format('YYYY-MM'));
    } else if (arg === 'today') {
      createDayPeriod(moment.utc());
    } else if (arg === 'yesterday') {
      createDayPeriod(moment.utc().subtract(1, 'd'));
    } else {
      if (validateDate(arg)) {
        const errorReason = `The format "${momentParseformat(arg)}" is not supported.`;
        logErrorAndExit(arg, errorReason);
      }
      throw new Error('validateDate failed to throw an error.');
    }
  });
}

class Report {
  constructor(reviews, period) {
    this.startDate = period[0].format('YYYY-MM-DD');
    this.endDate = period[1].format('YYYY-MM-DD');
    // We need to check if we're using the default startDate, so that we can
    // find the earliest date a submission was assigned. This date is needed to
    // get the correct number of days of the period, so we can calculate the
    // average daily earnings for the period.
    if (this.startDate === '2014-01-01') {
      const firstDate = reviews
        .map(review => moment(review.assigned_at)) // returns date of review
        .map(date => date.valueOf()) // returns date in Unix Time (milliseconds from 1970)
        .reduce((acc, val) => {
          if (acc < val) {
            return acc;
          }
          return val;
        }); // returns the smallest number
      this.startDate = moment(firstDate).format('YYYY-MM-DD');
    }
    this.reviews = reviews;
    this.projects = {};
    this.totalEarned = 0;
    this.totalAssigned = 0;
  }

  create() {
    this.reviews.forEach((review) => {
      this.countReview(review);
    });
  }

  countReview(review) {
    const id = review.project_id;
    const price = parseInt(review.price, 10);
    const assignedAt = moment(review.assigned_at);
    const completedAt = moment(review.completed_at);
    const turnaroundTime = completedAt.diff(assignedAt);

    // If the report does not yet contain an entry for the project type, create
    // the entry and try counting the review again.
    if (!Object.prototype.hasOwnProperty.call(this.projects, id)) {
      this.projects[id] = {
        id,
        name: review.project.name,
        passed: 0,
        failed: 0,
        ungradeable: 0,
        earned: 0,
        turnaroundTime: 0,
      };
      this.countReview(review);
    } else {
      this.projects[id][review.result] += 1;
      this.projects[id].earned += price;
      this.projects[id].turnaroundTime += turnaroundTime;
      this.totalEarned += price;
      this.totalAssigned += 1;
    }
  }

  print() {
    console.log(chalk.blue(`\nEarnings Report for ${this.startDate} to ${this.endDate}:`));
    console.log('=============================================');
    console.log(chalk.bgBlack.white(`Total Projects Assigned: ${this.totalAssigned}`));

    Object.keys(this.projects).forEach((key) => {
      const {name, id, ungradeable, passed, failed, earned, turnaroundTime} = this.projects[key];
      const totalAssigned = passed + failed + ungradeable;
      const avgTurnaroundTime = moment.utc(turnaroundTime / (totalAssigned));
      const projectInfo = `Project: ${name} (${id}):`;

      console.log(`
      ${chalk.white(projectInfo)}
      ${chalk.white(new Array(projectInfo.length + 1).join('-'))}
          ${chalk.white(`Total Assigned: ${totalAssigned}`)}
              ${chalk.white(`Reviewed: ${passed + failed}`)}
              ${chalk.white(`Ungradeable: ${ungradeable}`)}
          ${chalk.white(`Earned: ${currencyFormatter.format(earned, {code: 'USD'})}`)}
          ${chalk.white(`Average Turnaround Time: ${avgTurnaroundTime.format('HH:mm')}`)}
      `);
    });

    console.log(chalk.white(`Total Earned: ${currencyFormatter.format(this.totalEarned, {code: 'USD'})}`));

    // Only print Daily Average if we have more than one day to average
    let numberOfDays = moment(this.endDate).diff(this.startDate, 'days');
    // console.log(moment.utc().diff(this.startDate, 'days', true));
    if (numberOfDays > 1) {
      // If current month, add hours of current day to numberOfDays
      const month = moment(this.startDate).format('YYYY-MM');
      const isCurrentMonth = () => month === moment.utc().format('YYYY-MM');
      if (isCurrentMonth()) {
        numberOfDays = moment.utc().diff(moment.utc(this.startDate), 'days', true);
      }
      // Print Daily Average
      console.log(chalk.white(`Daily Average: ${
        currencyFormatter.format(this.totalEarned / numberOfDays, {code: 'USD'})}`));
    }
    console.log('=============================================');
  }
}

function printReports() {
  const resolvedReports = periods.map(async (period) => {
    const task = 'completed';
    const token = config.token;
    const body = {
      start_date: period[0].format('YYYY-MM-DDTHH:MM:ssZ'),
      end_date: period[1].format('YYYY-MM-DDTHH:MM:ssZ'),
    };

    const completed = await api({token, task, body});
    const report = new Report(completed.body, period);
    report.create();
    report.print();
    return Promise.resolve();
  });
  Promise.all(resolvedReports).then(() => process.exit(0));
}

export const revenueCmd = (args, options) => {
  definePeriods(args, options);
  printReports();
};
<|endoftext|>


<commit_before>"use strict";

// Unpkg imports
const Web3Modal = window.Web3Modal.default;
const WalletConnectProvider = window.WalletConnectProvider.default;
const Fortmatic = window.Fortmatic;
const evmChains = window.evmChains;

// Web3modal instance
let web3Modal;

// Chosen wallet provider given by the dialog window
let provider;

/**
 * Setup the orchestra
 */
function init() {

  console.log("WalletConnectProvider is", WalletConnectProvider);
  console.log("Fortmatic is", Fortmatic);
  console.log("window.web3 is", window.web3, "window.ethereum is", window.ethereum);

  // Check that the web page is run in a secure context,
  // as otherwise MetaMask won't be available
  if (location.protocol !== 'https:') {
    // https://ethereum.stackexchange.com/a/62217/620
    // TODO:
  }

  // Tell Web3modal what providers we have available.
  // Built-in web browser provider (only one can exist as a time)
  // like MetaMask, Brave or Opera is added automatically by Web3modal
  const providerOptions = {
    walletconnect: {
      package: WalletConnectProvider,
      options: {
        infuraId: "37c4b40db8fe47fb90a1e2ac4ff152eb",
      }
    }
  };

  web3Modal = new Web3Modal({
    cacheProvider: true, // optional
    providerOptions, // required
    disableInjectedProvider: false, // optional. For MetaMask / Brave / Opera.
  });

  console.log("Web3Modal instance is", web3Modal);
}

/**
 * Connect to the wallet provider.
 */
async function connect() {
  init();
  console.log("Opening a dialog", web3Modal);
  try {
    provider = await web3Modal.connect();
  } catch(e) {
    console.log("Could not get a wallet connection", e);
    return;
  }
  // Subscribe to accounts change
  provider.on("accountsChanged",async (accounts) => {
     // TODO:
  });

  // Subscribe to chainId change
  provider.on("chainChanged",async (chainId) => {
     // TODO:
  });

  // Subscribe to networkId change
  provider.on("networkChanged",async (networkId) => {
     // TODO:
  });

  return provider;
}

async function disconnect() {
  console.log("Killing the wallet connection", provider);

  // TODO: Which providers have close method?
  if (provider.close) {
    await provider.close();

    // If the cached provider is not cleared,
    // WalletConnect will default to the existing session
    // and does not allow to re-scan the QR code with a new wallet.
    // Depending on your use case you may want or want not his behavir.
    provider = null;
  }
  await web3Modal.clearCachedProvider();

}

/**
 * Main entry point.
 */
$( document ).ready(function() {

  $('#logout').on('click', async function(e) {
    e.preventDefault();
    await disconnect();
    window.location = $(this).attr('href');
  });
});<commit_msg>fix #1576<commit_after>"use strict";

// Unpkg imports
const Web3Modal = window.Web3Modal.default;
const WalletConnectProvider = window.WalletConnectProvider.default;
const Fortmatic = window.Fortmatic;
const evmChains = window.evmChains;

// Web3modal instance
let web3Modal;

// Chosen wallet provider given by the dialog window
let provider;

/**
 * Setup the orchestra
 */
function init() {

  console.log("WalletConnectProvider is", WalletConnectProvider);
  console.log("Fortmatic is", Fortmatic);
  console.log("window.web3 is", window.web3, "window.ethereum is", window.ethereum);

  // Check that the web page is run in a secure context,
  // as otherwise MetaMask won't be available
  if (location.protocol !== 'https:') {
    // https://ethereum.stackexchange.com/a/62217/620
    // TODO:
  }

  // Tell Web3modal what providers we have available.
  // Built-in web browser provider (only one can exist as a time)
  // like MetaMask, Brave or Opera is added automatically by Web3modal
  const providerOptions = {
    walletconnect: {
      package: WalletConnectProvider,
      options: {
        infuraId: "37c4b40db8fe47fb90a1e2ac4ff152eb",
      }
    }
  };

  web3Modal = new Web3Modal({
    cacheProvider: true, // optional
    providerOptions, // required
    disableInjectedProvider: false, // optional. For MetaMask / Brave / Opera.
  });

  console.log("Web3Modal instance is", web3Modal);
}

/**
 * Connect to the wallet provider.
 */
async function connect() {
  if (web3Modal === undefined){
    init();
  }
  console.log("Opening a dialog", web3Modal);
  try {
    provider = await web3Modal.connect();
  } catch(e) {
    console.log("Could not get a wallet connection", e);
    return;
  }
  // Subscribe to accounts change
  provider.on("accountsChanged",async (accounts) => {
     // TODO:
  });

  // Subscribe to chainId change
  provider.on("chainChanged",async (chainId) => {
     // TODO:
  });

  // Subscribe to networkId change
  provider.on("networkChanged",async (networkId) => {
     // TODO:
  });

  return provider;
}

async function disconnect() {
  console.log("Killing the wallet connection", provider);

  // TODO: Which providers have close method?
  if (provider.close) {
    await provider.close();

    // If the cached provider is not cleared,
    // WalletConnect will default to the existing session
    // and does not allow to re-scan the QR code with a new wallet.
    // Depending on your use case you may want or want not his behavir.
    provider = null;
  }
  await web3Modal.clearCachedProvider();
}

/**
 * Main entry point.
 */
$( document ).ready(function() {

  $('#logout').on('click', async function(e) {
    e.preventDefault();
    await disconnect();
    window.location = $(this).attr('href');
  });
});<|endoftext|>


<commit_before>var session = require('../session');
var compact = require('lodash-node/modern/arrays/compact');
var utils = require('../utils');
var i18n = require('../i18n');
var formWidgets = require('./_formWidgets');
var settings = require('../settings');
var Zanimo = require('zanimo');
var gamesMenu = require('./gamesMenu');
var loginModal = require('./loginModal');

var menu = {};

menu.isOpen = false;
var settingsOpen = false;

function openSettings() {
  settingsOpen = true;
}

function closeSettings() {
  settingsOpen = false;
}

// we need to transition manually the menu on route change, because mithril's
// diff strategy is 'all'
function menuRouteAction(route) {
  return function() {
    menu.close();
    return Zanimo(document.getElementById('page'), 'transform', 'translate3d(0,0,0)',
      '200', 'ease-out').then(utils.ƒ(m.route, route));
  };
}

menu.toggle = function() {
  menu.isOpen = !menu.isOpen;
};

menu.open = function() {
  menu.isOpen = true;
};

menu.close = function() {
  menu.isOpen = false;
  closeSettings();
};

var perfsOpen = m.prop(false);

var perfTypes = [
  ['bullet', 'Bullet'],
  ['chess960', 'Chess960'],
  ['blitz', 'Blitz'],
  ['kingOfTheHill', 'King Of The Hill'],
  ['classical', 'Classical'],
  ['threeCheck', 'Three-check'],
  ['correspondence', 'Correspondence'],
  ['antichess', 'Antichess'],
  ['puzzle', 'Training'],
  ['atomic', 'Atomic']
];

function renderPerf(key, name, perf) {
  return m('div.perf', {
    'data-icon': utils.variantIconsMap[key]
  }, [
    m('span.name', name),
    m('div.rating', [
      perf.rating,
      utils.progress(perf.prog),
      m('span.nb', '/ ' + perf.games)
    ])
  ]);
}

function openPerfs(user) {
  return perfTypes.map(function(p) {
    return p ? renderPerf(p[0], p[1], user.perfs[p[0]]) : m('div.perf');
  });
};

function closedPerfs(user) {
  var perfs = compact(Object.keys(user.perfs).map(function(key) {
    var type = perfTypes.filter(function(pt) {
      return pt[0] === key && 'puzzle' !== key;
    })[0];
    if (type) return {
      key: key,
      name: type[1],
      perf: user.perfs[key]
    };
  }));
  return perfs.sort(function(a, b) {
    return a.games < b.games;
  }).slice(0, 2).map(function(p) {
    return renderPerf(p.key, p.name, p.perf);
  });
};

menu.view = function(onSettingChange) {
  if (!menu.isOpen) return;
  if (settingsOpen) return m('div#settings', [
    m('header', [
      m('button[data-icon=L]', {
        config: utils.ontouchend(closeSettings)
      }),
      m('h2', i18n('settings'))
    ]),
    m('section', [
      formWidgets.renderCheckbox(i18n('pieceAnimation'), 'animations',
        settings.onChange(settings.general.animations, onSettingChange)),
      formWidgets.renderCheckbox(i18n('pieceDestinations'), 'pieceDestinations',
        settings.onChange(settings.general.pieceDestinations, onSettingChange)),
      formWidgets.renderCheckbox(i18n('toggleSound'), 'sound', settings.general.sound),
    ]),
    m('section.app_version', window.lichess.version)
  ]);
  var user = session.get();
  var header = user ? [
    m('h2', user.username),
    m('section.ratings', {
      config: utils.ontouchend(function() {
        perfsOpen(!perfsOpen());
      })
    }, perfsOpen() ? openPerfs(user) : closedPerfs(user))
  ] : [
    m('h2', i18n('notConnected')),
    m('button.login', {
      config: utils.ontouchend(loginModal.open)
    }, i18n('login'))
  ];
  header.unshift(
    m('div.logo', [
      m('button.settings[data-icon=%]', {
        config: utils.ontouchend(openSettings)
      })
    ])
  );
  var nowPlaying = session.nowPlaying();
  var links = [
    m('li.side_link', {
      config: utils.ontouchend(function() {
        menu.close();
        setTimeout(gamesMenu.openNewGame, 1);
      })
    }, i18n('createAGame')),
    m('li.side_link', {
      config: utils.ontouchend(menuRouteAction('/otb'))
    }, i18n('playOnTheBoardOffline'))
  ];
  if (session.isConnected()) {
    links.unshift(
      m('li.side_link', {
        class: utils.classSet({
          disabled: nowPlaying.length === 0
        }),
        config: utils.ontouchend(function() {
          if (session.nowPlaying().length) {
            menu.close();
            setTimeout(gamesMenu.openCurrentGames, 1);
          }
        })
      }, [i18n('playingRightNow'), m('span.highlight', ' (' + nowPlaying.length + ')')])
    );
    links.push(
      m('li.side_link.logout[data-icon=w]', {
        config: utils.ontouchend(session.logout)
      }, i18n('logOut'))
    );
  }
  return [
    m('header.side_menu_header', header),
    m('nav#side_links', m('ul', links))
  ];
};

module.exports = menu;
<commit_msg>fix menu double touch issue<commit_after>var session = require('../session');
var compact = require('lodash-node/modern/arrays/compact');
var utils = require('../utils');
var i18n = require('../i18n');
var formWidgets = require('./_formWidgets');
var settings = require('../settings');
var Zanimo = require('zanimo');
var gamesMenu = require('./gamesMenu');
var loginModal = require('./loginModal');

var menu = {};

menu.isOpen = false;
var settingsOpen = false;

function openSettings() {
  settingsOpen = true;
}

function closeSettings() {
  settingsOpen = false;
}

// we need to transition manually the menu on route change, because mithril's
// diff strategy is 'all'
function menuRouteAction(route) {
  return function() {
    menu.close();
    return Zanimo(document.getElementById('page'), 'transform', 'translate3d(0,0,0)',
      '200', 'ease-out').then(utils.ƒ(m.route, route));
  };
}

menu.toggle = function() {
  menu.isOpen = !menu.isOpen;
};

menu.open = function() {
  menu.isOpen = true;
};

menu.close = function() {
  menu.isOpen = false;
  closeSettings();
};

var perfsOpen = m.prop(false);

var perfTypes = [
  ['bullet', 'Bullet'],
  ['chess960', 'Chess960'],
  ['blitz', 'Blitz'],
  ['kingOfTheHill', 'King Of The Hill'],
  ['classical', 'Classical'],
  ['threeCheck', 'Three-check'],
  ['correspondence', 'Correspondence'],
  ['antichess', 'Antichess'],
  ['puzzle', 'Training'],
  ['atomic', 'Atomic']
];

function renderPerf(key, name, perf) {
  return m('div.perf', {
    'data-icon': utils.variantIconsMap[key]
  }, [
    m('span.name', name),
    m('div.rating', [
      perf.rating,
      utils.progress(perf.prog),
      m('span.nb', '/ ' + perf.games)
    ])
  ]);
}

function openPerfs(user) {
  return perfTypes.map(function(p) {
    return p ? renderPerf(p[0], p[1], user.perfs[p[0]]) : m('div.perf');
  });
};

function closedPerfs(user) {
  var perfs = compact(Object.keys(user.perfs).map(function(key) {
    var type = perfTypes.filter(function(pt) {
      return pt[0] === key && 'puzzle' !== key;
    })[0];
    if (type) return {
      key: key,
      name: type[1],
      perf: user.perfs[key]
    };
  }));
  return perfs.sort(function(a, b) {
    return a.games < b.games;
  }).slice(0, 2).map(function(p) {
    return renderPerf(p.key, p.name, p.perf);
  });
};

menu.view = function(onSettingChange) {
  if (!menu.isOpen) return;
  if (settingsOpen) return m('div#settings', [
    m('header', [
      m('button[data-icon=L]', {
        config: utils.ontouchend(closeSettings)
      }),
      m('h2', i18n('settings'))
    ]),
    m('section', [
      formWidgets.renderCheckbox(i18n('pieceAnimation'), 'animations',
        settings.onChange(settings.general.animations, onSettingChange)),
      formWidgets.renderCheckbox(i18n('pieceDestinations'), 'pieceDestinations',
        settings.onChange(settings.general.pieceDestinations, onSettingChange)),
      formWidgets.renderCheckbox(i18n('toggleSound'), 'sound', settings.general.sound),
    ]),
    m('section.app_version', window.lichess.version)
  ]);
  var user = session.get();
  var header = user ? [
    m('h2', user.username),
    m('section.ratings', {
      config: utils.ontouchend(function() {
        perfsOpen(!perfsOpen());
      })
    }, perfsOpen() ? openPerfs(user) : closedPerfs(user))
  ] : [
    m('h2', i18n('notConnected')),
    m('button.login', {
      config: utils.ontouchend(loginModal.open)
    }, i18n('login'))
  ];
  header.unshift(
    m('div.logo', [
      m('button.settings[data-icon=%]', {
        config: utils.ontouchend(openSettings)
      })
    ])
  );
  var nowPlaying = session.nowPlaying();
  var links = [
    m('li.side_link', {
      config: utils.ontouchend(function(e) {
        e.stopPropagation();
        e.preventDefault();
        menu.close();
        gamesMenu.openNewGame();
      })
    }, i18n('createAGame')),
    m('li.side_link', {
      config: utils.ontouchend(menuRouteAction('/otb'))
    }, i18n('playOnTheBoardOffline'))
  ];
  if (session.isConnected()) {
    links.unshift(
      m('li.side_link', {
        class: utils.classSet({
          disabled: nowPlaying.length === 0
        }),
        config: utils.ontouchend(function(e) {
          e.stopPropagation();
          e.preventDefault();
          if (session.nowPlaying().length) {
            menu.close();
            gamesMenu.openCurrentGames();
          }
        })
      }, [i18n('playingRightNow'), m('span.highlight', ' (' + nowPlaying.length + ')')])
    );
    links.push(
      m('li.side_link.logout[data-icon=w]', {
        config: utils.ontouchend(session.logout)
      }, i18n('logOut'))
    );
  }
  return [
    m('header.side_menu_header', header),
    m('nav#side_links', m('ul', links))
  ];
};

module.exports = menu;
<|endoftext|>


<commit_before>/*!
 * connect-jugglingdb
 * Copyright 2013 Jérémy Lal <kapouer@melix.org>
 * MIT Licensed, see LICENSE file
 */

/**
 * Default options
 */

var defaults = {
	table: 'sessions',
	maxAge: 1000 * 60 * 60 * 24 * 14 // even session cookies should be destroyed, eventually
};

function noop() {}

module.exports = function(session) {
	"use strict";
	var Store = session.Store;

	/**
	 * Initialize JugglingStore with the given `options`.
	 *
	 * @param {JugglingDB.Schema} schema
	 * @param {Object} options
	 * @api public
	 */

	function JugglingStore(schema, options) {
		options = options || {};
		Store.call(this, options);
		this.maxAge = options.maxAge || defaults.maxAge;
		var coll = this.collection = schema.define('Session', {
			sid: {
				type: String,
				index: true
			},
			expires: {
				type: Date,
				index: true
			},
			session: schema.constructor.JSON
		}, {
			table: options.table || defaults.table
		});

		coll.validatesUniquenessOf('sid');

		// destroy all expired sessions after each create/update
		coll.afterSave = function(next) {
			coll.iterate({where: {
				expires: {lte: new Date()}
			}}, function(obj, nexti, i) {
				obj.destroy(nexti);
			}, next);
		};
	}

	/**
	 * Inherit from `Store`.
	 */

	require('util').inherits(JugglingStore, Store);

	/**
	 * Attempt to fetch session by the given `sid`.
	 *
	 * @param {String} sid
	 * @param {Function} callback
	 * @api public
	 */

	JugglingStore.prototype.get = function(sid, callback) {
		var self = this;
		callback = callback || noop;
		this.collection.findOne({where: {sid: sid}}, function(err, session) {
			if (err) return callback(err);
			if (!session) return callback();
			if (!session.expires || new Date() < session.expires) {
				callback(null, session.session);
			} else {
				self.destroy(sid, callback);
			}
		});
	};

	/**
	 * Commit the given `session` object associated with the given `sid`.
	 *
	 * @param {String} sid
	 * @param {Session} session
	 * @param {Function} callback
	 * @api public
	 */

	JugglingStore.prototype.set = function(sid, session, callback) {
		callback = callback || noop;
		var s = {
			session: session
		};
		if (session && session.cookie && session.cookie.expires) {
			s.expires = new Date(session.cookie.expires);
		} else {
			s.expires = new Date(Date.now() + this.maxAge);
		}
		var coll = this.collection;
		coll.findOne({where: {sid: sid}}, function(err, session) {
			if (err) return callback(err);
			if (session) {
				session.updateAttributes(s, function(err) {
					callback(err);
				});
			} else {
				s.sid = sid;
				coll.create(s, function(err) {
					callback(err);
				});
			}
		});
	};

	/**
	 * Destroy the session associated with the given `sid`.
	 *
	 * @param {String} sid
	 * @param {Function} callback
	 * @api public
	 */

	JugglingStore.prototype.destroy = function(sid, callback) {
		callback = callback || noop;
		var coll = this.collection;
		coll.findOne({where: {sid: sid}}, function(err, session) {
			if (err) return callback(err);
			if (!session) return callback();
			session.destroy(callback);
		});
	};

	/**
	 * Fetch number of sessions.
	 *
	 * @param {Function} callback
	 * @api public
	 */

	JugglingStore.prototype.length = function(callback) {
		this.collection.count(callback);
	};

	/**
	 * Clear all sessions.
	 *
	 * @param {Function} callback
	 * @api public
	 */

	JugglingStore.prototype.clear = function(callback) {
		this.collection.destroyAll(callback);
	};

	return JugglingStore;
};
<commit_msg>Updated as per @kapouer's comment<commit_after>/*!
 * connect-jugglingdb
 * Copyright 2013 Jérémy Lal <kapouer@melix.org>
 * MIT Licensed, see LICENSE file
 */

/**
 * Default options
 */

var defaults = {
	table: 'sessions',
	maxAge: 1000 * 60 * 60 * 24 * 14 // even session cookies should be destroyed, eventually
};

function noop() {}

module.exports = function(session) {
	"use strict";
        var Store = session.Store || session.session.Store

	/**
	 * Initialize JugglingStore with the given `options`.
	 *
	 * @param {JugglingDB.Schema} schema
	 * @param {Object} options
	 * @api public
	 */

	function JugglingStore(schema, options) {
		options = options || {};
		Store.call(this, options);
		this.maxAge = options.maxAge || defaults.maxAge;
		var coll = this.collection = schema.define('Session', {
			sid: {
				type: String,
				index: true
			},
			expires: {
				type: Date,
				index: true
			},
			session: schema.constructor.JSON
		}, {
			table: options.table || defaults.table
		});

		coll.validatesUniquenessOf('sid');

		// destroy all expired sessions after each create/update
		coll.afterSave = function(next) {
			coll.iterate({where: {
				expires: {lte: new Date()}
			}}, function(obj, nexti, i) {
				obj.destroy(nexti);
			}, next);
		};
	}

	/**
	 * Inherit from `Store`.
	 */

	require('util').inherits(JugglingStore, Store);

	/**
	 * Attempt to fetch session by the given `sid`.
	 *
	 * @param {String} sid
	 * @param {Function} callback
	 * @api public
	 */

	JugglingStore.prototype.get = function(sid, callback) {
		var self = this;
		callback = callback || noop;
		this.collection.findOne({where: {sid: sid}}, function(err, session) {
			if (err) return callback(err);
			if (!session) return callback();
			if (!session.expires || new Date() < session.expires) {
				callback(null, session.session);
			} else {
				self.destroy(sid, callback);
			}
		});
	};

	/**
	 * Commit the given `session` object associated with the given `sid`.
	 *
	 * @param {String} sid
	 * @param {Session} session
	 * @param {Function} callback
	 * @api public
	 */

	JugglingStore.prototype.set = function(sid, session, callback) {
		callback = callback || noop;
		var s = {
			session: session
		};
		if (session && session.cookie && session.cookie.expires) {
			s.expires = new Date(session.cookie.expires);
		} else {
			s.expires = new Date(Date.now() + this.maxAge);
		}
		var coll = this.collection;
		coll.findOne({where: {sid: sid}}, function(err, session) {
			if (err) return callback(err);
			if (session) {
				session.updateAttributes(s, function(err) {
					callback(err);
				});
			} else {
				s.sid = sid;
				coll.create(s, function(err) {
					callback(err);
				});
			}
		});
	};

	/**
	 * Destroy the session associated with the given `sid`.
	 *
	 * @param {String} sid
	 * @param {Function} callback
	 * @api public
	 */

	JugglingStore.prototype.destroy = function(sid, callback) {
		callback = callback || noop;
		var coll = this.collection;
		coll.findOne({where: {sid: sid}}, function(err, session) {
			if (err) return callback(err);
			if (!session) return callback();
			session.destroy(callback);
		});
	};

	/**
	 * Fetch number of sessions.
	 *
	 * @param {Function} callback
	 * @api public
	 */

	JugglingStore.prototype.length = function(callback) {
		this.collection.count(callback);
	};

	/**
	 * Clear all sessions.
	 *
	 * @param {Function} callback
	 * @api public
	 */

	JugglingStore.prototype.clear = function(callback) {
		this.collection.destroyAll(callback);
	};

	return JugglingStore;
};
<|endoftext|>


<commit_before>enterUrlMsg = "Paste or drag-and-drop URL of video...";

pauseImgSrc = "//cdn.rawgit.com/iconic/open-iconic/master/png/media-pause-4x.png";
playImgSrc = "//cdn.rawgit.com/iconic/open-iconic/master/png/media-play-4x.png";

var videoUrl;
var videoName;
var videoTime = null;

var videos = {};
var videoCounter = 0;
var videoIteration = 0;
var videoPaused;
var timer;

function Timer(callback, delay) {
  var timerId, start, remaining = delay;

  this.pause = function() {
      window.clearTimeout(timerId);
      remaining -= new Date() - start;
  };

  this.resume = function() {
      start = new Date();
      window.clearTimeout(timerId);
      timerId = window.setTimeout(callback, remaining);
  };

  this.resume();
}

function msConversion(millis) {
  var minutes = Math.floor(millis / 60000);
  var seconds = ((millis % 60000) / 1000).toFixed(0);
  return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
}

function highlight(i) {
  $("tr:nth-child(" + i + ")").attr("id", "newSelected");
  $("tr.selected").removeClass("selected");
  $("#newSelected").addClass("selected");
  $("#newSelected").removeAttr("id");
}

function playVideo() {
  highlight(videoIteration);
  document.title = "Streamly - " + videos[videoIteration]["name"];

  var embedUrl = videos[videoIteration]["url"].replace("/watch?v=", "/embed/") + "?autoplay=1";
  $("#youtube").attr("src", embedUrl);
  $("#pauseImg").attr("src", pauseImgSrc);
}

function loopVideo() {
  videoIteration++;
  playVideo();
  timer = new Timer(function() {
    if (videoIteration < videoCounter) {
      loopVideo();
    }
    else {
      timer.pause();
      timer = 0;
      $("#youtube").attr("src", "");
      document.title = "Streamly";
    }
  }, videos[videoIteration]["time"] + 2000);
}

function pauseVideo() {
  if (!videoPaused) {
    timer.pause();
    $("#pauseImg").attr("src", playImgSrc);
    videoPaused = true;
  }
  else {
    timer.resume();
    $("#pauseImg").attr("src", pauseImgSrc);
    videoPaused = false;
  }
}

function backVideo() {
  if (videoIteration - 2 > -1) {
    videoIteration = videoIteration - 2;
    if (timer != 0) {
      timer.pause();
    }
    timer = 0;
    loopVideo();
  }
}

function forwardVideo() {
  if (videoIteration + 1 <= videoCounter) {
    if (timer != 0) {
      timer.pause();
    }
    timer = 0;
    loopVideo();
  }
}

function Base64EncodeUrl(str){
  str = window.btoa(str);
  return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
}

function Base64DecodeUrl(str){
  str = (str + '===').slice(0, str.length + (str.length % 4));
  str = str.replace(/-/g, '+').replace(/_/g, '/');
  return window.atob(str);
}

function setPlaylist() {
  var playlist = JSON.stringify(videos);
  playlist = Base64EncodeUrl(playlist);
  window.location.hash = playlist;
}

function getPlaylist() {
  if (window.location.hash.substr(1) !== "") {
    var playlist = window.location.hash.substr(1);
    playlist = Base64DecodeUrl(playlist);
    playlist = JSON.parse(playlist);
    videos = playlist;

    for (var key in videos) {
      videoCounter++;
      var printTime = msConversion(videos[videoCounter]["time"]);
      $("#videosTable").append("
" + videos[videoCounter]["name"] + "	" + printTime + "
");
    }
    loopVideo();
  }
}

function getVideoData() {
  $.ajax({
    url: videoUrl,
    type: 'GET',
    success: function(res) {
      var data = $(res.responseText);
      videoName = data.find("span#eow-title");
      videoName = videoName[0].textContent;
      videoTime = null;
      for (iteration in data) {
        var str = data[iteration].innerHTML;
        if (videoTime == null && typeof str != "undefined") {
          videoTime = str.match(/,"length_seconds":"\d+",/g);
        }
      }
      videoTime = videoTime[0];
      videoTime = videoTime.replace(/,"length_seconds":"/g, "").replace(/",/g, "");
      videoTime = +videoTime * 1000;
    }
  });
}

function addVideo() {
  videoCounter++;
  videos[videoCounter] = {};
  videos[videoCounter]["name"] = videoName;
  videos[videoCounter]["time"] = videoTime;
  videos[videoCounter]["url"] = videoUrl;

  var printTime = msConversion(videos[videoCounter]["time"]);

  $("#videosTable").append("
" + videoName + "	" + printTime + "
");

  setPlaylist();

  if (videoCounter == 1 || timer == 0) {
    loopVideo();
  }
}

function input() {
  switch ($("#inputBox").attr("placeholder")) {
    case enterUrlMsg:
      videoUrl = $("#inputBox").val();
      $("#inputBox").val("");

      getVideoData();

      $(document).ajaxStop(function() {
        alert("Here...");
        addVideo();
      });
      break;
  }
}
<commit_msg>moved ajaxstop outside of function<commit_after>enterUrlMsg = "Paste or drag-and-drop URL of video...";

pauseImgSrc = "//cdn.rawgit.com/iconic/open-iconic/master/png/media-pause-4x.png";
playImgSrc = "//cdn.rawgit.com/iconic/open-iconic/master/png/media-play-4x.png";

var videoUrl;
var videoName;
var videoTime = null;

var videos = {};
var videoCounter = 0;
var videoIteration = 0;
var videoPaused;
var timer;

function Timer(callback, delay) {
  var timerId, start, remaining = delay;

  this.pause = function() {
      window.clearTimeout(timerId);
      remaining -= new Date() - start;
  };

  this.resume = function() {
      start = new Date();
      window.clearTimeout(timerId);
      timerId = window.setTimeout(callback, remaining);
  };

  this.resume();
}

function msConversion(millis) {
  var minutes = Math.floor(millis / 60000);
  var seconds = ((millis % 60000) / 1000).toFixed(0);
  return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
}

function highlight(i) {
  $("tr:nth-child(" + i + ")").attr("id", "newSelected");
  $("tr.selected").removeClass("selected");
  $("#newSelected").addClass("selected");
  $("#newSelected").removeAttr("id");
}

function playVideo() {
  highlight(videoIteration);
  document.title = "Streamly - " + videos[videoIteration]["name"];

  var embedUrl = videos[videoIteration]["url"].replace("/watch?v=", "/embed/") + "?autoplay=1";
  $("#youtube").attr("src", embedUrl);
  $("#pauseImg").attr("src", pauseImgSrc);
}

function loopVideo() {
  videoIteration++;
  playVideo();
  timer = new Timer(function() {
    if (videoIteration < videoCounter) {
      loopVideo();
    }
    else {
      timer.pause();
      timer = 0;
      $("#youtube").attr("src", "");
      document.title = "Streamly";
    }
  }, videos[videoIteration]["time"] + 2000);
}

function pauseVideo() {
  if (!videoPaused) {
    timer.pause();
    $("#pauseImg").attr("src", playImgSrc);
    videoPaused = true;
  }
  else {
    timer.resume();
    $("#pauseImg").attr("src", pauseImgSrc);
    videoPaused = false;
  }
}

function backVideo() {
  if (videoIteration - 2 > -1) {
    videoIteration = videoIteration - 2;
    if (timer != 0) {
      timer.pause();
    }
    timer = 0;
    loopVideo();
  }
}

function forwardVideo() {
  if (videoIteration + 1 <= videoCounter) {
    if (timer != 0) {
      timer.pause();
    }
    timer = 0;
    loopVideo();
  }
}

function Base64EncodeUrl(str){
  str = window.btoa(str);
  return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
}

function Base64DecodeUrl(str){
  str = (str + '===').slice(0, str.length + (str.length % 4));
  str = str.replace(/-/g, '+').replace(/_/g, '/');
  return window.atob(str);
}

function setPlaylist() {
  var playlist = JSON.stringify(videos);
  playlist = Base64EncodeUrl(playlist);
  window.location.hash = playlist;
}

function getPlaylist() {
  if (window.location.hash.substr(1) !== "") {
    var playlist = window.location.hash.substr(1);
    playlist = Base64DecodeUrl(playlist);
    playlist = JSON.parse(playlist);
    videos = playlist;

    for (var key in videos) {
      videoCounter++;
      var printTime = msConversion(videos[videoCounter]["time"]);
      $("#videosTable").append("
" + videos[videoCounter]["name"] + "	" + printTime + "
");
    }
    loopVideo();
  }
}

function getVideoData() {
  $.ajax({
    url: videoUrl,
    type: 'GET',
    success: function(res) {
      var data = $(res.responseText);
      videoName = data.find("span#eow-title");
      videoName = videoName[0].textContent;
      videoTime = null;
      for (iteration in data) {
        var str = data[iteration].innerHTML;
        if (videoTime == null && typeof str != "undefined") {
          videoTime = str.match(/,"length_seconds":"\d+",/g);
        }
      }
      videoTime = videoTime[0];
      videoTime = videoTime.replace(/,"length_seconds":"/g, "").replace(/",/g, "");
      videoTime = +videoTime * 1000;
    }
  });
}

function addVideo() {
  videoCounter++;
  videos[videoCounter] = {};
  videos[videoCounter]["name"] = videoName;
  videos[videoCounter]["time"] = videoTime;
  videos[videoCounter]["url"] = videoUrl;

  var printTime = msConversion(videos[videoCounter]["time"]);

  $("#videosTable").append("
" + videoName + "	" + printTime + "
");

  setPlaylist();

  if (videoCounter == 1 || timer == 0) {
    loopVideo();
  }
}

function input() {
  switch ($("#inputBox").attr("placeholder")) {
    case enterUrlMsg:
      videoUrl = $("#inputBox").val();
      $("#inputBox").val("");
      getVideoData();
      break;
  }
}

$(document).ajaxStop(function() {
  addVideo();
});
<|endoftext|>


<commit_before>/**
 * Created by tusharmathur on 5/15/15.
 */
"use strict";
var _ = require('lodash');
var request = require('request');
var fs = require('fs');
var event = require('common-js-pub-sub');
var MAX_BUFFER = 1000;
var utils = function (params, ev) {
    var u = {
        //DIRTY METHODS
        onError: function (err) {
            if (err instanceof Error) {
                throw err;
            } else {
                throw Error(err);
            }
        },

        setProperty: function (obj, key, val) {
            obj[key] = val;
        },

        extractAndSetProperty: function (obj, key, func, val) {
            u.setProperty(obj, key, func(val));
        },

        getContentLength: function (res) {
            return parseInt(res.headers['content-length'], 10);
        },

        addParamPosition: function (val) {
            return params.position + val;
        },

        stripFirstParamAsError: function (func) {
            return function (err, res) {
                if (err instanceof Error) {
                    u.onError(err);
                }
                func(res);
            };
        },

        getItemLength: function (item) {
            return item.length;
        },

        writeBufferAtPosition: function (buffer) {
            fs.write(params.fd, buffer, 0, u.getItemLength(buffer), params.position, u.DATA_SAVE);
        },

        makeRequest: function () {
            request(params.uri, params.headers)
                .on('data', u.DATA_RECEIVE)
                .on('response', u.DATA_START)
                .on('error', u.ERROR)
        },
        updateAndSetPositionOnParams: function (buffer) {
            params.position += buffer.length;
        },
        createTriggerFor: function (eventName) {
            return _.partial(ev.publish, eventName);
        },
        saveDownloadedBytes: function () {
            var buf = new Buffer(MAX_BUFFER);
            buf.write(JSON.stringify(params));
            fs.write(params.fd, buf, params.totalFileSize, 'utf8', u.METADATA_SAVE)
        },
        truncate: function () {
            fs.truncate(params.fd, params.totalFileSize, u.FILE_TRUNCATE);
        },
        rename: function () {
            fs.rename(params.path, params.path.replace('.mtd', ''), u.FILE_RENAME);
        },
        checkForCompletion: function () {
            if (params.position === params.totalFileSize) {
                u.FILE_COMPLETE();
            }
        }

    };
    //THUNKS (PRETTY METHODS!)
    u.stripErrorParamAndCreateTrigger = _.flow(u.createTriggerFor, u.stripFirstParamAsError);

    //EVENTS
    u.FILE_OPEN = u.stripErrorParamAndCreateTrigger('FILE_OPEN');
    u.METADATA_SAVE = u.stripErrorParamAndCreateTrigger('METADATA_SAVE');
    u.FILE_TRUNCATE = u.stripErrorParamAndCreateTrigger('FILE_TRUNCATE');
    u.DATA_SAVE = u.stripErrorParamAndCreateTrigger('DATA_SAVE');
    u.FILE_RENAME = u.stripErrorParamAndCreateTrigger('FILE_RENAME');

    u.DATA_RECEIVE = u.createTriggerFor('DATA_RECEIVE');
    u.DATA_START = u.createTriggerFor('DATA_START');
    u.FILE_COMPLETE = u.createTriggerFor('FILE_COMPLETE');
    u.ERROR = _.partial(ev.publish, 'ERROR');

    u.createFileDescriptor = _.partial(_.ary(fs.open, 3), params.path, 'w+', u.FILE_OPEN);
    u.setFileDescriptorOnParams = _.partial(u.setProperty, params, 'fd');
    u.extractAndSetContentLengthOnParams = _.partial(u.extractAndSetProperty, params, 'totalFileSize', u.getContentLength);
    u.setDownloadedBytesOnParams = _.partial(u.setProperty, params, 'downloadedBytes');

    return u;
};

var defaultOptions = {
    headers: {}
};
function download(options) {
    options = _.assign(options, defaultOptions);
    return {
        start: function (cb) {
            var params = {
                position: 0, path: options.path + '.mtd', uri: options.uri
            }, ev = event();
            var u = utils(params, ev);

            //Thunks
            //TODO: Public to test
            ev.subscribe('FILE_RENAME', cb);
            ev.subscribe('FILE_TRUNCATE', u.rename);
            ev.subscribe('FILE_COMPLETE', u.truncate);
            ev.subscribe('ERROR', u.onError);

            ev.subscribe('INIT', u.createFileDescriptor);

            ev.subscribe('FILE_OPEN', u.makeRequest);
            ev.subscribe('FILE_OPEN', u.setFileDescriptorOnParams);

            ev.subscribe('DATA_START', u.extractAndSetContentLengthOnParams);

            ev.subscribe('DATA_RECEIVE', u.writeBufferAtPosition);
            ev.subscribe('DATA_RECEIVE', u.updateAndSetPositionOnParams);

            ev.subscribe('DATA_SAVE', u.setDownloadedBytesOnParams);
            ev.subscribe('DATA_SAVE', u.saveDownloadedBytes);

            ev.subscribe('METADATA_SAVE', u.checkForCompletion);

            //START
            ev.publish('INIT', null);

        }
    }
}

exports.utils = utils;
exports.download = download;<commit_msg>trigger data complete<commit_after>/**
 * Created by tusharmathur on 5/15/15.
 */
"use strict";
var _ = require('lodash');
var request = require('request');
var fs = require('fs');
var event = require('common-js-pub-sub');
var MAX_BUFFER = 1000;
var utils = function (params, ev) {
    var u = {
        //DIRTY METHODS
        onError: function (err) {
            if (err instanceof Error) {
                throw err;
            } else {
                throw Error(err);
            }
        },

        setProperty: function (obj, key, val) {
            obj[key] = val;
        },

        extractAndSetProperty: function (obj, key, func, val) {
            u.setProperty(obj, key, func(val));
        },

        getContentLength: function (res) {
            return parseInt(res.headers['content-length'], 10);
        },

        addParamPosition: function (val) {
            return params.position + val;
        },

        stripFirstParamAsError: function (func) {
            return function (err, res) {
                if (err instanceof Error) {
                    u.onError(err);
                }
                func(res);
            };
        },

        getItemLength: function (item) {
            return item.length;
        },

        writeBufferAtPosition: function (buffer) {
            fs.write(params.fd, buffer, 0, u.getItemLength(buffer), params.position, u.DATA_SAVE);
        },

        makeRequest: function () {
            request(params.uri, params.headers)
                .on('data', u.DATA_RECEIVE)
                .on('response', u.DATA_START)
                .on('error', u.ERROR)
                .on('complete', u.DATA_COMPLETE)
        },
        updateAndSetPositionOnParams: function (buffer) {
            params.position += buffer.length;
        },
        createTriggerFor: function (eventName) {
            return _.partial(ev.publish, eventName);
        },
        saveDownloadedBytes: function () {
            var buf = new Buffer(MAX_BUFFER);
            buf.write(JSON.stringify(params));
            fs.write(params.fd, buf, params.totalFileSize, 'utf8', u.METADATA_SAVE)
        },
        truncate: function () {
            fs.truncate(params.fd, params.totalFileSize, u.FILE_TRUNCATE);
        },
        rename: function () {
            fs.rename(params.path, params.path.replace('.mtd', ''), u.FILE_RENAME);
        },
        checkForCompletion: function () {
            if (params.position === params.totalFileSize) {
                u.FILE_COMPLETE();
            }
        }

    };
    //THUNKS (PRETTY METHODS!)
    u.stripErrorParamAndCreateTrigger = _.flow(u.createTriggerFor, u.stripFirstParamAsError);

    //EVENTS
    u.FILE_OPEN = u.stripErrorParamAndCreateTrigger('FILE_OPEN');
    u.METADATA_SAVE = u.stripErrorParamAndCreateTrigger('METADATA_SAVE');
    u.FILE_TRUNCATE = u.stripErrorParamAndCreateTrigger('FILE_TRUNCATE');
    u.DATA_SAVE = u.stripErrorParamAndCreateTrigger('DATA_SAVE');
    u.FILE_RENAME = u.stripErrorParamAndCreateTrigger('FILE_RENAME');

    u.DATA_RECEIVE = u.createTriggerFor('DATA_RECEIVE');
    u.DATA_COMPLETE = u.createTriggerFor('DATA_COMPLETE');
    u.DATA_START = u.createTriggerFor('DATA_START');
    u.FILE_COMPLETE = u.createTriggerFor('FILE_COMPLETE');
    u.ERROR = _.partial(ev.publish, 'ERROR');

    u.createFileDescriptor = _.partial(_.ary(fs.open, 3), params.path, 'w+', u.FILE_OPEN);
    u.setFileDescriptorOnParams = _.partial(u.setProperty, params, 'fd');
    u.extractAndSetContentLengthOnParams = _.partial(u.extractAndSetProperty, params, 'totalFileSize', u.getContentLength);
    u.setDownloadedBytesOnParams = _.partial(u.setProperty, params, 'downloadedBytes');

    return u;
};

var defaultOptions = {
    headers: {}
};
function download(options) {
    options = _.assign(options, defaultOptions);
    return {
        start: function (cb) {
            var params = {
                position: 0, path: options.path + '.mtd', uri: options.uri
            }, ev = event();
            var u = utils(params, ev);

            //Thunks
            //TODO: Public to test
            ev.subscribe('FILE_RENAME', cb);
            ev.subscribe('FILE_TRUNCATE', u.rename);
            ev.subscribe('FILE_COMPLETE', u.truncate);
            ev.subscribe('ERROR', u.onError);

            ev.subscribe('INIT', u.createFileDescriptor);

            ev.subscribe('FILE_OPEN', u.makeRequest);
            ev.subscribe('FILE_OPEN', u.setFileDescriptorOnParams);

            ev.subscribe('DATA_START', u.extractAndSetContentLengthOnParams);

            ev.subscribe('DATA_RECEIVE', u.writeBufferAtPosition);
            ev.subscribe('DATA_RECEIVE', u.updateAndSetPositionOnParams);

            ev.subscribe('DATA_SAVE', u.setDownloadedBytesOnParams);
            ev.subscribe('DATA_SAVE', u.saveDownloadedBytes);

            ev.subscribe('METADATA_SAVE', u.checkForCompletion);

            //START
            ev.publish('INIT', null);

        }
    }
}

exports.utils = utils;
exports.download = download;<|endoftext|>


<commit_before>;(function() {

	var
		hid = require('node-hid')
		, stream = require('stream')
		, util = require('util')
		, hidParsers = require('./lib/parser')
	;

	function hidDevice(path, opts) {

		if(!path) { return new Error("no HID path specified"); }
		if(!opts) { opts = {}; }
		if(!opts.parser) { opts.parser = hidParsers.raw; }
		else if(typeof opts.parser != "function") {

			return new Error("Invalid parser function specified");
		}

		this.opts = opts;
		this.path = path;
		this.device = new hid.HID(path);

		stream.Stream.call(this);

		this.parser = opts.parser.bind(this);
		this.device.read(this.parser);
		return this;
	};

	util.inherits(hidDevice, stream.Stream);

	exports.device = hidDevice;
	exports.parser = hidParsers;

})();
<commit_msg>Added getDevices function. Returns the node-hid list of devices.<commit_after>;(function() {

	var
		hid = require('node-hid')
		, stream = require('stream')
		, util = require('util')
		, hidParsers = require('./lib/parser')
	;

	function hidDevice(path, opts) {

		if(!path) { return new Error("no HID path specified"); }
		if(!opts) { opts = {}; }
		if(!opts.parser) { opts.parser = hidParsers.raw; }
		else if(typeof opts.parser != "function") {

			return new Error("Invalid parser function specified");
		}

		this.opts = opts;
		this.path = path;
		this.device = new hid.HID(path);

		stream.Stream.call(this);

		this.parser = opts.parser.bind(this);
		this.device.read(this.parser);
		return this;
	};

	function getDevices() {

		return hid.devices();
	};

	util.inherits(hidDevice, stream.Stream);

	exports.device = hidDevice;
	exports.parser = hidParsers;
	exports.getDevices = getDevices;

})();
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var revHash = require('rev-hash');
var through = require('through2');
var gutil = require('gulp-util');
var del = require('del');

function rename (name, suffix) {
  var dir = path.dirname(name);
  var ext = path.extname(name);
  var fileName = path.basename(name, ext);

  return path.join(dir, fileName + '_' + suffix + ext);
}

function revise () {
  return through.obj(
    function transform (file, encoding, callback) {
      if (path.extname(file.path) == '.map') {
        callback(new gutil.PluginError('gulp-revise', 'sourcemaps must be created after the revision'));
        return;
      }

      var hash = revHash(file.contents);

      file.beforeRev = file.path;
      file.path = rename(file.path, hash);
      this.push(file);
      callback();
    }
  );
}

function write (pth) {
  return through.obj(
    function transform (file, encoding, callback) {
      this.push(file);

      if (!file.beforeRev) {
        callback();
        return;
      }

      var revisionFileName = path.basename(file.beforeRev) + '.rev';
      var newRevisionName = path.basename(file.path);

      this.push(new gutil.File({
        cwd: file.cwd,
        base: file.base,
        path: path.join(file.base, path.dirname(file.relative), revisionFileName),
        contents: new Buffer(newRevisionName)
      }));

      if (pth == null) {
        callback(new gutil.PluginError('gulp-revise', 'required argument "dest path" for revise.write() is missing'));
        return;
      }

      var outputDir = path.join(process.cwd(), pth);
      var pathToRevision = path.join(outputDir, revisionFileName);
      fs.readFile(pathToRevision, 'utf8', function (err, currentVersion) {
        if (err) {
          callback();
          return;
        }

        if (newRevisionName == currentVersion) {
          callback();
          return;
        }

        del([
          path.join(outputDir, currentVersion),
          path.join(outputDir, currentVersion + '.map')
        ]).then(function () {
          callback();
        });
      });
    }
  );
}

function merge () {
  var manifest = {};

  return through.obj(
    function transform (file, encoding, callback) {
      var fileName = path.basename(file.path, '.rev');
      manifest[fileName] = file.contents.toString();
      callback();
    },
    function flush (callback) {
      var file = new gutil.File();
      file.contents = new Buffer(JSON.stringify(manifest, null, 2));
      file.path = 'rev-manifest.json';
      this.push(file);
      callback();
    }
  );
}

module.exports = revise;
module.exports.write = write;
module.exports.merge = merge;
<commit_msg>fix issue regarding source maps generation<commit_after>var fs = require('fs');
var path = require('path');
var revHash = require('rev-hash');
var through = require('through2');
var gutil = require('gulp-util');
var del = require('del');

function rename (name, suffix) {
  var dir = path.dirname(name);
  var ext = path.extname(name);
  var fileName = path.basename(name, ext);

  return path.join(dir, fileName + '_' + suffix + ext);
}

function revise () {
  return through.obj(
    function transform (file, encoding, callback) {
      if (path.extname(file.path) == '.map') {
        callback(new gutil.PluginError('gulp-revise', 'sourcemaps must be created after the revision'));
        return;
      }

      var hash = revHash(file.contents);

      file.beforeRev = file.path;
      file.path = rename(file.path, hash);

      if (file.sourceMap) {
        file.sourceMap.file = file.relative;
      }

      this.push(file);
      callback();
    }
  );
}

function write (pth) {
  return through.obj(
    function transform (file, encoding, callback) {
      this.push(file);

      if (!file.beforeRev) {
        callback();
        return;
      }

      var revisionFileName = path.basename(file.beforeRev) + '.rev';
      var newRevisionName = path.basename(file.path);

      this.push(new gutil.File({
        cwd: file.cwd,
        base: file.base,
        path: path.join(file.base, path.dirname(file.relative), revisionFileName),
        contents: new Buffer(newRevisionName)
      }));

      if (pth == null) {
        callback(new gutil.PluginError('gulp-revise', 'required argument "dest path" for revise.write() is missing'));
        return;
      }

      var outputDir = path.join(process.cwd(), pth);
      var pathToRevision = path.join(outputDir, revisionFileName);
      fs.readFile(pathToRevision, 'utf8', function (err, currentVersion) {
        if (err) {
          callback();
          return;
        }

        if (newRevisionName == currentVersion) {
          callback();
          return;
        }

        del([
          path.join(outputDir, currentVersion),
          path.join(outputDir, currentVersion + '.map')
        ]).then(function () {
          callback();
        });
      });
    }
  );
}

function merge () {
  var manifest = {};

  return through.obj(
    function transform (file, encoding, callback) {
      var fileName = path.basename(file.path, '.rev');
      manifest[fileName] = file.contents.toString();
      callback();
    },
    function flush (callback) {
      var file = new gutil.File();
      file.contents = new Buffer(JSON.stringify(manifest, null, 2));
      file.path = 'rev-manifest.json';
      this.push(file);
      callback();
    }
  );
}

module.exports = revise;
module.exports.write = write;
module.exports.merge = merge;
<|endoftext|>