<commit_before>/*
 * grunticon
 * https://github.com/filamentgroup/grunticon
 *
 * Copyright (c) 2012 Scott Jehl, Filament Group, Inc
 * Licensed under the MIT license.
 */

/*global __dirname:true*/
/*global require:true*/

module.exports = function( grunt , undefined ) {

	"use strict";

	var path = require( 'path' );
	var os = require( 'os' );

	var fs = require( 'fs-extra' );
	var uglify = require( 'uglify-js' );

	var DirectoryColorfy = require( 'directory-colorfy' );
	var DirectoryEncoder = require( 'directory-encoder' );
	var svgToPng = require( 'svg-to-png' );

	var helper = require( path.join( '..', 'lib', 'grunticon-helper' ) );

	grunt.registerMultiTask( 'grunticon', 'A mystical CSS icon solution.', function() {
		var done = this.async();

		// get the config
		var config = this.options({
			datasvgcss: "icons.data.svg.css",
			datapngcss: "icons.data.png.css",
			urlpngcss: "icons.fallback.css",
			files: {
				loader: path.join( __dirname, 'grunticon', 'static', 'grunticon.loader.js'),
				embed: path.join( __dirname, 'grunticon', 'static', 'grunticon.embed.js'),
				banner: path.join( __dirname, 'grunticon', 'static', 'grunticon.loader.banner.js')
			},
			previewhtml: "preview.html",
			loadersnippet: "grunticon.loader.js",
			cssbasepath: path.sep,
			customselectors: {},
			cssprefix: ".icon-",
			defaultWidth: "400px",
			defaultHeight: "300px",
			colors: {},
			dynamicColorOnly: false,
			pngfolder: "png",
			pngpath: "",
			template: "",
			tmpPath: os.tmpDir(),
			tmpDir: "grunticon-tmp",
			previewTemplate: path.join( __dirname, "..", "example", "preview.hbs" ),
			compressPNG: false,
			optimizationLevel: 3,
			enhanceSVG: false
		});

		// just a quick starting message
		grunt.verbose.writeln( "Look, it's a grunticon!" );

		var files = this.files.filter( function( file ){
			return file.src[0].match( /png|svg/ );
		});
		if( files.length === 0 ){
			grunt.log.writeln( "Grunticon has no files to read!" );
			done();
			return;
		}

		files = files.map( function( file ){
			return file.src[0];
		});

		config.dest = this.files[0].orig.dest;

		if( !config.dest || config.dest && config.dest === "" ){
			grunt.fatal("The destination must be a directory");
			done( false );
		}

		// folder name (within the output folder) for generated png files
		var pngfolder = path.join.apply( null, config.pngfolder.split( path.sep ) );

		// create the output directory
		fs.mkdirpSync( config.dest );

		// minify the source of the grunticon loader and write that to the output
		grunt.verbose.writeln( "grunticon now minifying the stylesheet loader source." );
		var banner = fs.readFileSync( config.files.banner );
		config.min = banner + "\n" + uglify.minify( config.files.loader ).code;
		if( config.enhanceSVG ){ config.min += uglify.minify( config.files.embed ).code; }
		fs.writeFileSync( path.join( config.dest, config.loadersnippet ), config.min );
		grunt.verbose.writeln( "grunticon loader file created." );

		var svgToPngOpts = {
			defaultWidth: config.defaultWidth,
			defaultHeight: config.defaultHeight,
			compress: config.compressPNG,
			optimizationLevel: config.optimizationLevel
		};

		var o = {
			pngfolder: pngfolder,
			pngpath: config.pngpath,
			customselectors: config.customselectors,
			template: config.template ? path.resolve( config.template ) : "",
			previewTemplate: path.resolve( config.previewTemplate ),
			noencodepng: false,
			prefix: config.cssprefix
		};

		var o2 = JSON.parse(JSON.stringify(o)); /* clone object */
		o2.noencodepng = true;

		grunt.verbose.writeln("Coloring SVG files");
		// create the tmp directory
		var tmp = path.join( config.tmpPath, config.tmpDir );
		if( fs.existsSync( tmp ) ){
			fs.removeSync( tmp );
		}
		fs.mkdirpSync( tmp );
		var dc;
		try{
			dc = new DirectoryColorfy( files, tmp, {
				colors: config.colors,
				dynamicColorOnly: config.dynamicColorOnly
			});
		} catch( e ){
			grunt.fatal(e);
			done( false );
		}

		dc.convert()
		.then(function(){
			//copy non color config files into temp directory
			var transferFiles = files.filter( function( f ){
				return !f.match( /\.colors/ );
			});

			transferFiles.forEach( function( f ){
				var filename = path.basename(f);
				fs.copySync( f, path.join( tmp, filename ) );
			});

			grunt.verbose.writeln("Converting SVG to PNG");

			var tmpFiles = fs.readdirSync( tmp )
				.map( function( file ){
					return path.join( tmp, file );
				});

			var svgFiles = tmpFiles.filter( function( file ){
				return path.extname( file ) === ".svg";
			}),
			pngFiles = tmpFiles.filter( function( file ){
				return path.extname( file ) === ".png";
			});

			pngFiles.forEach(function( f ){
				var filename = path.basename(f);
				fs.copySync( f, path.join( config.dest, pngfolder, filename ) );
			});

			svgToPng.convert( svgFiles, path.join( config.dest, pngfolder ), svgToPngOpts )
			.then( function( result , err ){
				if( err ){
					grunt.fatal( err );
					done( false );
				}

				var pngs = fs.readdirSync( path.join( config.dest, pngfolder ) )
				.map(function( file ){
					return path.join( config.dest, pngfolder, file );
				});

				var svgde = new DirectoryEncoder( tmpFiles, path.join( config.dest, config.datasvgcss ), o ),
					pngde = new DirectoryEncoder( pngs, path.join( config.dest, config.datapngcss ), o ),
					pngdefall = new DirectoryEncoder( pngs, path.join( config.dest, config.urlpngcss ), o2 );

				grunt.verbose.writeln("Writing CSS");
				try {
					svgde.encode();
					pngde.encode();
					pngdefall.encode();
				} catch( e ){
					grunt.fatal( e );
					done( false );
				}

				grunt.verbose.writeln( "Grunticon now creating Preview File" );
				try {
					helper.createPreview( tmp, config );
				} catch(er) {
					grunt.fatal(er);
					done( false );
				}

				grunt.verbose.writeln( "Delete Temp Files" );
				fs.removeSync( tmp );

				var impressed = grunt.util.kindOf(result) === "array" ? result.length.toString() : "0";
				grunt.log.ok( "Grunticon processed " + impressed + " files." );

				done();
			});
		});

	});
};
<commit_msg>files is where the amount of processed files is stored<commit_after>/*
 * grunticon
 * https://github.com/filamentgroup/grunticon
 *
 * Copyright (c) 2012 Scott Jehl, Filament Group, Inc
 * Licensed under the MIT license.
 */

/*global __dirname:true*/
/*global require:true*/

module.exports = function( grunt , undefined ) {

	"use strict";

	var path = require( 'path' );
	var os = require( 'os' );

	var fs = require( 'fs-extra' );
	var uglify = require( 'uglify-js' );

	var DirectoryColorfy = require( 'directory-colorfy' );
	var DirectoryEncoder = require( 'directory-encoder' );
	var svgToPng = require( 'svg-to-png' );

	var helper = require( path.join( '..', 'lib', 'grunticon-helper' ) );

	grunt.registerMultiTask( 'grunticon', 'A mystical CSS icon solution.', function() {
		var done = this.async();

		// get the config
		var config = this.options({
			datasvgcss: "icons.data.svg.css",
			datapngcss: "icons.data.png.css",
			urlpngcss: "icons.fallback.css",
			files: {
				loader: path.join( __dirname, 'grunticon', 'static', 'grunticon.loader.js'),
				embed: path.join( __dirname, 'grunticon', 'static', 'grunticon.embed.js'),
				banner: path.join( __dirname, 'grunticon', 'static', 'grunticon.loader.banner.js')
			},
			previewhtml: "preview.html",
			loadersnippet: "grunticon.loader.js",
			cssbasepath: path.sep,
			customselectors: {},
			cssprefix: ".icon-",
			defaultWidth: "400px",
			defaultHeight: "300px",
			colors: {},
			dynamicColorOnly: false,
			pngfolder: "png",
			pngpath: "",
			template: "",
			tmpPath: os.tmpDir(),
			tmpDir: "grunticon-tmp",
			previewTemplate: path.join( __dirname, "..", "example", "preview.hbs" ),
			compressPNG: false,
			optimizationLevel: 3,
			enhanceSVG: false
		});

		// just a quick starting message
		grunt.verbose.writeln( "Look, it's a grunticon!" );

		var files = this.files.filter( function( file ){
			return file.src[0].match( /png|svg/ );
		});
		if( files.length === 0 ){
			grunt.log.writeln( "Grunticon has no files to read!" );
			done();
			return;
		}

		files = files.map( function( file ){
			return file.src[0];
		});

		config.dest = this.files[0].orig.dest;

		if( !config.dest || config.dest && config.dest === "" ){
			grunt.fatal("The destination must be a directory");
			done( false );
		}

		// folder name (within the output folder) for generated png files
		var pngfolder = path.join.apply( null, config.pngfolder.split( path.sep ) );

		// create the output directory
		fs.mkdirpSync( config.dest );

		// minify the source of the grunticon loader and write that to the output
		grunt.verbose.writeln( "grunticon now minifying the stylesheet loader source." );
		var banner = fs.readFileSync( config.files.banner );
		config.min = banner + "\n" + uglify.minify( config.files.loader ).code;
		if( config.enhanceSVG ){ config.min += uglify.minify( config.files.embed ).code; }
		fs.writeFileSync( path.join( config.dest, config.loadersnippet ), config.min );
		grunt.verbose.writeln( "grunticon loader file created." );

		var svgToPngOpts = {
			defaultWidth: config.defaultWidth,
			defaultHeight: config.defaultHeight,
			compress: config.compressPNG,
			optimizationLevel: config.optimizationLevel
		};

		var o = {
			pngfolder: pngfolder,
			pngpath: config.pngpath,
			customselectors: config.customselectors,
			template: config.template ? path.resolve( config.template ) : "",
			previewTemplate: path.resolve( config.previewTemplate ),
			noencodepng: false,
			prefix: config.cssprefix
		};

		var o2 = JSON.parse(JSON.stringify(o)); /* clone object */
		o2.noencodepng = true;

		grunt.verbose.writeln("Coloring SVG files");
		// create the tmp directory
		var tmp = path.join( config.tmpPath, config.tmpDir );
		if( fs.existsSync( tmp ) ){
			fs.removeSync( tmp );
		}
		fs.mkdirpSync( tmp );
		var dc;
		try{
			dc = new DirectoryColorfy( files, tmp, {
				colors: config.colors,
				dynamicColorOnly: config.dynamicColorOnly
			});
		} catch( e ){
			grunt.fatal(e);
			done( false );
		}

		dc.convert()
		.then(function(){
			//copy non color config files into temp directory
			var transferFiles = files.filter( function( f ){
				return !f.match( /\.colors/ );
			});

			transferFiles.forEach( function( f ){
				var filename = path.basename(f);
				fs.copySync( f, path.join( tmp, filename ) );
			});

			grunt.verbose.writeln("Converting SVG to PNG");

			var tmpFiles = fs.readdirSync( tmp )
				.map( function( file ){
					return path.join( tmp, file );
				});

			var svgFiles = tmpFiles.filter( function( file ){
				return path.extname( file ) === ".svg";
			}),
			pngFiles = tmpFiles.filter( function( file ){
				return path.extname( file ) === ".png";
			});

			pngFiles.forEach(function( f ){
				var filename = path.basename(f);
				fs.copySync( f, path.join( config.dest, pngfolder, filename ) );
			});

			svgToPng.convert( svgFiles, path.join( config.dest, pngfolder ), svgToPngOpts )
			.then( function( result , err ){
				if( err ){
					grunt.fatal( err );
					done( false );
				}

				var pngs = fs.readdirSync( path.join( config.dest, pngfolder ) )
				.map(function( file ){
					return path.join( config.dest, pngfolder, file );
				});

				var svgde = new DirectoryEncoder( tmpFiles, path.join( config.dest, config.datasvgcss ), o ),
					pngde = new DirectoryEncoder( pngs, path.join( config.dest, config.datapngcss ), o ),
					pngdefall = new DirectoryEncoder( pngs, path.join( config.dest, config.urlpngcss ), o2 );

				grunt.verbose.writeln("Writing CSS");
				try {
					svgde.encode();
					pngde.encode();
					pngdefall.encode();
				} catch( e ){
					grunt.fatal( e );
					done( false );
				}

				grunt.verbose.writeln( "Grunticon now creating Preview File" );
				try {
					helper.createPreview( tmp, config );
				} catch(er) {
					grunt.fatal(er);
					done( false );
				}

				grunt.verbose.writeln( "Delete Temp Files" );
				fs.removeSync( tmp );

				grunt.log.ok( "Grunticon processed " + files.length + " files." );

				done();
			});
		});

	});
};
<|endoftext|>


<commit_before>'use strict';

module.exports = function(grunt) {

	var wd = require('wd');
	var Batch = require('batch');
	var request = require('request');
	var SauceTunnel = require('sauce-tunnel');
	var mkdirp = require('mkdirp');
	var path = require('path');
	var fs = require('fs');
	var testResultsPath = path.join(__dirname, '../test/results');
	var UA = require('../lib/UA');

	var pollTick = 1000;
	var retryLimit = 10;
	var sauceConnectPort = 4445;

	grunt.registerMultiTask('saucelabs', 'Perform tests on Sauce', function() {
		var gruntDone = this.async();
		var nameSuffix = process.env.TRAVIS_BUILD_NUMBER ? "-travis-build:" + process.env.TRAVIS_BUILD_NUMBER : ':' + (new Date()).toUTCString();
		var tunnelId = 'SauceTunnel-' + nameSuffix;

		if (!process.env.SAUCE_USER_NAME || !process.env.SAUCE_API_KEY) {
			gruntDone(new Error('SAUCE_USER_NAME and SAUCE_API_KEY must be set in the environment'));
		}

		var options = this.options({
			name: 'grunt-test' + nameSuffix,
			build: null,
			username: process.env.SAUCE_USER_NAME,
			key: process.env.SAUCE_API_KEY,
			browsers: [],
			url: {},
			concurrency: 3,
			tags: [],
			video: true,
			screenshots: true,
			cibuild: false
		});

		var batch = new Batch();
		batch.concurrency(options.concurrency);

		var tunnel = new SauceTunnel(options.username, options.key, tunnelId, true);

		// Create a new job to pass into the Batch runner, returns a function
		function newTestJob(url, urlName, conf) {
			return function testJob(done) {

				// Variadic like console.log(...);
				function log() {
					var leader = (new Date()).toString() + ' ' + conf.browserName + "/" + conf.version + ": ";
					var args = Array.prototype.slice.call(arguments);
					args.unshift(leader);
					console.log.apply(null, args);
				}

				log("started job");

				var browser = wd.remote("127.0.0.1", sauceConnectPort, options.username, options.key);

				log("requested browser");

				browser.init(conf, function() {

					log("browser launched");

					browser.get(url, function(err) {
						var refreshed = false;
						var remainingCount = null;
						var retryCount = 0;

						if (err) {
							log(err);
							return done(err);
						}

						browser.refresh(function() {
							refreshed = true;
							log("kicked");
							setTimeout(waitOnResults, pollTick);
						});

						// Wait until results are available
						function waitOnResults() {
							browser.eval('window.global_test_results ||  window.remainingCount', function(err, data) {

								if (typeof data !== 'object') {
									if (retryCount > retryLimit) {
										log('Timed out');
										browser.quit();
										done(null, { status: 'failed', uaString: (typeof data == 'object') ? (data.uaString || '??') : '??', id: browser.sessionID, name: conf.browserName, version: conf.version, failed: '??', total: '??'});
									} else {
										if (typeof data === 'number' && data !== remainingCount) {
											remainingCount = data;
											retryCount = 0;
											log(remainingCount + ' test pages remaining');
										} else {
											retryCount++;
											log('no changes, waiting');
										}
										setTimeout(waitOnResults, pollTick);
									}
									return;
								}

								log("results received, closing remote browser");

								// Close the browser as soon as we have the
								// results.  No need to keep it hanging around
								// until the rest of the job is complete.
								browser.quit();

								process.nextTick(function() {
									done(null, {
										id: browser.sessionID,
										browserName: conf.browserName,
										version: conf.version,
										urlName: urlName,
										results: data
									});
								});

								request({
									method: "PUT",
									uri: ["https://", options.username, ":", options.key, "@saucelabs.com/rest", "/v1/", options.username, "/jobs/", browser.sessionID].join(''),
									headers: {'Content-Type': 'application/json'},
									body: JSON.stringify({
										'custom-data': { custom: data },
										'passed': !data.failed
									})
								});
							});
						};
					});
				});
			};
		}

		function readResultsFile(cb) {
			var file = path.join(testResultsPath, 'results.json');
			mkdirp(testResultsPath, function(err) {
				if (err) {
					grunt.warn('Cannot write test results - '+testResultsPath+' cannot be written');
					cb({});
				} else {
					fs.readFile(file, function(err, filedata) {
						if (err) {
							// If ENOENT, continue as writeFile will create it later
							if (err.code !== 'ENOENT') {
								throw err;
							}
						}
						cb(filedata ? JSON.parse(filedata) : {});
					});
				}
			});
		}

		function writeResultsFile(data) {
			var file = path.join(testResultsPath, 'results.json');
			fs.writeFile(file, JSON.stringify(data, null, 2), function(err) {
				if (err) {
					console.log(err);
					throw err;
				}
			});
		}

		options.browsers.forEach(function(conf) {
			conf['name'] = options.name;
			conf['record-video'] = options.video;
			conf['record-screenshots'] = options.screenshots;
			conf['tunnel-identifier'] = tunnelId;
			if (options.tags) conf['tags'] = options.tags;
			if (options.build) conf['build'] = options.build;

			Object.keys(options.urls).forEach(function(urlName) {
				var url = options.urls[urlName];
				batch.push(newTestJob(url, urlName, conf));
				console.log('Adding batch job for '+options.name+', '+urlName+' on '+conf.browserName+' '+conf.version);
			});
		});

		readResultsFile(function(testResults) {

			grunt.log.writeln('Opening tunnel to Sauce Labs');
			tunnel.start(function(status) {

				grunt.log.writeln("Tunnel Started");
				if (status !== true)  {
					gruntDone(status);
				}

				grunt.log.writeln("Starting test jobs");
				batch.end(function(err, jobresults) {
					tunnel.stop(function() {
						var passingUAs = [];
						var failed = false;
						grunt.log.writeln('Sauce tunnel stopped');
						if (err) {
							gruntDone(err);
							return;
						}
						grunt.log.writeln("Failed tests:")
						jobresults.forEach(function(job) {
							if (job.results.failed && job.results.failingSuites) {
								grunt.log.writeln(' - '+job.browserName+' '+job.version+' (Sauce results: https://saucelabs.com/tests/' + job.id+')')
								Object.keys(job.results.failingSuites).forEach(function(feature) {
									var url = options.urls[job.urlName].replace(/test\/director/, 'test/runner')+'&feature='+feature;
									grunt.log.writeln('    -> '+feature);
									grunt.log.writeln('       '+url);
								});
								failed = true;
							} else {
								passingUAs.push(job.browserName+'/'+job.version);
							}

							var browserName = UA.normalizeName(job.browserName) || job.browserName;

							if (!testResults[browserName]) {
								testResults[browserName] = {};
							}

							if (!testResults[browserName][job.version]) {
								testResults[browserName][job.version]	= {};
							}

							testResults[browserName][job.version][job.urlName] = {
								passed: job.results.passed,
								failed: job.results.failed,
								failingSuites: job.results.failingSuites ? Object.keys(job.results.failingSuites) : [],
								testedSuites: job.results.testedSuites
							};
						});
						if (passingUAs.length) {
							console.log('No failures in: '+passingUAs.join(', '));
						}

						writeResultsFile(testResults);

						process.nextTick(function() {

							// Always report the grunt task as successful if not running as CI
							if (!options.cibuild) {
								gruntDone(null, true);
							} else {
								gruntDone(!failed, failed);
							}
						});
					});
				});
			});
		});
	});
};
<commit_msg>Fix link<commit_after>'use strict';

module.exports = function(grunt) {

	var wd = require('wd');
	var Batch = require('batch');
	var request = require('request');
	var SauceTunnel = require('sauce-tunnel');
	var mkdirp = require('mkdirp');
	var path = require('path');
	var fs = require('fs');
	var testResultsPath = path.join(__dirname, '../test/results');
	var UA = require('../lib/UA');

	var pollTick = 1000;
	var retryLimit = 10;
	var sauceConnectPort = 4445;

	grunt.registerMultiTask('saucelabs', 'Perform tests on Sauce', function() {
		var gruntDone = this.async();
		var nameSuffix = process.env.TRAVIS_BUILD_NUMBER ? "-travis-build:" + process.env.TRAVIS_BUILD_NUMBER : ':' + (new Date()).toUTCString();
		var tunnelId = 'SauceTunnel-' + nameSuffix;

		if (!process.env.SAUCE_USER_NAME || !process.env.SAUCE_API_KEY) {
			gruntDone(new Error('SAUCE_USER_NAME and SAUCE_API_KEY must be set in the environment'));
		}

		var options = this.options({
			name: 'grunt-test' + nameSuffix,
			build: null,
			username: process.env.SAUCE_USER_NAME,
			key: process.env.SAUCE_API_KEY,
			browsers: [],
			url: {},
			concurrency: 3,
			tags: [],
			video: true,
			screenshots: true,
			cibuild: false
		});

		var batch = new Batch();
		batch.concurrency(options.concurrency);

		var tunnel = new SauceTunnel(options.username, options.key, tunnelId, true);

		// Create a new job to pass into the Batch runner, returns a function
		function newTestJob(url, urlName, conf) {
			return function testJob(done) {

				// Variadic like console.log(...);
				function log() {
					var leader = (new Date()).toString() + ' ' + conf.browserName + "/" + conf.version + ": ";
					var args = Array.prototype.slice.call(arguments);
					args.unshift(leader);
					console.log.apply(null, args);
				}

				log("started job");

				var browser = wd.remote("127.0.0.1", sauceConnectPort, options.username, options.key);

				log("requested browser");

				browser.init(conf, function() {

					log("browser launched");

					browser.get(url, function(err) {
						var refreshed = false;
						var remainingCount = null;
						var retryCount = 0;

						if (err) {
							log(err);
							return done(err);
						}

						browser.refresh(function() {
							refreshed = true;
							log("kicked");
							setTimeout(waitOnResults, pollTick);
						});

						// Wait until results are available
						function waitOnResults() {
							browser.eval('window.global_test_results ||  window.remainingCount', function(err, data) {

								if (typeof data !== 'object') {
									if (retryCount > retryLimit) {
										log('Timed out');
										browser.quit();
										done(null, { status: 'failed', uaString: (typeof data == 'object') ? (data.uaString || '??') : '??', id: browser.sessionID, name: conf.browserName, version: conf.version, failed: '??', total: '??'});
									} else {
										if (typeof data === 'number' && data !== remainingCount) {
											remainingCount = data;
											retryCount = 0;
											log(remainingCount + ' test pages remaining');
										} else {
											retryCount++;
											log('no changes, waiting');
										}
										setTimeout(waitOnResults, pollTick);
									}
									return;
								}

								log("results received, closing remote browser");

								// Close the browser as soon as we have the
								// results.  No need to keep it hanging around
								// until the rest of the job is complete.
								browser.quit();

								process.nextTick(function() {
									done(null, {
										id: browser.sessionID,
										browserName: conf.browserName,
										version: conf.version,
										urlName: urlName,
										results: data
									});
								});

								request({
									method: "PUT",
									uri: ["https://", options.username, ":", options.key, "@saucelabs.com/rest", "/v1/", options.username, "/jobs/", browser.sessionID].join(''),
									headers: {'Content-Type': 'application/json'},
									body: JSON.stringify({
										'custom-data': { custom: data },
										'passed': !data.failed
									})
								});
							});
						};
					});
				});
			};
		}

		function readResultsFile(cb) {
			var file = path.join(testResultsPath, 'results.json');
			mkdirp(testResultsPath, function(err) {
				if (err) {
					grunt.warn('Cannot write test results - '+testResultsPath+' cannot be written');
					cb({});
				} else {
					fs.readFile(file, function(err, filedata) {
						if (err) {
							// If ENOENT, continue as writeFile will create it later
							if (err.code !== 'ENOENT') {
								throw err;
							}
						}
						cb(filedata ? JSON.parse(filedata) : {});
					});
				}
			});
		}

		function writeResultsFile(data) {
			var file = path.join(testResultsPath, 'results.json');
			fs.writeFile(file, JSON.stringify(data, null, 2), function(err) {
				if (err) {
					console.log(err);
					throw err;
				}
			});
		}

		options.browsers.forEach(function(conf) {
			conf['name'] = options.name;
			conf['record-video'] = options.video;
			conf['record-screenshots'] = options.screenshots;
			conf['tunnel-identifier'] = tunnelId;
			if (options.tags) conf['tags'] = options.tags;
			if (options.build) conf['build'] = options.build;

			Object.keys(options.urls).forEach(function(urlName) {
				var url = options.urls[urlName];
				batch.push(newTestJob(url, urlName, conf));
				console.log('Adding batch job for '+options.name+', '+urlName+' on '+conf.browserName+' '+conf.version);
			});
		});

		readResultsFile(function(testResults) {

			grunt.log.writeln('Opening tunnel to Sauce Labs');
			tunnel.start(function(status) {

				grunt.log.writeln("Tunnel Started");
				if (status !== true)  {
					gruntDone(status);
				}

				grunt.log.writeln("Starting test jobs");
				batch.end(function(err, jobresults) {
					tunnel.stop(function() {
						var passingUAs = [];
						var failed = false;
						grunt.log.writeln('Sauce tunnel stopped');
						if (err) {
							gruntDone(err);
							return;
						}
						grunt.log.writeln("Failed tests:")
						jobresults.forEach(function(job) {
							if (job.results.failed && job.results.failingSuites) {
								grunt.log.writeln(' - '+job.browserName+' '+job.version+' (Sauce results: https://saucelabs.com/tests/' + job.id+')')
								Object.keys(job.results.failingSuites).forEach(function(feature) {
									var url = options.urls[job.urlName].replace(/test\/director/, 'test/tests')+'&feature='+feature;
									grunt.log.writeln('    -> '+feature);
									grunt.log.writeln('       '+url);
								});
								failed = true;
							} else {
								passingUAs.push(job.browserName+'/'+job.version);
							}

							var browserName = UA.normalizeName(job.browserName) || job.browserName;

							if (!testResults[browserName]) {
								testResults[browserName] = {};
							}

							if (!testResults[browserName][job.version]) {
								testResults[browserName][job.version]	= {};
							}

							testResults[browserName][job.version][job.urlName] = {
								passed: job.results.passed,
								failed: job.results.failed,
								failingSuites: job.results.failingSuites ? Object.keys(job.results.failingSuites) : [],
								testedSuites: job.results.testedSuites
							};
						});
						if (passingUAs.length) {
							console.log('No failures in: '+passingUAs.join(', '));
						}

						writeResultsFile(testResults);

						process.nextTick(function() {

							// Always report the grunt task as successful if not running as CI
							if (!options.cibuild) {
								gruntDone(null, true);
							} else {
								gruntDone(!failed, failed);
							}
						});
					});
				});
			});
		});
	});
};
<|endoftext|>


<commit_before>/**
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

import * as utils from './utils.js';
import {eventPropertyNamesForElement, eventPropertyNamesForHTMLElement} from './patch-events.js';

/** @implements {IWrapper} */
class Wrapper {

  /** @param {!Node} node */
  constructor(node) {
    this.node = node;
  }

  // node
  addEventListener(name, fn, options) {
    return this.node[utils.SHADY_PREFIX + 'addEventListener'](name, fn, options);
  }

  removeEventListener(name, fn, options) {
    return this.node[utils.SHADY_PREFIX + 'removeEventListener'](name, fn, options);
  }

  appendChild(node) {
    return this.node[utils.SHADY_PREFIX + 'appendChild'](node);
  }

  insertBefore(node, ref_node) {
    return this.node[utils.SHADY_PREFIX + 'insertBefore'](node, ref_node);
  }

  removeChild(node) {
    return this.node[utils.SHADY_PREFIX + 'removeChild'](node);
  }

  replaceChild(node, ref_node) {
    return this.node[utils.SHADY_PREFIX + 'replaceChild'](node, ref_node);
  }

  cloneNode(deep) {
    return this.node[utils.SHADY_PREFIX + 'cloneNode'](deep);
  }

  getRootNode(options) {
    return this.node[utils.SHADY_PREFIX + 'getRootNode'](options);
  }

  contains(node) {
    return this.node[utils.SHADY_PREFIX + 'contains'](node);
  }

  dispatchEvent(event) {
    return this.node[utils.SHADY_PREFIX + 'dispatchEvent'](event);
  }

  // element
  setAttribute(name, value) {
    this.node[utils.SHADY_PREFIX + 'setAttribute'](name, value);
  }

  // NOTE: not needed, just here for balance
  getAttribute(name) {
    return this.node[utils.NATIVE_PREFIX + 'getAttribute'](name);
  }

  // NOTE: not needed, just here for balance
  hasAttribute(name) {
    return this.node[utils.NATIVE_PREFIX + 'hasAttribute'](name);
  }

  removeAttribute(name) {
    this.node[utils.SHADY_PREFIX + 'removeAttribute'](name);
  }

  attachShadow(options) {
    return this.node[utils.SHADY_PREFIX + 'attachShadow'](options);
  }

  /** @return {!Node|undefined} */
  get activeElement() {
    if (utils.isShadyRoot(this.node) || this.node.nodeType === Node.DOCUMENT_NODE) {
      const e = this.node[utils.SHADY_PREFIX + 'activeElement'];
      return e;
    }
    return undefined;
  }

  /**
   * Installed for compatibility with browsers (older Chrome/Safari) that do
   * not have a configurable `activeElement` accessor. Enables noPatch and
   * patch mode both to consistently use ShadyDOM.wrap(document)._activeElement.
   * @override
   * @return {!Node|undefined}
   */
  get _activeElement() {
    return this.activeElement;
  }

  // NOTE: not needed, just here for balance
  /** @override */
  focus() {
    this.node[utils.NATIVE_PREFIX + 'focus']();
  }

  blur() {
    this.node[utils.SHADY_PREFIX + 'blur']();
  }

  // document
  importNode(node, deep) {
    if (this.node.nodeType === Node.DOCUMENT_NODE) {
      return this.node[utils.SHADY_PREFIX + 'importNode'](node, deep);
    }
  }

  getElementById(id) {
    if (this.node.nodeType === Node.DOCUMENT_NODE) {
      return this.node[utils.SHADY_PREFIX + 'getElementById'](id);
    }
  }

  // query
  querySelector(selector) {
    return this.node[utils.SHADY_PREFIX + 'querySelector'](selector);
  }

  querySelectorAll(selector, useNative) {
    return this.node[utils.SHADY_PREFIX + 'querySelectorAll'](selector, useNative);
  }

  // slot
  assignedNodes(options) {
    if (this.node.localName === 'slot') {
      return this.node[utils.SHADY_PREFIX + 'assignedNodes'](options);
    }
  }

  get host() {
    if (utils.isShadyRoot(this.node)) {
      return /** @type {!ShadowRoot} */(this.node).host;
    }
    return undefined;
  }

  get parentNode() {
    return this.node[utils.SHADY_PREFIX + 'parentNode'];
  }

  get firstChild() {
    return this.node[utils.SHADY_PREFIX + 'firstChild'];
  }

  get lastChild() {
    return this.node[utils.SHADY_PREFIX + 'lastChild'];
  }

  get nextSibling() {
    return this.node[utils.SHADY_PREFIX + 'nextSibling'];
  }

  get previousSibling() {
    return this.node[utils.SHADY_PREFIX + 'previousSibling'];
  }

  get childNodes() {
    return this.node[utils.SHADY_PREFIX + 'childNodes'];
  }

  get parentElement() {
    return this.node[utils.SHADY_PREFIX + 'parentElement'];
  }

  get firstElementChild() {
    return this.node[utils.SHADY_PREFIX + 'firstElementChild'];
  }

  get lastElementChild() {
    return this.node[utils.SHADY_PREFIX + 'lastElementChild'];
  }

  get nextElementSibling() {
    return this.node[utils.SHADY_PREFIX + 'nextElementSibling'];
  }

  get previousElementSibling() {
    return this.node[utils.SHADY_PREFIX + 'previousElementSibling'];
  }

  get children() {
    return this.node[utils.SHADY_PREFIX + 'children'];
  }

  get childElementCount() {
    return this.node[utils.SHADY_PREFIX + 'childElementCount'];
  }

  get shadowRoot() {
    return this.node[utils.SHADY_PREFIX + 'shadowRoot'];
  }

  get assignedSlot() {
    return this.node[utils.SHADY_PREFIX + 'assignedSlot'];
  }

  get isConnected() {
    return this.node[utils.SHADY_PREFIX + 'isConnected'];
  }

  get innerHTML() {
    return this.node[utils.SHADY_PREFIX + 'innerHTML'];
  }

  set innerHTML(value) {
    this.node[utils.SHADY_PREFIX + 'innerHTML'] = value;
  }

  get textContent() {
    return this.node[utils.SHADY_PREFIX + 'textContent'];
  }

  set textContent(value) {
    this.node[utils.SHADY_PREFIX + 'textContent'] = value;
  }

  get slot() {
    return this.node[utils.SHADY_PREFIX + 'slot'];
  }

  set slot(value) {
    this.node[utils.SHADY_PREFIX + 'slot'] = value;
  }

  get className() {
    return this.node[utils.SHADY_PREFIX + 'className'];
  }

  set className(value) {
    return this.node[utils.SHADY_PREFIX + 'className'] = value;
  }

}

const addEventPropertyWrapper = (name) => {
  Object.defineProperty(Wrapper.prototype, name, {
    /** @this {Wrapper} */
    get() {
      return this.node[utils.SHADY_PREFIX + name];
    },
    /** @this {Wrapper} */
    set(value) {
      this.node[utils.SHADY_PREFIX + name] = value;
    },
    configurable: true
  });
};

eventPropertyNamesForElement.forEach(name => addEventPropertyWrapper(name));
eventPropertyNamesForHTMLElement.forEach(name => addEventPropertyWrapper(name));

export {Wrapper};

const wrapperMap = new WeakMap();

export function wrap(obj) {
  if (utils.isShadyRoot(obj) || obj instanceof Wrapper) {
    return obj;
  }
  let wrapper = wrapperMap.get(obj)
  if (!wrapper) {
    wrapper = new Wrapper(obj);
    wrapperMap.set(obj, wrapper);
  }
  return wrapper;
}
<commit_msg>Add ParentNode methods to Wrapper.<commit_after>/**
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

import * as utils from './utils.js';
import {eventPropertyNamesForElement, eventPropertyNamesForHTMLElement} from './patch-events.js';

/** @implements {IWrapper} */
class Wrapper {

  /** @param {!Node} node */
  constructor(node) {
    this.node = node;
  }

  // node
  addEventListener(name, fn, options) {
    return this.node[utils.SHADY_PREFIX + 'addEventListener'](name, fn, options);
  }

  removeEventListener(name, fn, options) {
    return this.node[utils.SHADY_PREFIX + 'removeEventListener'](name, fn, options);
  }

  appendChild(node) {
    return this.node[utils.SHADY_PREFIX + 'appendChild'](node);
  }

  insertBefore(node, ref_node) {
    return this.node[utils.SHADY_PREFIX + 'insertBefore'](node, ref_node);
  }

  removeChild(node) {
    return this.node[utils.SHADY_PREFIX + 'removeChild'](node);
  }

  replaceChild(node, ref_node) {
    return this.node[utils.SHADY_PREFIX + 'replaceChild'](node, ref_node);
  }

  cloneNode(deep) {
    return this.node[utils.SHADY_PREFIX + 'cloneNode'](deep);
  }

  getRootNode(options) {
    return this.node[utils.SHADY_PREFIX + 'getRootNode'](options);
  }

  contains(node) {
    return this.node[utils.SHADY_PREFIX + 'contains'](node);
  }

  dispatchEvent(event) {
    return this.node[utils.SHADY_PREFIX + 'dispatchEvent'](event);
  }

  // element
  setAttribute(name, value) {
    this.node[utils.SHADY_PREFIX + 'setAttribute'](name, value);
  }

  // NOTE: not needed, just here for balance
  getAttribute(name) {
    return this.node[utils.NATIVE_PREFIX + 'getAttribute'](name);
  }

  // NOTE: not needed, just here for balance
  hasAttribute(name) {
    return this.node[utils.NATIVE_PREFIX + 'hasAttribute'](name);
  }

  removeAttribute(name) {
    this.node[utils.SHADY_PREFIX + 'removeAttribute'](name);
  }

  attachShadow(options) {
    return this.node[utils.SHADY_PREFIX + 'attachShadow'](options);
  }

  /** @return {!Node|undefined} */
  get activeElement() {
    if (utils.isShadyRoot(this.node) || this.node.nodeType === Node.DOCUMENT_NODE) {
      const e = this.node[utils.SHADY_PREFIX + 'activeElement'];
      return e;
    }
    return undefined;
  }

  /**
   * Installed for compatibility with browsers (older Chrome/Safari) that do
   * not have a configurable `activeElement` accessor. Enables noPatch and
   * patch mode both to consistently use ShadyDOM.wrap(document)._activeElement.
   * @override
   * @return {!Node|undefined}
   */
  get _activeElement() {
    return this.activeElement;
  }

  // NOTE: not needed, just here for balance
  /** @override */
  focus() {
    this.node[utils.NATIVE_PREFIX + 'focus']();
  }

  blur() {
    this.node[utils.SHADY_PREFIX + 'blur']();
  }

  // document
  importNode(node, deep) {
    if (this.node.nodeType === Node.DOCUMENT_NODE) {
      return this.node[utils.SHADY_PREFIX + 'importNode'](node, deep);
    }
  }

  getElementById(id) {
    if (this.node.nodeType === Node.DOCUMENT_NODE) {
      return this.node[utils.SHADY_PREFIX + 'getElementById'](id);
    }
  }

  // query
  querySelector(selector) {
    return this.node[utils.SHADY_PREFIX + 'querySelector'](selector);
  }

  querySelectorAll(selector, useNative) {
    return this.node[utils.SHADY_PREFIX + 'querySelectorAll'](selector, useNative);
  }

  // slot
  assignedNodes(options) {
    if (this.node.localName === 'slot') {
      return this.node[utils.SHADY_PREFIX + 'assignedNodes'](options);
    }
  }

  get host() {
    if (utils.isShadyRoot(this.node)) {
      return /** @type {!ShadowRoot} */(this.node).host;
    }
    return undefined;
  }

  get parentNode() {
    return this.node[utils.SHADY_PREFIX + 'parentNode'];
  }

  get firstChild() {
    return this.node[utils.SHADY_PREFIX + 'firstChild'];
  }

  get lastChild() {
    return this.node[utils.SHADY_PREFIX + 'lastChild'];
  }

  get nextSibling() {
    return this.node[utils.SHADY_PREFIX + 'nextSibling'];
  }

  get previousSibling() {
    return this.node[utils.SHADY_PREFIX + 'previousSibling'];
  }

  get childNodes() {
    return this.node[utils.SHADY_PREFIX + 'childNodes'];
  }

  get parentElement() {
    return this.node[utils.SHADY_PREFIX + 'parentElement'];
  }

  get firstElementChild() {
    return this.node[utils.SHADY_PREFIX + 'firstElementChild'];
  }

  get lastElementChild() {
    return this.node[utils.SHADY_PREFIX + 'lastElementChild'];
  }

  get nextElementSibling() {
    return this.node[utils.SHADY_PREFIX + 'nextElementSibling'];
  }

  get previousElementSibling() {
    return this.node[utils.SHADY_PREFIX + 'previousElementSibling'];
  }

  get children() {
    return this.node[utils.SHADY_PREFIX + 'children'];
  }

  get childElementCount() {
    return this.node[utils.SHADY_PREFIX + 'childElementCount'];
  }

  get shadowRoot() {
    return this.node[utils.SHADY_PREFIX + 'shadowRoot'];
  }

  get assignedSlot() {
    return this.node[utils.SHADY_PREFIX + 'assignedSlot'];
  }

  get isConnected() {
    return this.node[utils.SHADY_PREFIX + 'isConnected'];
  }

  get innerHTML() {
    return this.node[utils.SHADY_PREFIX + 'innerHTML'];
  }

  set innerHTML(value) {
    this.node[utils.SHADY_PREFIX + 'innerHTML'] = value;
  }

  get textContent() {
    return this.node[utils.SHADY_PREFIX + 'textContent'];
  }

  set textContent(value) {
    this.node[utils.SHADY_PREFIX + 'textContent'] = value;
  }

  get slot() {
    return this.node[utils.SHADY_PREFIX + 'slot'];
  }

  set slot(value) {
    this.node[utils.SHADY_PREFIX + 'slot'] = value;
  }

  get className() {
    return this.node[utils.SHADY_PREFIX + 'className'];
  }

  set className(value) {
    return this.node[utils.SHADY_PREFIX + 'className'] = value;
  }

  append(...args) {
    return this.node[utils.SHADY_PREFIX + 'append'](...args);
  }

  prepend(...args) {
    return this.node[utils.SHADY_PREFIX + 'prepend'](...args);
  }

  replaceChildren(...args) {
    return this.node[utils.SHADY_PREFIX + 'replaceChildren'](...args);
  }

}

const addEventPropertyWrapper = (name) => {
  Object.defineProperty(Wrapper.prototype, name, {
    /** @this {Wrapper} */
    get() {
      return this.node[utils.SHADY_PREFIX + name];
    },
    /** @this {Wrapper} */
    set(value) {
      this.node[utils.SHADY_PREFIX + name] = value;
    },
    configurable: true
  });
};

eventPropertyNamesForElement.forEach(name => addEventPropertyWrapper(name));
eventPropertyNamesForHTMLElement.forEach(name => addEventPropertyWrapper(name));

export {Wrapper};

const wrapperMap = new WeakMap();

export function wrap(obj) {
  if (utils.isShadyRoot(obj) || obj instanceof Wrapper) {
    return obj;
  }
  let wrapper = wrapperMap.get(obj)
  if (!wrapper) {
    wrapper = new Wrapper(obj);
    wrapperMap.set(obj, wrapper);
  }
  return wrapper;
}
<|endoftext|>


<commit_before>const EosKnowledgePrivate = imports.gi.EosKnowledgePrivate;
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;

const ArticleObjectModel = imports.search.articleObjectModel;
const ArticlePresenter = imports.app.articlePresenter;
const TreeNode = imports.search.treeNode;
const Utils = imports.tests.utils;
const SearchUtils = imports.search.utils;

Gtk.init(null);

const MockView = new Lang.Class({
    Name: 'MockView',
    GTypeName: 'testArticlePresenter_MockView',
    Extends: GObject.Object,
    Signals: {
        'new-view-transitioned': {}
    },

    _init: function (props) {
        this.parent(props);
        this.toc = {
            connect: function () {},
        }
    },

    switch_in_content_view: function (view, animation_type) {
        this.emit('new-view-transitioned');
    },
});

describe('Article Presenter', function () {
    let presenter;
    let view;
    let toc_json = { "tableOfContents":
        [{"hasIndex": 0, "hasIndexLabel": 1, "hasLabel": "Foo", "hasContent": "#Foo"},
         {"hasIndex": 1, "hasIndexLabel": 2, "hasLabel": "Bar", "hasContent": "#Bar"},
         {"hasIndex": 2, "hasIndexLabel": 3, "hasLabel": "Baz", "hasContent": "#Baz"}]};
    let articleObject;

    beforeEach(function (done) {
        Utils.register_gresource();

        let toc = TreeNode.tree_model_from_tree_node(toc_json);
        articleObject = new ArticleObjectModel.ArticleObjectModel({
            ekn_id: 'ekn:///foo/bar',
            content_type: 'text/html',
            get_content_stream: () => { return SearchUtils.string_to_stream('
hi
'); },
            title: 'Wikihow & title',
            table_of_contents: toc,
        });

        view = new MockView();
        view.connect_after('new-view-transitioned', done);

        presenter = new ArticlePresenter.ArticlePresenter({
            article_view: view,
        });
        presenter.load_article(articleObject, EosKnowledgePrivate.LoadingAnimationType.NONE);
    });

    it('can be constructed', function () {});

    it('can set title and subtitle on view', function () {
        expect(view.title).toBe(articleObject.title);
    });

    it('can set toc section list', function () {
        let labels = [];
        for (let obj of toc_json['tableOfContents']) {
            if (!('hasParent' in obj)) {
                labels.push(obj['hasLabel']);
            }
        }
        expect(view.toc.section_list).toEqual(labels);
    });
});
<commit_msg>Mock out engine in ArticlePresenter test<commit_after>const EosKnowledgePrivate = imports.gi.EosKnowledgePrivate;
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;

const ArticleObjectModel = imports.search.articleObjectModel;
const ArticlePresenter = imports.app.articlePresenter;
const Engine = imports.search.engine;
const TreeNode = imports.search.treeNode;
const Utils = imports.tests.utils;
const SearchUtils = imports.search.utils;

Gtk.init(null);

const MockView = new Lang.Class({
    Name: 'MockView',
    GTypeName: 'testArticlePresenter_MockView',
    Extends: GObject.Object,
    Signals: {
        'new-view-transitioned': {}
    },

    _init: function (props) {
        this.parent(props);
        this.toc = {
            connect: function () {},
        }
    },

    switch_in_content_view: function (view, animation_type) {
        this.emit('new-view-transitioned');
    },
});

describe('Article Presenter', function () {
    let presenter;
    let view;
    let toc_json = { "tableOfContents":
        [{"hasIndex": 0, "hasIndexLabel": 1, "hasLabel": "Foo", "hasContent": "#Foo"},
         {"hasIndex": 1, "hasIndexLabel": 2, "hasLabel": "Bar", "hasContent": "#Bar"},
         {"hasIndex": 2, "hasIndexLabel": 3, "hasLabel": "Baz", "hasContent": "#Baz"}]};
    let articleObject;

    beforeEach(function (done) {
        Utils.register_gresource();

        let toc = TreeNode.tree_model_from_tree_node(toc_json);
        articleObject = new ArticleObjectModel.ArticleObjectModel({
            ekn_id: 'ekn:///foo/bar',
            content_type: 'text/html',
            get_content_stream: () => { return SearchUtils.string_to_stream('
hi
'); },
            title: 'Wikihow & title',
            table_of_contents: toc,
        });
        let engine = Engine.Engine.get_default();
        spyOn(engine, 'get_object_by_id').and.callFake(function (id, cancellable, callback) {
            callback(engine);
        });
        spyOn(engine, 'get_object_by_id_finish').and.returnValue(articleObject);

        view = new MockView();
        view.connect_after('new-view-transitioned', done);

        presenter = new ArticlePresenter.ArticlePresenter({
            article_view: view,
        });
        presenter.load_article(articleObject, EosKnowledgePrivate.LoadingAnimationType.NONE);
    });

    it('can be constructed', function () {});

    it('can set title and subtitle on view', function () {
        expect(view.title).toBe(articleObject.title);
    });

    it('can set toc section list', function () {
        let labels = [];
        for (let obj of toc_json['tableOfContents']) {
            if (!('hasParent' in obj)) {
                labels.push(obj['hasLabel']);
            }
        }
        expect(view.toc.section_list).toEqual(labels);
    });
});
<|endoftext|>


<commit_before>import { c } from 'ttag';

export const MAX_RETRY_AFTER_TIMEOUT = 10; // seconds
export const MAX_RETRY_AFTER_ATTEMPT = 5; // how many times to try the same request
export const APPS = {
    PROTONMAIL: 'protonmail',
    PROTONMAIL_SETTINGS: 'protonmail-settings',
    PROTONCONTACTS: 'protoncontacts',
    PROTONDRIVE: 'protondrive',
    PROTONCALENDAR: 'protoncalendar',
    PROTONWALLET: 'protonwallet',
    PROTONVPN_SETTINGS: 'protonvpn-settings'
};
export const MAIN_USER_KEY = 'USER_KEYS';
export const SECURE_SESSION_STORAGE_KEY = 'SECURE';
export const MAILBOX_PASSWORD_KEY = 'proton:mailbox_pwd';
export const UID_KEY = 'proton:oauth:UID';
export const INTERVAL_EVENT_TIMER = 30 * 1000;
export const MAX_SIZE_SCREENSHOT = 500 * 1000;
export const FILTER_VERSION = 2;
export const EVENT_ACTIONS = {
    DELETE: 0,
    CREATE: 1,
    UPDATE: 2,
    UPDATE_DRAFT: 2,
    UPDATE_FLAGS: 3
};
export const USER_ROLES = {
    FREE_ROLE: 0,
    MEMBER_ROLE: 1,
    ADMIN_ROLE: 2
};
export const ELEMENTS_PER_PAGE = 10;
export const INVOICE_OWNER = {
    USER: 0,
    ORGANIZATION: 1
};
export const PM_SIGNATURE = 'Sent with ProtonMail Secure Email.';
export const DEFAULT_CURRENCY = 'EUR';
export const CURRENCIES = ['EUR', 'USD', 'CHF'];
export const MIN_BITCOIN_AMOUNT = 500;
export const DEFAULT_CREDITS_AMOUNT = 5000;
export const DEFAULT_DONATION_AMOUNT = 5000;
export const BTC_DONATION_ADDRESS = '1Q1nhq1NbxPYAbw1BppwKbCqg58ZqMb9A8';
export const AUTH_LOG_EVENTS = {
    LOGIN_FAILURE_PASSWORD: 0,
    LOGIN_SUCCESS: 1,
    LOGOUT: 2,
    LOGIN_FAILURE_2FA: 3,
    LOGIN_SUCCESS_AWAIT_2FA: 4
};
export const INVOICE_TYPE = {
    OTHER: 0,
    SUBSCRIPTION: 1,
    CANCELLATION: 2,
    CREDIT: 3,
    DONATION: 4,
    CHARGEBACK: 5,
    RENEWAL: 6,
    REFUND: 7,
    MODIFICATION: 8,
    ADDITION: 9
};
export const INVOICE_STATE = {
    UNPAID: 0,
    PAID: 1,
    VOID: 2,
    BILLED: 3,
    WRITEOFF: 4
};

export const BASE_SIZE = 1024;
export const GIGA = BASE_SIZE ** 3;

export const LOGS_STATE = {
    DISABLE: 0,
    BASIC: 1,
    ADVANCED: 2
};

export const MEMBER_TYPE = {
    MEMBER: 0,
    SUB_MEMBER: 1
};

export const DOMAIN_STATE = {
    DOMAIN_STATE_DEFAULT: 0, // Domain's State before verify or after deactivation
    DOMAIN_STATE_ACTIVE: 1, // active once verified
    DOMAIN_STATE_WARN: 2 // detected backward DNS change after ACTIVE
};

export const VERIFY_STATE = {
    VERIFY_STATE_DEFAULT: 0, // 0 is default, no good
    VERIFY_STATE_EXIST: 1, // 1 is has code but doesn't match DB's, no good
    VERIFY_STATE_GOOD: 2 // 2 is has code and matches DB's, good!
};

export const MX_STATE = {
    MX_STATE_DEFAULT: 0, // 0 is default, no good
    MX_STATE_NO_US: 1, // 1 is set but does not have us
    MX_STATE_INC_US: 2, // 2 is includes our MX but priority no good
    MX_STATE_GOOD: 3 // 3 is includes our MX and we are highest and pri is legit, good!
};

export const SPF_STATE = {
    SPF_STATE_DEFAULT: 0, // 0 is default, no spf record
    SPF_STATE_ONE: 1, // 1 is has spf record but not us
    SPF_STATE_MULT: 2, // 2 is has multiple spf records, no good
    SPF_STATE_GOOD: 3 // 3 is has spf record and includes us, good!
};

export const DKIM_STATE = {
    DKIM_STATE_DEFAULT: 0, // 0 is default, no dkim record
    DKIM_STATE_ONE: 1, // 1 is found entries but format wrong
    DKIM_STATE_MULT: 2, // 2 is multiple dkim records, no good
    DKIM_STATE_CHECK: 3, // 3 is proper entry, but did not check or did not match DB's public key
    DKIM_STATE_GOOD: 4, // 4 is proper entry and matches DB's public key, good!
    DKIM_STATE_OFF: 5 // 5 is User wants to stop signing with dkim
};

export const DMARC_STATE = {
    DMARC_STATE_DEFAULT: 0, // 0 is default, no dmarc record
    DMARC_STATE_ONE: 1, // 1 is found entries but format wrong
    DMARC_STATE_MULT: 2, // 2 is multiple dmarc records, no good
    DMARC_STATE_GOOD: 3 // 3 is good!
};

export const ADDRESS_STATUS = {
    STATUS_DISABLED: 0,
    STATUS_ENABLED: 1
};

export const ADDRESS_TYPE = {
    TYPE_ORIGINAL: 1,
    TYPE_ALIAS: 2,
    TYPE_CUSTOM_DOMAIN: 3,
    TYPE_PREMIUM: 4
};

export const RECEIVE_ADDRESS = {
    RECEIVE_YES: 1,
    RECEIVE_NO: 0
};

export const SEND_ADDRESS = {
    SEND_YES: 1,
    SEND_NO: 0
};

export const MEMBER_PRIVATE = {
    READABLE: 0,
    UNREADABLE: 1
};

export const MEMBER_ROLE = {
    ORGANIZATION_NONE: 0,
    ORGANIZATION_MEMBER: 1,
    ORGANIZATION_OWNER: 2
};

export const PACKAGE_TYPE = {
    SEND_PM: 1,
    SEND_EO: 2,
    SEND_CLEAR: 4,
    SEND_PGP_INLINE: 8,
    SEND_PGP_MIME: 16
};

export const MIME_TYPES = {
    MIME: 'multipart/mixed',
    PLAINTEXT: 'text/plain',
    DEFAULT: 'text/html'
};

export const RECIPIENT_TYPE = {
    TYPE_INTERNAL: 1,
    TYPE_EXTERNAL: 2,
    TYPE_NO_RECEIVE: 3
};

export const KEY_FLAGS = {
    DISABLED: 0,
    ENABLE_VERIFICATION: 1,
    ENABLE_ENCRYPTION: 2
};

export const SHOW_IMAGES = {
    REMOTE: 1,
    EMBEDDED: 2
};

export const COMPOSER_MODE = {
    POPUP: 0,
    MAXIMIZED: 1
};

export const VIEW_LAYOUT = {
    COLUMN: 0,
    ROW: 1
};

export const STICKY_LABELS = {
    OFF: 0,
    ON: 1
};

export const VIEW_MODE = {
    GROUP: 0,
    SINGLE: 1
};

export const SHOW_MOVED = {
    NONE: 0,
    DRAFTS: 1,
    SENT: 2,
    DRAFTS_AND_SENT: 3
};

export const DRAFT_TYPE = {
    NORMAL: 'text/html',
    PLAIN_TEXT: 'text/plain'
};

export const RIGHT_TO_LEFT = {
    OFF: 0,
    ON: 1
};

export const THEMES = {
    DARK: {
        label: c('Theme').t`Dark (Default)`,
        identifier: '/* dark-theme */'
    },
    LIGHT: {
        label: c('Theme').t`Light`,
        identifier: '/* light-theme */'
    },
    BLUE: {
        label: c('Theme').t`Blue`,
        identifier: '/* blue-theme */'
    },
    CUSTOM: {
        label: c('Theme').t`Custom theme`,
        identifier: '/* custom-theme */'
    }
};

export const DEFAULT_CYCLE = 12;

export const CYCLE = {
    MONTHLY: 1,
    YEARLY: 12,
    TWO_YEARS: 24
};

const BLACK_FRIDAY_YEAR = 2018;
export const BLACK_FRIDAY = {
    YEAR: BLACK_FRIDAY_YEAR,
    COUPON_CODE: 'TWO4ONE2018',
    CYCLE: CYCLE.TWO_YEARS,
    BETWEEN: {
        START: `${BLACK_FRIDAY_YEAR}-11-23`,
        END: `${BLACK_FRIDAY_YEAR}-11-26`
    },
    INTERVAL: 10 * 60 * 1000
};
export const MIN_PAYPAL_AMOUNT = 500;
export const MAX_PAYPAL_AMOUNT = 99999900;
export const NEWS = {
    ANNOUNCEMENTS: 1,
    FEATURES: 2,
    NEWSLETTER: 4,
    BETA: 8,
    BUSINESS: 16
};

export const CONTACT_EMAILS_LIMIT = 1000;
export const CONTACTS_LIMIT = 1000;
export const EXPORT_CONTACTS_LIMIT = 50; // Maximum page size for export is 50 from API
export const CONTACTS_REQUESTS_PER_SECOND = 10;
export const ALL_MEMBERS_ID = -100;

export const LABEL_EXCLUSIVE = {
    FOLDER: 1,
    LABEL: 0
};

export const LABEL_COLORS = [
    '#7272a7',
    '#8989ac',

    '#cf5858',
    '#cf7e7e',

    '#c26cc7',
    '#c793ca',

    '#7569d1',
    '#9b94d1',

    '#69a9d1',
    '#a8c4d5',

    '#5ec7b7',
    '#97c9c1',

    '#72bb75',
    '#9db99f',

    '#c3d261',
    '#c6cd97',

    '#e6c04c',
    '#e7d292',

    '#e6984c',
    '#dfb286'
];
export const REGEX_IMAGE_EXTENSION = /\.(gif|jpe?g|tiff|png)$/i;

export const LINK_WARNING = {
    KEY: 'link_warning',
    VALUE: 'dontAsk'
};

export const ADDON_NAMES = {
    ADDRESS: '5address',
    MEMBER: '1member',
    DOMAIN: '1domain',
    SPACE: '1gb',
    VPN: '1vpn'
};

export const PLAN_TYPES = {
    PLAN: 1,
    ADDON: 0
};

export const PLAN_SERVICES = {
    MAIL: 1,
    VPN: 4
};

export const FREE_SUBSCRIPTION = {}; // You don't need more, use `user.isPaid`
export const FREE_ORGANIZATION = {}; // You don't need more, use `user.isPaid`

export const PLAN_NAMES = {
    plus: 'Plus',
    professional: 'Professional',
    visionary: 'Visionary',
    vpnbasic: 'Basic',
    vpnplus: 'Plus'
};

export const COUPON_CODES = {
    BUNDLE: 'BUNDLE',
    PMTEAM: 'PMTEAM',
    BLACK_FRIDAY_2018: 'TWO4ONE2018'
};

export const GIFT_CODE_LENGTH = 16;

export const PERMISSIONS = {
    ADMIN: 'admin',
    MEMBER: 'member',
    FREE: 'free',
    UPGRADER: 'upgrader',
    MULTI_USERS: 'multi-users',
    PAID: 'paid',
    PAID_MAIL: 'paid-mail',
    PAID_VPN: 'paid-vpn'
};

export const MESSAGE_BUTTONS = {
    READ_UNREAD: 0,
    UNREAD_READ: 1
};

export const KEY_FILE_EXTENSION = '.asc';

export const ENCRYPTION_TYPES = {
    RSA2048: 'RSA2048',
    RSA4096: 'RSA4096',
    X25519: 'X25519'
};

export const DEFAULT_ENCRYPTION_CONFIG = ENCRYPTION_TYPES.RSA2048;

export const ENCRYPTION_CONFIGS = {
    [ENCRYPTION_TYPES.X25519]: { curve: 'ed25519' },
    [ENCRYPTION_TYPES.RSA4096]: { numBits: 4096 },
    [ENCRYPTION_TYPES.RSA2048]: { numBits: 2048 }
};

export const KEY_FLAG = {
    ENCRYPTED_AND_SIGNED: 3,
    ENCRYPTED: 2,
    SIGNED: 1,
    CLEAR_TEXT: 0
};

export const USER_STATUS = {
    DELETED: 0,
    DISABLED: 1,
    USER: 2,
    VPN_ADMIN: 3,
    ADMIN: 4,
    SUPER_ADMIN: 5
};

export const MAILBOX_IDENTIFIERS = {
    inbox: '0',
    allDrafts: '1',
    allSent: '2',
    trash: '3',
    spam: '4',
    allmail: '5',
    starred: '10',
    archive: '6',
    sent: '7',
    drafts: '8',
    search: 'search',
    label: 'label'
};

/* eslint  no-useless-escape: "off" */
export const REGEX_EMAIL = /(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/i;

export const FILTER_STATUS = {
    ENABLED: 1,
    DISABLED: 0
};
export const VCARD_VERSION = '4.0';
export const CONTACT_CARD_TYPE = {
    ENCRYPTED_AND_SIGNED: 3,
    SIGNED: 2,
    ENCRYPTED: 1,
    CLEAR_TEXT: 0
};

export const LABEL_TYPE = {
    MESSAGE: 1,
    CONTACT_GROUP: 2,
    MESSAGE_FOLDER: 3
};
export const DEFAULT_LOCALE = 'en_US';

export const PASSWORD_MODE = {
    SINGLE: 1,
    TWO_PASSWORD: 2
};

export const TWO_FA_CONFIG = {
    PERIOD: 30,
    DIGITS: 6,
    ALGORITHM: 'SHA1'
};

export const SORT_DIRECTION = {
    ASC: 'ascending',
    DESC: 'descending'
};

export const SERVER_FEATURES = {
    SECURE_CORE: 1,
    TOR: 2,
    P2P: 4,
    XOR: 8,
    IPV6: 16
};

export const PAYMENT_TOKEN_STATUS = {
    STATUS_PENDING: 0,
    STATUS_CHARGEABLE: 1,
    STATUS_FAILED: 2,
    STATUS_CONSUMED: 3,
    STATUS_NOT_SUPPORTED: 4
};

export const PAYMENT_METHOD_TYPES = {
    CARD: 'card',
    PAYPAL: 'paypal',
    BITCOIN: 'bitcoin',
    CASH: 'cash'
};
<commit_msg>Add two fa flags<commit_after>import { c } from 'ttag';

export const MAX_RETRY_AFTER_TIMEOUT = 10; // seconds
export const MAX_RETRY_AFTER_ATTEMPT = 5; // how many times to try the same request
export const APPS = {
    PROTONMAIL: 'protonmail',
    PROTONMAIL_SETTINGS: 'protonmail-settings',
    PROTONCONTACTS: 'protoncontacts',
    PROTONDRIVE: 'protondrive',
    PROTONCALENDAR: 'protoncalendar',
    PROTONWALLET: 'protonwallet',
    PROTONVPN_SETTINGS: 'protonvpn-settings'
};
export const MAIN_USER_KEY = 'USER_KEYS';
export const SECURE_SESSION_STORAGE_KEY = 'SECURE';
export const MAILBOX_PASSWORD_KEY = 'proton:mailbox_pwd';
export const UID_KEY = 'proton:oauth:UID';
export const INTERVAL_EVENT_TIMER = 30 * 1000;
export const MAX_SIZE_SCREENSHOT = 500 * 1000;
export const FILTER_VERSION = 2;
export const EVENT_ACTIONS = {
    DELETE: 0,
    CREATE: 1,
    UPDATE: 2,
    UPDATE_DRAFT: 2,
    UPDATE_FLAGS: 3
};
export const USER_ROLES = {
    FREE_ROLE: 0,
    MEMBER_ROLE: 1,
    ADMIN_ROLE: 2
};
export const ELEMENTS_PER_PAGE = 10;
export const INVOICE_OWNER = {
    USER: 0,
    ORGANIZATION: 1
};
export const PM_SIGNATURE = 'Sent with ProtonMail Secure Email.';
export const DEFAULT_CURRENCY = 'EUR';
export const CURRENCIES = ['EUR', 'USD', 'CHF'];
export const MIN_BITCOIN_AMOUNT = 500;
export const DEFAULT_CREDITS_AMOUNT = 5000;
export const DEFAULT_DONATION_AMOUNT = 5000;
export const BTC_DONATION_ADDRESS = '1Q1nhq1NbxPYAbw1BppwKbCqg58ZqMb9A8';
export const AUTH_LOG_EVENTS = {
    LOGIN_FAILURE_PASSWORD: 0,
    LOGIN_SUCCESS: 1,
    LOGOUT: 2,
    LOGIN_FAILURE_2FA: 3,
    LOGIN_SUCCESS_AWAIT_2FA: 4
};
export const INVOICE_TYPE = {
    OTHER: 0,
    SUBSCRIPTION: 1,
    CANCELLATION: 2,
    CREDIT: 3,
    DONATION: 4,
    CHARGEBACK: 5,
    RENEWAL: 6,
    REFUND: 7,
    MODIFICATION: 8,
    ADDITION: 9
};
export const INVOICE_STATE = {
    UNPAID: 0,
    PAID: 1,
    VOID: 2,
    BILLED: 3,
    WRITEOFF: 4
};

export const BASE_SIZE = 1024;
export const GIGA = BASE_SIZE ** 3;

export const LOGS_STATE = {
    DISABLE: 0,
    BASIC: 1,
    ADVANCED: 2
};

export const MEMBER_TYPE = {
    MEMBER: 0,
    SUB_MEMBER: 1
};

export const DOMAIN_STATE = {
    DOMAIN_STATE_DEFAULT: 0, // Domain's State before verify or after deactivation
    DOMAIN_STATE_ACTIVE: 1, // active once verified
    DOMAIN_STATE_WARN: 2 // detected backward DNS change after ACTIVE
};

export const VERIFY_STATE = {
    VERIFY_STATE_DEFAULT: 0, // 0 is default, no good
    VERIFY_STATE_EXIST: 1, // 1 is has code but doesn't match DB's, no good
    VERIFY_STATE_GOOD: 2 // 2 is has code and matches DB's, good!
};

export const MX_STATE = {
    MX_STATE_DEFAULT: 0, // 0 is default, no good
    MX_STATE_NO_US: 1, // 1 is set but does not have us
    MX_STATE_INC_US: 2, // 2 is includes our MX but priority no good
    MX_STATE_GOOD: 3 // 3 is includes our MX and we are highest and pri is legit, good!
};

export const SPF_STATE = {
    SPF_STATE_DEFAULT: 0, // 0 is default, no spf record
    SPF_STATE_ONE: 1, // 1 is has spf record but not us
    SPF_STATE_MULT: 2, // 2 is has multiple spf records, no good
    SPF_STATE_GOOD: 3 // 3 is has spf record and includes us, good!
};

export const DKIM_STATE = {
    DKIM_STATE_DEFAULT: 0, // 0 is default, no dkim record
    DKIM_STATE_ONE: 1, // 1 is found entries but format wrong
    DKIM_STATE_MULT: 2, // 2 is multiple dkim records, no good
    DKIM_STATE_CHECK: 3, // 3 is proper entry, but did not check or did not match DB's public key
    DKIM_STATE_GOOD: 4, // 4 is proper entry and matches DB's public key, good!
    DKIM_STATE_OFF: 5 // 5 is User wants to stop signing with dkim
};

export const DMARC_STATE = {
    DMARC_STATE_DEFAULT: 0, // 0 is default, no dmarc record
    DMARC_STATE_ONE: 1, // 1 is found entries but format wrong
    DMARC_STATE_MULT: 2, // 2 is multiple dmarc records, no good
    DMARC_STATE_GOOD: 3 // 3 is good!
};

export const ADDRESS_STATUS = {
    STATUS_DISABLED: 0,
    STATUS_ENABLED: 1
};

export const ADDRESS_TYPE = {
    TYPE_ORIGINAL: 1,
    TYPE_ALIAS: 2,
    TYPE_CUSTOM_DOMAIN: 3,
    TYPE_PREMIUM: 4
};

export const RECEIVE_ADDRESS = {
    RECEIVE_YES: 1,
    RECEIVE_NO: 0
};

export const SEND_ADDRESS = {
    SEND_YES: 1,
    SEND_NO: 0
};

export const MEMBER_PRIVATE = {
    READABLE: 0,
    UNREADABLE: 1
};

export const MEMBER_ROLE = {
    ORGANIZATION_NONE: 0,
    ORGANIZATION_MEMBER: 1,
    ORGANIZATION_OWNER: 2
};

export const PACKAGE_TYPE = {
    SEND_PM: 1,
    SEND_EO: 2,
    SEND_CLEAR: 4,
    SEND_PGP_INLINE: 8,
    SEND_PGP_MIME: 16
};

export const MIME_TYPES = {
    MIME: 'multipart/mixed',
    PLAINTEXT: 'text/plain',
    DEFAULT: 'text/html'
};

export const RECIPIENT_TYPE = {
    TYPE_INTERNAL: 1,
    TYPE_EXTERNAL: 2,
    TYPE_NO_RECEIVE: 3
};

export const KEY_FLAGS = {
    DISABLED: 0,
    ENABLE_VERIFICATION: 1,
    ENABLE_ENCRYPTION: 2
};

export const SHOW_IMAGES = {
    REMOTE: 1,
    EMBEDDED: 2
};

export const COMPOSER_MODE = {
    POPUP: 0,
    MAXIMIZED: 1
};

export const VIEW_LAYOUT = {
    COLUMN: 0,
    ROW: 1
};

export const STICKY_LABELS = {
    OFF: 0,
    ON: 1
};

export const VIEW_MODE = {
    GROUP: 0,
    SINGLE: 1
};

export const SHOW_MOVED = {
    NONE: 0,
    DRAFTS: 1,
    SENT: 2,
    DRAFTS_AND_SENT: 3
};

export const DRAFT_TYPE = {
    NORMAL: 'text/html',
    PLAIN_TEXT: 'text/plain'
};

export const RIGHT_TO_LEFT = {
    OFF: 0,
    ON: 1
};

export const THEMES = {
    DARK: {
        label: c('Theme').t`Dark (Default)`,
        identifier: '/* dark-theme */'
    },
    LIGHT: {
        label: c('Theme').t`Light`,
        identifier: '/* light-theme */'
    },
    BLUE: {
        label: c('Theme').t`Blue`,
        identifier: '/* blue-theme */'
    },
    CUSTOM: {
        label: c('Theme').t`Custom theme`,
        identifier: '/* custom-theme */'
    }
};

export const DEFAULT_CYCLE = 12;

export const CYCLE = {
    MONTHLY: 1,
    YEARLY: 12,
    TWO_YEARS: 24
};

const BLACK_FRIDAY_YEAR = 2018;
export const BLACK_FRIDAY = {
    YEAR: BLACK_FRIDAY_YEAR,
    COUPON_CODE: 'TWO4ONE2018',
    CYCLE: CYCLE.TWO_YEARS,
    BETWEEN: {
        START: `${BLACK_FRIDAY_YEAR}-11-23`,
        END: `${BLACK_FRIDAY_YEAR}-11-26`
    },
    INTERVAL: 10 * 60 * 1000
};
export const MIN_PAYPAL_AMOUNT = 500;
export const MAX_PAYPAL_AMOUNT = 99999900;
export const NEWS = {
    ANNOUNCEMENTS: 1,
    FEATURES: 2,
    NEWSLETTER: 4,
    BETA: 8,
    BUSINESS: 16
};

export const CONTACT_EMAILS_LIMIT = 1000;
export const CONTACTS_LIMIT = 1000;
export const EXPORT_CONTACTS_LIMIT = 50; // Maximum page size for export is 50 from API
export const CONTACTS_REQUESTS_PER_SECOND = 10;
export const ALL_MEMBERS_ID = -100;

export const LABEL_EXCLUSIVE = {
    FOLDER: 1,
    LABEL: 0
};

export const LABEL_COLORS = [
    '#7272a7',
    '#8989ac',

    '#cf5858',
    '#cf7e7e',

    '#c26cc7',
    '#c793ca',

    '#7569d1',
    '#9b94d1',

    '#69a9d1',
    '#a8c4d5',

    '#5ec7b7',
    '#97c9c1',

    '#72bb75',
    '#9db99f',

    '#c3d261',
    '#c6cd97',

    '#e6c04c',
    '#e7d292',

    '#e6984c',
    '#dfb286'
];
export const REGEX_IMAGE_EXTENSION = /\.(gif|jpe?g|tiff|png)$/i;

export const LINK_WARNING = {
    KEY: 'link_warning',
    VALUE: 'dontAsk'
};

export const ADDON_NAMES = {
    ADDRESS: '5address',
    MEMBER: '1member',
    DOMAIN: '1domain',
    SPACE: '1gb',
    VPN: '1vpn'
};

export const PLAN_TYPES = {
    PLAN: 1,
    ADDON: 0
};

export const PLAN_SERVICES = {
    MAIL: 1,
    VPN: 4
};

export const FREE_SUBSCRIPTION = {}; // You don't need more, use `user.isPaid`
export const FREE_ORGANIZATION = {}; // You don't need more, use `user.isPaid`

export const PLAN_NAMES = {
    plus: 'Plus',
    professional: 'Professional',
    visionary: 'Visionary',
    vpnbasic: 'Basic',
    vpnplus: 'Plus'
};

export const COUPON_CODES = {
    BUNDLE: 'BUNDLE',
    PMTEAM: 'PMTEAM',
    BLACK_FRIDAY_2018: 'TWO4ONE2018'
};

export const GIFT_CODE_LENGTH = 16;

export const PERMISSIONS = {
    ADMIN: 'admin',
    MEMBER: 'member',
    FREE: 'free',
    UPGRADER: 'upgrader',
    MULTI_USERS: 'multi-users',
    PAID: 'paid',
    PAID_MAIL: 'paid-mail',
    PAID_VPN: 'paid-vpn'
};

export const MESSAGE_BUTTONS = {
    READ_UNREAD: 0,
    UNREAD_READ: 1
};

export const KEY_FILE_EXTENSION = '.asc';

export const ENCRYPTION_TYPES = {
    RSA2048: 'RSA2048',
    RSA4096: 'RSA4096',
    X25519: 'X25519'
};

export const DEFAULT_ENCRYPTION_CONFIG = ENCRYPTION_TYPES.RSA2048;

export const ENCRYPTION_CONFIGS = {
    [ENCRYPTION_TYPES.X25519]: { curve: 'ed25519' },
    [ENCRYPTION_TYPES.RSA4096]: { numBits: 4096 },
    [ENCRYPTION_TYPES.RSA2048]: { numBits: 2048 }
};

export const KEY_FLAG = {
    ENCRYPTED_AND_SIGNED: 3,
    ENCRYPTED: 2,
    SIGNED: 1,
    CLEAR_TEXT: 0
};

export const USER_STATUS = {
    DELETED: 0,
    DISABLED: 1,
    USER: 2,
    VPN_ADMIN: 3,
    ADMIN: 4,
    SUPER_ADMIN: 5
};

export const MAILBOX_IDENTIFIERS = {
    inbox: '0',
    allDrafts: '1',
    allSent: '2',
    trash: '3',
    spam: '4',
    allmail: '5',
    starred: '10',
    archive: '6',
    sent: '7',
    drafts: '8',
    search: 'search',
    label: 'label'
};

/* eslint  no-useless-escape: "off" */
export const REGEX_EMAIL = /(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/i;

export const FILTER_STATUS = {
    ENABLED: 1,
    DISABLED: 0
};
export const VCARD_VERSION = '4.0';
export const CONTACT_CARD_TYPE = {
    ENCRYPTED_AND_SIGNED: 3,
    SIGNED: 2,
    ENCRYPTED: 1,
    CLEAR_TEXT: 0
};

export const LABEL_TYPE = {
    MESSAGE: 1,
    CONTACT_GROUP: 2,
    MESSAGE_FOLDER: 3
};
export const DEFAULT_LOCALE = 'en_US';

export const PASSWORD_MODE = {
    SINGLE: 1,
    TWO_PASSWORD: 2
};

export const TWO_FA_FLAGS = {
    TOTP: 1,
    U2F: 2
};

export const TWO_FA_CONFIG = {
    PERIOD: 30,
    DIGITS: 6,
    ALGORITHM: 'SHA1'
};

export const SORT_DIRECTION = {
    ASC: 'ascending',
    DESC: 'descending'
};

export const SERVER_FEATURES = {
    SECURE_CORE: 1,
    TOR: 2,
    P2P: 4,
    XOR: 8,
    IPV6: 16
};

export const PAYMENT_TOKEN_STATUS = {
    STATUS_PENDING: 0,
    STATUS_CHARGEABLE: 1,
    STATUS_FAILED: 2,
    STATUS_CONSUMED: 3,
    STATUS_NOT_SUPPORTED: 4
};

export const PAYMENT_METHOD_TYPES = {
    CARD: 'card',
    PAYPAL: 'paypal',
    BITCOIN: 'bitcoin',
    CASH: 'cash'
};
<|endoftext|>


<commit_before><commit_msg>Create jslDate-0.0.1.min.js<commit_after><|endoftext|>


<commit_before>export const MAIN_USER_KEY = 'USER_KEYS';
export const SECURE_SESSION_STORAGE_KEY = 'SECURE';
export const MAILBOX_PASSWORD_KEY = 'proton:mailbox_pwd';
export const UID_KEY = 'proton:UID';
export const INTERVAL_EVENT_TIMER = 30 * 1000;
export const EVENT_ACTIONS = {
    DELETE: 0,
    CREATE: 1,
    UPDATE: 2,
    UPDATE_DRAFT: 2,
    UPDATE_FLAGS: 3
};
export const USER_ROLES = {
    FREE_ROLE: 0,
    MEMBER_ROLE: 1,
    ADMIN_ROLE: 2
};
export const ELEMENTS_PER_PAGE = 10;
export const INVOICE_OWNER = {
    USER: 0,
    ORGANIZATION: 1
};
export const DEFAULT_CURRENCY = 'EUR';
export const MIN_BITCOIN_AMOUNT = 500;
export const BTC_DONATION_ADDRESS = '1Q1nhq1NbxPYAbw1BppwKbCqg58ZqMb9A8';
export const AUTH_LOG_EVENTS = {
    LOGIN_FAILURE_PASSWORD: 0,
    LOGIN_SUCCESS: 1,
    LOGOUT: 2,
    LOGIN_FAILURE_2FA: 3,
    LOGIN_SUCCESS_AWAIT_2FA: 4
};
export const INVOICE_TYPE = {
    OTHER: 0,
    SUBSCRIPTION: 1,
    CANCELLATION: 2,
    CREDIT: 3,
    DONATION: 4
};
export const INVOICE_STATE = {
    UNPAID: 0,
    PAID: 1,
    VOID: 2,
    BILLED: 3
};

export const BASE_SIZE = 1024;
export const GIGA = BASE_SIZE ** 3;

export const LOGS_STATE = {
    DISABLE: 0,
    BASIC: 1,
    ADVANCED: 2
};

export const MEMBER_TYPE = {
    MEMBER: 0,
    SUB_MEMBER: 1
};

export const DOMAIN_STATE = {
    DOMAIN_STATE_DEFAULT: 0, // Domain's State before verify or after deactivation
    DOMAIN_STATE_ACTIVE: 1, // active once verified
    DOMAIN_STATE_WARN: 2 // detected backward DNS change after ACTIVE
};

export const VERIFY_STATE = {
    VERIFY_STATE_DEFAULT: 0, // 0 is default, no good
    VERIFY_STATE_EXIST: 1, // 1 is has code but doesn't match DB's, no good
    VERIFY_STATE_GOOD: 2 // 2 is has code and matches DB's, good!
};

export const MX_STATE = {
    MX_STATE_DEFAULT: 0, // 0 is default, no good
    MX_STATE_NO_US: 1, // 1 is set but does not have us
    MX_STATE_INC_US: 2, // 2 is includes our MX but priority no good
    MX_STATE_GOOD: 3 // 3 is includes our MX and we are highest and pri is legit, good!
};

export const SPF_STATE = {
    SPF_STATE_DEFAULT: 0, // 0 is default, no spf record
    SPF_STATE_ONE: 1, // 1 is has spf record but not us
    SPF_STATE_MULT: 2, // 2 is has multiple spf records, no good
    SPF_STATE_GOOD: 3 // 3 is has spf record and includes us, good!
};

export const DKIM_STATE = {
    DKIM_STATE_DEFAULT: 0, // 0 is default, no dkim record
    DKIM_STATE_ONE: 1, // 1 is found entries but format wrong
    DKIM_STATE_MULT: 2, // 2 is multiple dkim records, no good
    DKIM_STATE_CHECK: 3, // 3 is proper entry, but did not check or did not match DB's public key
    DKIM_STATE_GOOD: 4, // 4 is proper entry and matches DB's public key, good!
    DKIM_STATE_OFF: 5 // 5 is User wants to stop signing with dkim
};

export const DMARC_STATE = {
    DMARC_STATE_DEFAULT: 0, // 0 is default, no dmarc record
    DMARC_STATE_ONE: 1, // 1 is found entries but format wrong
    DMARC_STATE_MULT: 2, // 2 is multiple dmarc records, no good
    DMARC_STATE_GOOD: 3 // 3 is good!
};

export const ADDRESS_STATUS = {
    STATUS_DISABLED: 0,
    STATUS_ENABLED: 1
};

export const ADDRESS_TYPE = {
    TYPE_ORIGINAL: 1,
    TYPE_ALIAS: 2,
    TYPE_CUSTOM_DOMAIN: 3,
    TYPE_PREMIUM: 4
};

export const RECEIVE_ADDRESS = {
    RECEIVE_YES: 1,
    RECEIVE_NO: 0
};

export const SEND_ADDRESS = {
    SEND_YES: 1,
    SEND_NO: 0
};

export const MEMBER_PRIVATE = {
    READABLE: 0,
    UNREADABLE: 1
};

export const MEMBER_ROLE = {
    ORGANIZATION_NONE: 0,
    ORGANIZATION_MEMBER: 1,
    ORGANIZATION_OWNER: 2
};

export const PACKAGE_TYPE = {
    SEND_PM: 1,
    SEND_EO: 2,
    SEND_CLEAR: 4,
    SEND_PGP_INLINE: 8,
    SEND_PGP_MIME: 16
};

export const SHOW_IMAGES = {
    REMOTE: 1,
    EMBEDDED: 2
};

export const COMPOSER_MODE = {
    POPUP: 0,
    MAXIMIZED: 1
};
<commit_msg>Add few constants for payment<commit_after>export const MAIN_USER_KEY = 'USER_KEYS';
export const SECURE_SESSION_STORAGE_KEY = 'SECURE';
export const MAILBOX_PASSWORD_KEY = 'proton:mailbox_pwd';
export const UID_KEY = 'proton:UID';
export const INTERVAL_EVENT_TIMER = 30 * 1000;
export const EVENT_ACTIONS = {
    DELETE: 0,
    CREATE: 1,
    UPDATE: 2,
    UPDATE_DRAFT: 2,
    UPDATE_FLAGS: 3
};
export const USER_ROLES = {
    FREE_ROLE: 0,
    MEMBER_ROLE: 1,
    ADMIN_ROLE: 2
};
export const ELEMENTS_PER_PAGE = 10;
export const INVOICE_OWNER = {
    USER: 0,
    ORGANIZATION: 1
};
export const DEFAULT_CURRENCY = 'EUR';
export const MIN_BITCOIN_AMOUNT = 500;
export const BTC_DONATION_ADDRESS = '1Q1nhq1NbxPYAbw1BppwKbCqg58ZqMb9A8';
export const AUTH_LOG_EVENTS = {
    LOGIN_FAILURE_PASSWORD: 0,
    LOGIN_SUCCESS: 1,
    LOGOUT: 2,
    LOGIN_FAILURE_2FA: 3,
    LOGIN_SUCCESS_AWAIT_2FA: 4
};
export const INVOICE_TYPE = {
    OTHER: 0,
    SUBSCRIPTION: 1,
    CANCELLATION: 2,
    CREDIT: 3,
    DONATION: 4
};
export const INVOICE_STATE = {
    UNPAID: 0,
    PAID: 1,
    VOID: 2,
    BILLED: 3
};

export const BASE_SIZE = 1024;
export const GIGA = BASE_SIZE ** 3;

export const LOGS_STATE = {
    DISABLE: 0,
    BASIC: 1,
    ADVANCED: 2
};

export const MEMBER_TYPE = {
    MEMBER: 0,
    SUB_MEMBER: 1
};

export const DOMAIN_STATE = {
    DOMAIN_STATE_DEFAULT: 0, // Domain's State before verify or after deactivation
    DOMAIN_STATE_ACTIVE: 1, // active once verified
    DOMAIN_STATE_WARN: 2 // detected backward DNS change after ACTIVE
};

export const VERIFY_STATE = {
    VERIFY_STATE_DEFAULT: 0, // 0 is default, no good
    VERIFY_STATE_EXIST: 1, // 1 is has code but doesn't match DB's, no good
    VERIFY_STATE_GOOD: 2 // 2 is has code and matches DB's, good!
};

export const MX_STATE = {
    MX_STATE_DEFAULT: 0, // 0 is default, no good
    MX_STATE_NO_US: 1, // 1 is set but does not have us
    MX_STATE_INC_US: 2, // 2 is includes our MX but priority no good
    MX_STATE_GOOD: 3 // 3 is includes our MX and we are highest and pri is legit, good!
};

export const SPF_STATE = {
    SPF_STATE_DEFAULT: 0, // 0 is default, no spf record
    SPF_STATE_ONE: 1, // 1 is has spf record but not us
    SPF_STATE_MULT: 2, // 2 is has multiple spf records, no good
    SPF_STATE_GOOD: 3 // 3 is has spf record and includes us, good!
};

export const DKIM_STATE = {
    DKIM_STATE_DEFAULT: 0, // 0 is default, no dkim record
    DKIM_STATE_ONE: 1, // 1 is found entries but format wrong
    DKIM_STATE_MULT: 2, // 2 is multiple dkim records, no good
    DKIM_STATE_CHECK: 3, // 3 is proper entry, but did not check or did not match DB's public key
    DKIM_STATE_GOOD: 4, // 4 is proper entry and matches DB's public key, good!
    DKIM_STATE_OFF: 5 // 5 is User wants to stop signing with dkim
};

export const DMARC_STATE = {
    DMARC_STATE_DEFAULT: 0, // 0 is default, no dmarc record
    DMARC_STATE_ONE: 1, // 1 is found entries but format wrong
    DMARC_STATE_MULT: 2, // 2 is multiple dmarc records, no good
    DMARC_STATE_GOOD: 3 // 3 is good!
};

export const ADDRESS_STATUS = {
    STATUS_DISABLED: 0,
    STATUS_ENABLED: 1
};

export const ADDRESS_TYPE = {
    TYPE_ORIGINAL: 1,
    TYPE_ALIAS: 2,
    TYPE_CUSTOM_DOMAIN: 3,
    TYPE_PREMIUM: 4
};

export const RECEIVE_ADDRESS = {
    RECEIVE_YES: 1,
    RECEIVE_NO: 0
};

export const SEND_ADDRESS = {
    SEND_YES: 1,
    SEND_NO: 0
};

export const MEMBER_PRIVATE = {
    READABLE: 0,
    UNREADABLE: 1
};

export const MEMBER_ROLE = {
    ORGANIZATION_NONE: 0,
    ORGANIZATION_MEMBER: 1,
    ORGANIZATION_OWNER: 2
};

export const PACKAGE_TYPE = {
    SEND_PM: 1,
    SEND_EO: 2,
    SEND_CLEAR: 4,
    SEND_PGP_INLINE: 8,
    SEND_PGP_MIME: 16
};

export const SHOW_IMAGES = {
    REMOTE: 1,
    EMBEDDED: 2
};

export const COMPOSER_MODE = {
    POPUP: 0,
    MAXIMIZED: 1
};

export const DEFAULT_CYCLE = 12;

export const CYCLE = {
    MONTHLY: 1,
    YEARLY: 12,
    TWO_YEARS: 24
};

export const BUNDLE_COUPON_CODE = 'BUNDLE';
const BLACK_FRIDAY_YEAR = 2018;
export const BLACK_FRIDAY = {
    YEAR: BLACK_FRIDAY_YEAR,
    COUPON_CODE: 'TWO4ONE2018',
    CYCLE: CYCLE.TWO_YEARS,
    BETWEEN: {
        START: `${BLACK_FRIDAY_YEAR}-11-23`,
        END: `${BLACK_FRIDAY_YEAR}-11-26`
    },
    INTERVAL: 10 * 60 * 1000
};
export const MIN_PAYPAL_AMOUNT = 500;
export const MAX_PAYPAL_AMOUNT = 99999900;
<|endoftext|>


<commit_before><commit_msg>Fancy graphics should be enabled at graphics=1<commit_after><|endoftext|>


<commit_before>/*
element focus
*/
export default {
    bind: function (el, { value }, vnode) {
        if (value) {
            const { focus } = value;
            if (focus) {
                vnode.context.$nextTick(() => {
                    el.focus();
                });
            }
        }
    },
    update: function (el, { value }) {
        if (value) {
            const { focus } = value;
            if (focus) {
                el.focus();
            }
        }
    },
};
<commit_msg>Update focus.js<commit_after>/*
element focus
*/
export default {
    bind: function (el, { value }, vnode) {
        if (value) {
            const { focus, select } = value;

            vnode.context.$nextTick(() => {
                if (focus) {
                    el.focus();
                }

                if (select) {
                    el.select();
                }
            });
        }
    },
    update: function (el, { value }) {
        if (value) {
            const { focus, select } = value;
            if (focus) {
                el.focus();
            }

            if (select) {
                el.select();
            }
        }
    },
};
<|endoftext|>


<commit_before>/*
 * grunt-link-html
 * https://github.com/pythonandchips/grunt-link-html
 *
 * Copyright (c) 2013 Colin Gemmell
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

  grunt.registerMultiTask('link_html', 'link css and javascipt in html file', function() {

    var options = this.options({
      expand: true,
      cwd: '.'
    });

    //TODO: test this method away from the rest of the code. not sure how to do that yet.
    var includes = function(template, path, options){
      var cssFiles = grunt.file.expand(options, path);
      var cssIncludes = cssFiles.map( function(file) {
        return grunt.template.process(template, {data: {file: file}});
      });
      cssIncludes.unshift('');
      cssIncludes = cssIncludes.join('\n') + '\n';
      return cssIncludes;
    };

    //TODO: test this method away from the rest of the code. not sure how to do that yet.
    var processIncludes = function(filepath, content, fileType, files) {
      var begin = content.html.match(new RegExp('<!--\\s*begin:' + fileType + '\\s*-->'));
      var skip = false;
      if(begin){
      var end = content.html.match(new RegExp('<!--\\s*end:' + fileType + '\\s*-->')).index;
      var startReplace = begin.index + begin[0].length;
      content.html = content.html.substring(0, startReplace) + files + content.html.substring(end, content.html.length);
      } else {
        skip = true;
        grunt.log.ok('Skipping ' + fileType  + ' for '+filepath);
      }
      return skip;
    };

    var cssIncludes = includes('<link rel="stylesheet" type="text/css" href="<%= file %>">', this.data.cssFiles, options);
    var jsIncludes = includes('<script src="<%= file %>"></script>', this.data.jsFiles, options);
    var targetFiles = grunt.file.expand(options, this.data.targetHtml);

    targetFiles.forEach( function(filepath) {
      var content = {html: grunt.file.read(options.cwd + '/' + filepath)};
      var skipCss = processIncludes(filepath, content, 'css', cssIncludes);
      var skipJs = processIncludes(filepath, content, 'js', jsIncludes);
      if(!skipJs && !skipCss){
        grunt.log.ok('Writing file '+ filepath);
        grunt.file.write(options.cwd + '/' + filepath, content.html);
      }
    });

  });

};
<commit_msg>only skip writing if *neither* css or js were found<commit_after>/*
 * grunt-link-html
 * https://github.com/pythonandchips/grunt-link-html
 *
 * Copyright (c) 2013 Colin Gemmell
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

  grunt.registerMultiTask('link_html', 'link css and javascipt in html file', function() {

    var options = this.options({
      expand: true,
      cwd: '.'
    });

    //TODO: test this method away from the rest of the code. not sure how to do that yet.
    var includes = function(template, path, options){
      var cssFiles = grunt.file.expand(options, path);
      var cssIncludes = cssFiles.map( function(file) {
        return grunt.template.process(template, {data: {file: file}});
      });
      cssIncludes.unshift('');
      cssIncludes = cssIncludes.join('\n') + '\n';
      return cssIncludes;
    };

    //TODO: test this method away from the rest of the code. not sure how to do that yet.
    var processIncludes = function(filepath, content, fileType, files) {
      var begin = content.html.match(new RegExp('<!--\\s*begin:' + fileType + '\\s*-->'));
      var skip = false;
      if(begin){
      var end = content.html.match(new RegExp('<!--\\s*end:' + fileType + '\\s*-->')).index;
      var startReplace = begin.index + begin[0].length;
      content.html = content.html.substring(0, startReplace) + files + content.html.substring(end, content.html.length);
      } else {
        skip = true;
        grunt.log.ok('Skipping ' + fileType  + ' for '+filepath);
      }
      return skip;
    };

    var cssIncludes = includes('<link rel="stylesheet" type="text/css" href="<%= file %>">', this.data.cssFiles, options);
    var jsIncludes = includes('<script src="<%= file %>"></script>', this.data.jsFiles, options);
    var targetFiles = grunt.file.expand(options, this.data.targetHtml);

    targetFiles.forEach( function(filepath) {
      var content = {html: grunt.file.read(options.cwd + '/' + filepath)};
      var skipCss = processIncludes(filepath, content, 'css', cssIncludes);
      var skipJs = processIncludes(filepath, content, 'js', jsIncludes);
      if(!(skipJs && skipCss)){
        grunt.log.ok('Writing file '+ filepath);
        grunt.file.write(options.cwd + '/' + filepath, content.html);
      }
    });

  });

};
<|endoftext|>