<commit_before>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
"use strict";

const { SyncBailHook } = require("tapable");
const { RawSource } = require("webpack-sources");
const Template = require("./Template");
const ModuleHotAcceptDependency = require("./dependencies/ModuleHotAcceptDependency");
const ModuleHotDeclineDependency = require("./dependencies/ModuleHotDeclineDependency");
const ConstDependency = require("./dependencies/ConstDependency");
const NullFactory = require("./NullFactory");
const ParserHelpers = require("./ParserHelpers");

module.exports = class HotModuleReplacementPlugin {
	constructor(options) {
		this.options = options || {};
		this.multiStep = this.options.multiStep;
		this.fullBuildTimeout = this.options.fullBuildTimeout || 200;
		this.requestTimeout = this.options.requestTimeout || 10000;
	}

	apply(compiler) {
		const multiStep = this.multiStep;
		const fullBuildTimeout = this.fullBuildTimeout;
		const requestTimeout = this.requestTimeout;
		const hotUpdateChunkFilename =
			compiler.options.output.hotUpdateChunkFilename;
		const hotUpdateMainFilename = compiler.options.output.hotUpdateMainFilename;
		compiler.hooks.additionalPass.tapAsync(
			"HotModuleReplacementPlugin",
			callback => {
				if (multiStep) return setTimeout(callback, fullBuildTimeout);
				return callback();
			}
		);
		compiler.hooks.compilation.tap(
			"HotModuleReplacementPlugin",
			(compilation, { normalModuleFactory }) => {
				const hotUpdateChunkTemplate = compilation.hotUpdateChunkTemplate;
				if (!hotUpdateChunkTemplate) return;

				compilation.dependencyFactories.set(ConstDependency, new NullFactory());
				compilation.dependencyTemplates.set(
					ConstDependency,
					new ConstDependency.Template()
				);

				compilation.dependencyFactories.set(
					ModuleHotAcceptDependency,
					normalModuleFactory
				);
				compilation.dependencyTemplates.set(
					ModuleHotAcceptDependency,
					new ModuleHotAcceptDependency.Template()
				);

				compilation.dependencyFactories.set(
					ModuleHotDeclineDependency,
					normalModuleFactory
				);
				compilation.dependencyTemplates.set(
					ModuleHotDeclineDependency,
					new ModuleHotDeclineDependency.Template()
				);

				compilation.hooks.record.tap(
					"HotModuleReplacementPlugin",
					(compilation, records) => {
						if (records.hash === compilation.hash) return;
						records.hash = compilation.hash;
						records.moduleHashs = {};
						for (const module of compilation.modules) {
							const identifier = module.identifier();
							records.moduleHashs[identifier] = module.hash;
						}
						records.chunkHashs = {};
						for (const chunk of compilation.chunks) {
							records.chunkHashs[chunk.id] = chunk.hash;
						}
						records.chunkModuleIds = {};
						for (const chunk of compilation.chunks) {
							records.chunkModuleIds[chunk.id] = Array.from(
								chunk.modulesIterable,
								m => m.id
							);
						}
					}
				);
				let initialPass = false;
				let recompilation = false;
				compilation.hooks.afterHash.tap("HotModuleReplacementPlugin", () => {
					let records = compilation.records;
					if (!records) {
						initialPass = true;
						return;
					}
					if (!records.hash) initialPass = true;
					const preHash = records.preHash || "x";
					const prepreHash = records.prepreHash || "x";
					if (preHash === compilation.hash) {
						recompilation = true;
						compilation.modifyHash(prepreHash);
						return;
					}
					records.prepreHash = records.hash || "x";
					records.preHash = compilation.hash;
					compilation.modifyHash(records.prepreHash);
				});
				compilation.hooks.shouldGenerateChunkAssets.tap(
					"HotModuleReplacementPlugin",
					() => {
						if (multiStep && !recompilation && !initialPass) return false;
					}
				);
				compilation.hooks.needAdditionalPass.tap(
					"HotModuleReplacementPlugin",
					() => {
						if (multiStep && !recompilation && !initialPass) return true;
					}
				);
				compilation.hooks.additionalChunkAssets.tap(
					"HotModuleReplacementPlugin",
					() => {
						const records = compilation.records;
						if (records.hash === compilation.hash) return;
						if (
							!records.moduleHashs ||
							!records.chunkHashs ||
							!records.chunkModuleIds
						)
							return;
						for (const module of compilation.modules) {
							const identifier = module.identifier();
							let hash = module.hash;
							module.hotUpdate = records.moduleHashs[identifier] !== hash;
						}
						const hotUpdateMainContent = {
							h: compilation.hash,
							c: {}
						};
						for (const key of Object.keys(records.chunkHashs)) {
							const chunkId = isNaN(+key) ? key : +key;
							const currentChunk = compilation.chunks.find(
								chunk => chunk.id === chunkId
							);
							if (currentChunk) {
								const newModules = currentChunk
									.getModules()
									.filter(module => module.hotUpdate);
								const allModules = new Set();
								for (const module of currentChunk.modulesIterable) {
									allModules.add(module.id);
								}
								const removedModules = records.chunkModuleIds[chunkId].filter(
									id => !allModules.has(id)
								);
								if (newModules.length > 0 || removedModules.length > 0) {
									const source = hotUpdateChunkTemplate.render(
										chunkId,
										newModules,
										removedModules,
										compilation.hash,
										compilation.moduleTemplates.javascript,
										compilation.dependencyTemplates
									);
									const filename = compilation.getPath(hotUpdateChunkFilename, {
										hash: records.hash,
										chunk: currentChunk
									});
									compilation.additionalChunkAssets.push(filename);
									compilation.assets[filename] = source;
									hotUpdateMainContent.c[chunkId] = true;
									currentChunk.files.push(filename);
									compilation.hooks.chunkAsset.call(
										"HotModuleReplacementPlugin",
										currentChunk,
										filename
									);
								}
							} else {
								hotUpdateMainContent.c[chunkId] = false;
							}
						}
						const source = new RawSource(JSON.stringify(hotUpdateMainContent));
						const filename = compilation.getPath(hotUpdateMainFilename, {
							hash: records.hash
						});
						compilation.assets[filename] = source;
					}
				);

				const mainTemplate = compilation.mainTemplate;

				mainTemplate.hooks.hash.tap("HotModuleReplacementPlugin", hash => {
					hash.update("HotMainTemplateDecorator");
				});

				mainTemplate.hooks.moduleRequire.tap(
					"HotModuleReplacementPlugin",
					(_, chunk, hash, varModuleId) => {
						return `hotCreateRequire(${varModuleId})`;
					}
				);

				mainTemplate.hooks.requireExtensions.tap(
					"HotModuleReplacementPlugin",
					source => {
						const buf = [source];
						buf.push("");
						buf.push("// __webpack_hash__");
						buf.push(
							mainTemplate.requireFn +
								".h = function() { return hotCurrentHash; };"
						);
						return Template.asString(buf);
					}
				);

				const needChunkLoadingCode = chunk => {
					for (const chunkGroup of chunk.groupsIterable) {
						if (chunkGroup.chunks.length > 1) return true;
						if (chunkGroup.getNumberOfChildren() > 0) return true;
					}
					return false;
				};

				mainTemplate.hooks.bootstrap.tap(
					"HotModuleReplacementPlugin",
					(source, chunk, hash) => {
						source = mainTemplate.hooks.hotBootstrap.call(source, chunk, hash);
						return Template.asString([
							source,
							"",
							hotInitCode
								.replace(/\$require\$/g, mainTemplate.requireFn)
								.replace(/\$hash\$/g, JSON.stringify(hash))
								.replace(/\$requestTimeout\$/g, requestTimeout)
								.replace(
									/\/\*foreachInstalledChunks\*\//g,
									needChunkLoadingCode(chunk)
										? "for(var chunkId in installedChunks)"
										: `var chunkId = ${JSON.stringify(chunk.id)};`
								)
						]);
					}
				);

				mainTemplate.hooks.globalHash.tap(
					"HotModuleReplacementPlugin",
					() => true
				);

				mainTemplate.hooks.currentHash.tap(
					"HotModuleReplacementPlugin",
					(_, length) => {
						if (isFinite(length)) {
							return `hotCurrentHash.substr(0, ${length})`;
						} else {
							return "hotCurrentHash";
						}
					}
				);

				mainTemplate.hooks.moduleObj.tap(
					"HotModuleReplacementPlugin",
					(source, chunk, hash, varModuleId) => {
						return Template.asString([
							`${source},`,
							`hot: hotCreateModule(${varModuleId}),`,
							"parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),",
							"children: []"
						]);
					}
				);

				const handler = (parser, parserOptions) => {
					parser.hooks.expression
						.for("__webpack_hash__")
						.tap(
							"HotModuleReplacementPlugin",
							ParserHelpers.toConstantDependencyWithWebpackRequire(
								parser,
								"__webpack_require__.h()"
							)
						);
					parser.hooks.evaluateTypeof
						.for("__webpack_hash__")
						.tap(
							"HotModuleReplacementPlugin",
							ParserHelpers.evaluateToString("string")
						);
					parser.hooks.evaluateIdentifier.for("module.hot").tap(
						{
							name: "HotModuleReplacementPlugin",
							before: "NodeStuffPlugin"
						},
						expr => {
							return ParserHelpers.evaluateToIdentifier(
								"module.hot",
								!!parser.state.compilation.hotUpdateChunkTemplate
							)(expr);
						}
					);
					// TODO webpack 5: refactor this, no custom hooks
					if (!parser.hooks.hotAcceptCallback) {
						parser.hooks.hotAcceptCallback = new SyncBailHook([
							"expression",
							"requests"
						]);
					}
					if (!parser.hooks.hotAcceptWithoutCallback) {
						parser.hooks.hotAcceptWithoutCallback = new SyncBailHook([
							"expression",
							"requests"
						]);
					}
					parser.hooks.call
						.for("module.hot.accept")
						.tap("HotModuleReplacementPlugin", expr => {
							if (!parser.state.compilation.hotUpdateChunkTemplate) {
								return false;
							}
							if (expr.arguments.length >= 1) {
								const arg = parser.evaluateExpression(expr.arguments[0]);
								let params = [];
								let requests = [];
								if (arg.isString()) {
									params = [arg];
								} else if (arg.isArray()) {
									params = arg.items.filter(param => param.isString());
								}
								if (params.length > 0) {
									params.forEach((param, idx) => {
										const request = param.string;
										const dep = new ModuleHotAcceptDependency(
											request,
											param.range
										);
										dep.optional = true;
										dep.loc = Object.create(expr.loc);
										dep.loc.index = idx;
										parser.state.module.addDependency(dep);
										requests.push(request);
									});
									if (expr.arguments.length > 1) {
										parser.hooks.hotAcceptCallback.call(
											expr.arguments[1],
											requests
										);
										parser.walkExpression(expr.arguments[1]); // other args are ignored
										return true;
									} else {
										parser.hooks.hotAcceptWithoutCallback.call(expr, requests);
										return true;
									}
								}
							}
						});
					parser.hooks.call
						.for("module.hot.decline")
						.tap("HotModuleReplacementPlugin", expr => {
							if (!parser.state.compilation.hotUpdateChunkTemplate) {
								return false;
							}
							if (expr.arguments.length === 1) {
								const arg = parser.evaluateExpression(expr.arguments[0]);
								let params = [];
								if (arg.isString()) {
									params = [arg];
								} else if (arg.isArray()) {
									params = arg.items.filter(param => param.isString());
								}
								params.forEach((param, idx) => {
									const dep = new ModuleHotDeclineDependency(
										param.string,
										param.range
									);
									dep.optional = true;
									dep.loc = Object.create(expr.loc);
									dep.loc.index = idx;
									parser.state.module.addDependency(dep);
								});
							}
						});
					parser.hooks.expression
						.for("module.hot")
						.tap("HotModuleReplacementPlugin", ParserHelpers.skipTraversal);
				};

				// TODO add HMR support for javascript/esm
				normalModuleFactory.hooks.parser
					.for("javascript/auto")
					.tap("HotModuleReplacementPlugin", handler);
				normalModuleFactory.hooks.parser
					.for("javascript/dynamic")
					.tap("HotModuleReplacementPlugin", handler);

				compilation.hooks.normalModuleLoader.tap(
					"HotModuleReplacementPlugin",
					context => {
						context.hot = true;
					}
				);
			}
		);
	}
};

const hotInitCode = Template.getFunctionContent(
	require("./HotModuleReplacement.runtime.js")
);
<commit_msg>fix memory leak with HMR<commit_after>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
"use strict";

const { SyncBailHook } = require("tapable");
const { RawSource } = require("webpack-sources");
const Template = require("./Template");
const ModuleHotAcceptDependency = require("./dependencies/ModuleHotAcceptDependency");
const ModuleHotDeclineDependency = require("./dependencies/ModuleHotDeclineDependency");
const ConstDependency = require("./dependencies/ConstDependency");
const NullFactory = require("./NullFactory");
const ParserHelpers = require("./ParserHelpers");

module.exports = class HotModuleReplacementPlugin {
	constructor(options) {
		this.options = options || {};
		this.multiStep = this.options.multiStep;
		this.fullBuildTimeout = this.options.fullBuildTimeout || 200;
		this.requestTimeout = this.options.requestTimeout || 10000;
	}

	apply(compiler) {
		const multiStep = this.multiStep;
		const fullBuildTimeout = this.fullBuildTimeout;
		const requestTimeout = this.requestTimeout;
		const hotUpdateChunkFilename =
			compiler.options.output.hotUpdateChunkFilename;
		const hotUpdateMainFilename = compiler.options.output.hotUpdateMainFilename;
		compiler.hooks.additionalPass.tapAsync(
			"HotModuleReplacementPlugin",
			callback => {
				if (multiStep) return setTimeout(callback, fullBuildTimeout);
				return callback();
			}
		);

		const addParserPlugins = (parser, parserOptions) => {
			parser.hooks.expression
				.for("__webpack_hash__")
				.tap(
					"HotModuleReplacementPlugin",
					ParserHelpers.toConstantDependencyWithWebpackRequire(
						parser,
						"__webpack_require__.h()"
					)
				);
			parser.hooks.evaluateTypeof
				.for("__webpack_hash__")
				.tap(
					"HotModuleReplacementPlugin",
					ParserHelpers.evaluateToString("string")
				);
			parser.hooks.evaluateIdentifier.for("module.hot").tap(
				{
					name: "HotModuleReplacementPlugin",
					before: "NodeStuffPlugin"
				},
				expr => {
					return ParserHelpers.evaluateToIdentifier(
						"module.hot",
						!!parser.state.compilation.hotUpdateChunkTemplate
					)(expr);
				}
			);
			// TODO webpack 5: refactor this, no custom hooks
			if (!parser.hooks.hotAcceptCallback) {
				parser.hooks.hotAcceptCallback = new SyncBailHook([
					"expression",
					"requests"
				]);
			}
			if (!parser.hooks.hotAcceptWithoutCallback) {
				parser.hooks.hotAcceptWithoutCallback = new SyncBailHook([
					"expression",
					"requests"
				]);
			}
			parser.hooks.call
				.for("module.hot.accept")
				.tap("HotModuleReplacementPlugin", expr => {
					if (!parser.state.compilation.hotUpdateChunkTemplate) {
						return false;
					}
					if (expr.arguments.length >= 1) {
						const arg = parser.evaluateExpression(expr.arguments[0]);
						let params = [];
						let requests = [];
						if (arg.isString()) {
							params = [arg];
						} else if (arg.isArray()) {
							params = arg.items.filter(param => param.isString());
						}
						if (params.length > 0) {
							params.forEach((param, idx) => {
								const request = param.string;
								const dep = new ModuleHotAcceptDependency(request, param.range);
								dep.optional = true;
								dep.loc = Object.create(expr.loc);
								dep.loc.index = idx;
								parser.state.module.addDependency(dep);
								requests.push(request);
							});
							if (expr.arguments.length > 1) {
								parser.hooks.hotAcceptCallback.call(
									expr.arguments[1],
									requests
								);
								parser.walkExpression(expr.arguments[1]); // other args are ignored
								return true;
							} else {
								parser.hooks.hotAcceptWithoutCallback.call(expr, requests);
								return true;
							}
						}
					}
				});
			parser.hooks.call
				.for("module.hot.decline")
				.tap("HotModuleReplacementPlugin", expr => {
					if (!parser.state.compilation.hotUpdateChunkTemplate) {
						return false;
					}
					if (expr.arguments.length === 1) {
						const arg = parser.evaluateExpression(expr.arguments[0]);
						let params = [];
						if (arg.isString()) {
							params = [arg];
						} else if (arg.isArray()) {
							params = arg.items.filter(param => param.isString());
						}
						params.forEach((param, idx) => {
							const dep = new ModuleHotDeclineDependency(
								param.string,
								param.range
							);
							dep.optional = true;
							dep.loc = Object.create(expr.loc);
							dep.loc.index = idx;
							parser.state.module.addDependency(dep);
						});
					}
				});
			parser.hooks.expression
				.for("module.hot")
				.tap("HotModuleReplacementPlugin", ParserHelpers.skipTraversal);
		};

		compiler.hooks.compilation.tap(
			"HotModuleReplacementPlugin",
			(compilation, { normalModuleFactory }) => {
				const hotUpdateChunkTemplate = compilation.hotUpdateChunkTemplate;
				if (!hotUpdateChunkTemplate) return;

				compilation.dependencyFactories.set(ConstDependency, new NullFactory());
				compilation.dependencyTemplates.set(
					ConstDependency,
					new ConstDependency.Template()
				);

				compilation.dependencyFactories.set(
					ModuleHotAcceptDependency,
					normalModuleFactory
				);
				compilation.dependencyTemplates.set(
					ModuleHotAcceptDependency,
					new ModuleHotAcceptDependency.Template()
				);

				compilation.dependencyFactories.set(
					ModuleHotDeclineDependency,
					normalModuleFactory
				);
				compilation.dependencyTemplates.set(
					ModuleHotDeclineDependency,
					new ModuleHotDeclineDependency.Template()
				);

				compilation.hooks.record.tap(
					"HotModuleReplacementPlugin",
					(compilation, records) => {
						if (records.hash === compilation.hash) return;
						records.hash = compilation.hash;
						records.moduleHashs = {};
						for (const module of compilation.modules) {
							const identifier = module.identifier();
							records.moduleHashs[identifier] = module.hash;
						}
						records.chunkHashs = {};
						for (const chunk of compilation.chunks) {
							records.chunkHashs[chunk.id] = chunk.hash;
						}
						records.chunkModuleIds = {};
						for (const chunk of compilation.chunks) {
							records.chunkModuleIds[chunk.id] = Array.from(
								chunk.modulesIterable,
								m => m.id
							);
						}
					}
				);
				let initialPass = false;
				let recompilation = false;
				compilation.hooks.afterHash.tap("HotModuleReplacementPlugin", () => {
					let records = compilation.records;
					if (!records) {
						initialPass = true;
						return;
					}
					if (!records.hash) initialPass = true;
					const preHash = records.preHash || "x";
					const prepreHash = records.prepreHash || "x";
					if (preHash === compilation.hash) {
						recompilation = true;
						compilation.modifyHash(prepreHash);
						return;
					}
					records.prepreHash = records.hash || "x";
					records.preHash = compilation.hash;
					compilation.modifyHash(records.prepreHash);
				});
				compilation.hooks.shouldGenerateChunkAssets.tap(
					"HotModuleReplacementPlugin",
					() => {
						if (multiStep && !recompilation && !initialPass) return false;
					}
				);
				compilation.hooks.needAdditionalPass.tap(
					"HotModuleReplacementPlugin",
					() => {
						if (multiStep && !recompilation && !initialPass) return true;
					}
				);
				compilation.hooks.additionalChunkAssets.tap(
					"HotModuleReplacementPlugin",
					() => {
						const records = compilation.records;
						if (records.hash === compilation.hash) return;
						if (
							!records.moduleHashs ||
							!records.chunkHashs ||
							!records.chunkModuleIds
						)
							return;
						for (const module of compilation.modules) {
							const identifier = module.identifier();
							let hash = module.hash;
							module.hotUpdate = records.moduleHashs[identifier] !== hash;
						}
						const hotUpdateMainContent = {
							h: compilation.hash,
							c: {}
						};
						for (const key of Object.keys(records.chunkHashs)) {
							const chunkId = isNaN(+key) ? key : +key;
							const currentChunk = compilation.chunks.find(
								chunk => chunk.id === chunkId
							);
							if (currentChunk) {
								const newModules = currentChunk
									.getModules()
									.filter(module => module.hotUpdate);
								const allModules = new Set();
								for (const module of currentChunk.modulesIterable) {
									allModules.add(module.id);
								}
								const removedModules = records.chunkModuleIds[chunkId].filter(
									id => !allModules.has(id)
								);
								if (newModules.length > 0 || removedModules.length > 0) {
									const source = hotUpdateChunkTemplate.render(
										chunkId,
										newModules,
										removedModules,
										compilation.hash,
										compilation.moduleTemplates.javascript,
										compilation.dependencyTemplates
									);
									const filename = compilation.getPath(hotUpdateChunkFilename, {
										hash: records.hash,
										chunk: currentChunk
									});
									compilation.additionalChunkAssets.push(filename);
									compilation.assets[filename] = source;
									hotUpdateMainContent.c[chunkId] = true;
									currentChunk.files.push(filename);
									compilation.hooks.chunkAsset.call(
										"HotModuleReplacementPlugin",
										currentChunk,
										filename
									);
								}
							} else {
								hotUpdateMainContent.c[chunkId] = false;
							}
						}
						const source = new RawSource(JSON.stringify(hotUpdateMainContent));
						const filename = compilation.getPath(hotUpdateMainFilename, {
							hash: records.hash
						});
						compilation.assets[filename] = source;
					}
				);

				const mainTemplate = compilation.mainTemplate;

				mainTemplate.hooks.hash.tap("HotModuleReplacementPlugin", hash => {
					hash.update("HotMainTemplateDecorator");
				});

				mainTemplate.hooks.moduleRequire.tap(
					"HotModuleReplacementPlugin",
					(_, chunk, hash, varModuleId) => {
						return `hotCreateRequire(${varModuleId})`;
					}
				);

				mainTemplate.hooks.requireExtensions.tap(
					"HotModuleReplacementPlugin",
					source => {
						const buf = [source];
						buf.push("");
						buf.push("// __webpack_hash__");
						buf.push(
							mainTemplate.requireFn +
								".h = function() { return hotCurrentHash; };"
						);
						return Template.asString(buf);
					}
				);

				const needChunkLoadingCode = chunk => {
					for (const chunkGroup of chunk.groupsIterable) {
						if (chunkGroup.chunks.length > 1) return true;
						if (chunkGroup.getNumberOfChildren() > 0) return true;
					}
					return false;
				};

				mainTemplate.hooks.bootstrap.tap(
					"HotModuleReplacementPlugin",
					(source, chunk, hash) => {
						source = mainTemplate.hooks.hotBootstrap.call(source, chunk, hash);
						return Template.asString([
							source,
							"",
							hotInitCode
								.replace(/\$require\$/g, mainTemplate.requireFn)
								.replace(/\$hash\$/g, JSON.stringify(hash))
								.replace(/\$requestTimeout\$/g, requestTimeout)
								.replace(
									/\/\*foreachInstalledChunks\*\//g,
									needChunkLoadingCode(chunk)
										? "for(var chunkId in installedChunks)"
										: `var chunkId = ${JSON.stringify(chunk.id)};`
								)
						]);
					}
				);

				mainTemplate.hooks.globalHash.tap(
					"HotModuleReplacementPlugin",
					() => true
				);

				mainTemplate.hooks.currentHash.tap(
					"HotModuleReplacementPlugin",
					(_, length) => {
						if (isFinite(length)) {
							return `hotCurrentHash.substr(0, ${length})`;
						} else {
							return "hotCurrentHash";
						}
					}
				);

				mainTemplate.hooks.moduleObj.tap(
					"HotModuleReplacementPlugin",
					(source, chunk, hash, varModuleId) => {
						return Template.asString([
							`${source},`,
							`hot: hotCreateModule(${varModuleId}),`,
							"parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),",
							"children: []"
						]);
					}
				);

				// TODO add HMR support for javascript/esm
				normalModuleFactory.hooks.parser
					.for("javascript/auto")
					.tap("HotModuleReplacementPlugin", addParserPlugins);
				normalModuleFactory.hooks.parser
					.for("javascript/dynamic")
					.tap("HotModuleReplacementPlugin", addParserPlugins);

				compilation.hooks.normalModuleLoader.tap(
					"HotModuleReplacementPlugin",
					context => {
						context.hot = true;
					}
				);
			}
		);
	}
};

const hotInitCode = Template.getFunctionContent(
	require("./HotModuleReplacement.runtime.js")
);
<|endoftext|>


<commit_before>if(!kopal.page)
  kopal.page = {};

kopal.page.widget_canvas_url = kopal.identity + 'home/widget_canvas';

kopal.page.ShowSelectWidgetPage = function()
{
  YUI().use("node", "selector-css3", function(Y) {
    Y.one('body').appendChild(
      Y.Node.create('

').
        setStyle('position', 'absolute').
        setStyle('top', '0').
        setStyle('bottom', '0').
        setStyle('left', '0').
        setStyle('right', '0').
        setStyle('height', '100%'). //FIXME: Still only shadows the "showing" part of a scrollable page.
        setStyle('width', '100%').
        setStyle('background-color', 'black').
        setStyle('z-index', '1001').
        setStyle('opacity', '0.8'));
    var overlay = Y.one("#PageSelectWidgetForm");
    overlay.setStyle('display', 'block'). //Should be removing 'display' CSS attribute from element. How to?
      setStyle('position', 'absolute').
      setStyle('top', '0').
      setStyle('background-color', 'white').
      setStyle('z-index', '1002');
    Y.one('body').appendChild(overlay);
  });
}

kopal.page.CloseSelectWidgetPage = function()
{
  YUI().use('node', 'selector-css3', function(Y){
    Y.one('#PageOverlayBackground').remove();
    Y.one('#PageSelectWidgetForm').setStyle('display', 'none')
  });
}

kopal.page.CreateCanvasForWidget = function(widget_key, widget_url)
{
  YUI().use('node', 'selector-css3', function(Y){
    var page_edit = Y.one("#PageEdit");
    var iframe_tag = Y.Node.create('');
    iframe_tag.set('id', 'WidgetCanvas_' + widget_key);
    iframe_tag.setAttribute('seamless', 'seamless');
    //TODO: Use "srcdoc" later, and we set variables within document like widget_key in a <meta />? (or window.postMessage).
    iframe_tag.setAttribute('src', kopal.page.widget_canvas_url +
      //Using # instead of ? so that browser can supply from cache.
      //TODO: Use window.postMessage() instead.
      '#mode=edit;widget_key=' + widget_key + ';widget_url=' + encodeURIComponent(encodeURIComponent(widget_url)) );
    page_edit.appendChild(iframe_tag);
  });
}<commit_msg>Got to work more on making iframe 'seamless'.<commit_after>if(!kopal.page)
  kopal.page = {};

kopal.page.widget_canvas_url = kopal.identity + 'home/widget_canvas';

kopal.page.ShowSelectWidgetPage = function()
{
  YUI().use("node", "selector-css3", function(Y) {
    Y.one('body').appendChild(
      Y.Node.create('

').
        setStyle('position', 'absolute').
        setStyle('top', '0').
        setStyle('bottom', '0').
        setStyle('left', '0').
        setStyle('right', '0').
        setStyle('height', '100%'). //FIXME: Still only shadows the "showing" part of a scrollable page.
        setStyle('width', '100%').
        setStyle('background-color', 'black').
        setStyle('z-index', '1001').
        setStyle('opacity', '0.8'));
    var overlay = Y.one("#PageSelectWidgetForm");
    overlay.setStyle('display', 'block'). //Should be removing 'display' CSS attribute from element. How to?
      setStyle('position', 'absolute').
      setStyle('top', '0').
      setStyle('background-color', 'white').
      setStyle('z-index', '1002');
    Y.one('body').appendChild(overlay);
  });
}

kopal.page.CloseSelectWidgetPage = function()
{
  YUI().use('node', 'selector-css3', function(Y){
    Y.one('#PageOverlayBackground').remove();
    Y.one('#PageSelectWidgetForm').setStyle('display', 'none')
  });
}

kopal.page.CreateCanvasForWidget = function(widget_key, widget_url)
{
  YUI().use('node', 'selector-css3', function(Y){
    var page_edit = Y.one("#PageEdit");
    var wrapper_tag = Y.Node.create("

");
    var iframe_tag = Y.Node.create('');
    wrapper_tag.setAttribute('id', 'WidgetCanvasWrapper_' + widget_key);
    iframe_tag.set('id', 'WidgetCanvas_' + widget_key);
    iframe_tag.setAttribute('seamless', 'seamless'); //This should be implemented by browsers ASAP.
    iframe_tag.setStyle('height', '100%').
      setStyle('width', '100%');
    //TODO: Use "srcdoc" later, and we set variables within document like widget_key in a <meta />? (or window.postMessage).
    iframe_tag.setAttribute('src', kopal.page.widget_canvas_url +
      //Using # instead of ? so that browser can supply from cache.
      //TODO: Use window.postMessage() instead.
      '#mode=edit;widget_key=' + widget_key + ';widget_url=' + encodeURIComponent(encodeURIComponent(widget_url)) );
    page_edit.appendChild(iframe_tag);
  });
}<|endoftext|>


<commit_before>// Creates N.mailer interface. Currently it allows to send email letters only.

'use strict';

const _          = require('lodash');
const os         = require('os');
const nodemailer = require('nodemailer');
const inspect    = require('util').inspect;
const thenify    = require('thenify');

// List of global settings used by the mailer.
var SETTINGS = [
  'email_transport',
  'email_header_from'
];

// Map of available internal transport types to NodeMailer transport engines.
var TRANSPORTS = {
  sendmail() {
    return require('nodemailer-sendmail-transport')();
  },
  dummy() {
    return require('nodemailer-stub-transport')();
  }
};

module.exports = function (N) {
  N.wire.before('init:server.worker-http', function init_mailer() {
    var settings, transport, logger;

    // Initialize or update mailer context if email transport setting was changed.
    //
    function checkTransport(callback) {
      N.settings.get(SETTINGS, {})
        .then(fetched => {
          if (!_.has(TRANSPORTS, fetched.email_transport)) {
            callback(`Unknown email transport "${fetched.email_transport}"`);
            return;
          }

          // Reinitialize if any of email settings was changed.
          if (!_.isEqual(settings, fetched)) {
            settings  = fetched;

            transport = nodemailer.createTransport(TRANSPORTS[settings.email_transport]());
            logger    = N.logger.getLogger('system@mailer.' + settings.email_transport);
          }

          process.nextTick(callback);
        }, err => process.nextTick(() => callback(err)));
    }

    // Add a letter to the mailer's queue and call back when that's actually sent.
    //
    let send = thenify.withCallback(function send(options, callback) {
      checkTransport(function (err) {
        if (err) {
          // Log transport init errors
          logger.error(`Mailer error: ${os.EOL}%s${os.EOL}%s`, inspect(err), inspect(options));
          callback(new Error('Mailer error: problem with sending email (init)'));
          return;
        }

        // Fill-in default letter options.
        options = _.assign({}, options, { from: settings.email_header_from });

        transport.sendMail(options, function (err) {
          if (err) {
            logger.error(`Mailer error: ${os.EOL}%s${os.EOL}%s`, inspect(err), inspect(options));
            callback(new Error('Mailer error: problem with sending email (process)'));
            return;
          }

          logger.debug('Mailer ok: ${os.EOL}%s', inspect(options));
          callback();
        });
      });
    });

    // Expose the public interface.
    N.mailer = { send };
  });
};
<commit_msg>Promisify mail send<commit_after>// Creates N.mailer interface. Currently it allows to send email letters only.

'use strict';

const _          = require('lodash');
const os         = require('os');
const nodemailer = require('nodemailer');
const inspect    = require('util').inspect;
const co         = require('co');

// List of global settings used by the mailer.
var SETTINGS = [
  'email_transport',
  'email_header_from'
];

// Map of available internal transport types to NodeMailer transport engines.
var TRANSPORTS = {
  sendmail() {
    return require('nodemailer-sendmail-transport')();
  },
  dummy() {
    return require('nodemailer-stub-transport')();
  }
};

module.exports = function (N) {
  N.wire.before('init:server.worker-http', function init_mailer() {
    var settings, transport, logger;

    // Initialize or update mailer context if email transport setting was changed.
    //
    function checkTransport() {
      return N.settings.get(SETTINGS, {})
        .then(fetched => {
          if (!_.has(TRANSPORTS, fetched.email_transport)) {
            throw `Unknown email transport "${fetched.email_transport}"`;
          }

          // Reinitialize if any of email settings was changed.
          if (!_.isEqual(settings, fetched)) {
            settings  = fetched;

            transport = nodemailer.createTransport(TRANSPORTS[settings.email_transport]());
            logger    = N.logger.getLogger('system@mailer.' + settings.email_transport);
          }
        });
    }

    // Add a letter to the mailer's queue and call back when that's actually sent.
    //
    let send = co.wrap(function* (options) {
      try {
        yield checkTransport();
      } catch (err) {
        // Log transport init errors
        logger.error(`Mailer error: ${os.EOL}%s${os.EOL}%s`, inspect(err), inspect(options));
        throw new Error('Mailer error: problem with sending email (init)');
      }

      // Fill-in default letter options.
      options = _.assign({}, options, { from: settings.email_header_from });

      try {
        yield new Promise((resolve, reject) => {
          transport.sendMail(options, err => {
            if (err) reject (err);
            else resolve();
          });
        });
      } catch (err) {
        logger.error(`Mailer error: ${os.EOL}%s${os.EOL}%s`, inspect(err), inspect(options));
        throw new Error('Mailer error: problem with sending email (process)');
      }

      logger.debug('Mailer ok: ${os.EOL}%s', inspect(options));
    });

    // Expose the public interface.
    N.mailer = { send };
  });
};
<|endoftext|>


<commit_before>// declare variables for position
var longitude = 0;
var latitude = 0;
var altitude = 0;
var heading = 0;
var accuracy = 0;
var speed = 0;
var timestamp = 0;
var altitudeAccuracy = 0;
var magneticHeading = 0;
var trueHeading = 0;

Ti.Geolocation.preferredProvider = "gps";

if (isIPhone3_2_Plus()) {
	//NOTE: starting in 3.2+, you'll need to set the applications
	//purpose property for using Location services on iPhone
	Ti.Geolocation.purpose = "GPS demo";
}

function translateErrorCode(code) {
	if (code == null) {
		return null;
	}
	switch (code) {
		case Ti.Geolocation.ERROR_LOCATION_UNKNOWN:
			return "Location unknown";
		case Ti.Geolocation.ERROR_DENIED:
			return "Access denied";
		case Ti.Geolocation.ERROR_NETWORK:
			return "Network error";
		case Ti.Geolocation.ERROR_HEADING_FAILURE:
			return "Failure to detect heading";
		case Ti.Geolocation.ERROR_REGION_MONITORING_DENIED:
			return "Region monitoring access denied";
		case Ti.Geolocation.ERROR_REGION_MONITORING_FAILURE:
			return "Region monitoring access failure";
		case Ti.Geolocation.ERROR_REGION_MONITORING_DELAYED:
			return "Region monitoring setup delayed";
	}
}

// state vars used by resume/pause
var headingAdded = false;
var locationAdded = false;

//
//  SHOW CUSTOM ALERT IF DEVICE HAS GEO TURNED OFF
//
if (Titanium.Geolocation.locationServicesEnabled === false) {
	Titanium.UI.createAlertDialog({
		title : 'Teppy Trek',
		message : 'Your device has geo turned off - turn it on.'
	}).show();
} else {
	if (Titanium.Platform.name != 'android') {
		if (win.openedflag == 0) {
			Ti.API.info('firing open event');
			win.fireEvent('open');
		}
		if (win.focusedflag == 0) {
			Ti.API.info('firing focus event');
			win.fireEvent('focus');
		}
		var authorization = Titanium.Geolocation.locationServicesAuthorization;
		Ti.API.info('Authorization: ' + authorization);
		if (authorization == Titanium.Geolocation.AUTHORIZATION_DENIED) {
			Ti.UI.createAlertDialog({
				title : 'Teppy Trek',
				message : 'You have disallowed Teppy Trek from running geolocation services.'
			}).show();
		} else if (authorization == Titanium.Geolocation.AUTHORIZATION_RESTRICTED) {
			Ti.UI.createAlertDialog({
				title : 'Teppy Trek',
				message : 'Your system has disallowed Teppy Trek from running geolocation services.'
			}).show();
		}
	}

	//
	// IF WE HAVE COMPASS GET THE HEADING
	//
	if (Titanium.Geolocation.hasCompass) {
		//
		//  TURN OFF ANNOYING COMPASS INTERFERENCE MESSAGE
		//
		Titanium.Geolocation.showCalibration = false;

		//
		// SET THE HEADING FILTER (THIS IS IN DEGREES OF ANGLE CHANGE)
		// EVENT WON'T FIRE UNLESS ANGLE CHANGE EXCEEDS THIS VALUE
		Titanium.Geolocation.headingFilter = 1;

		//
		//  GET CURRENT HEADING - THIS FIRES ONCE
		//
		Ti.Geolocation.getCurrentHeading(function(e) {
			if (e.error) {
				currentHeading.text = 'error: ' + e.error;
				Ti.API.info("Code translation: " + translateErrorCode(e.code));
				return;
			}

			magneticHeading = e.heading.magneticHeading;
			accuracy = e.heading.accuracy;
			trueHeading = e.heading.trueHeading;
			timestamp = e.heading.timestamp;

			Titanium.API.info('geo - current heading: ' + new Date(timestamp));
		});

		//
		// EVENT LISTENER FOR COMPASS EVENTS - THIS WILL FIRE REPEATEDLY (BASED ON HEADING FILTER)
		//
		var headingCallback = function(e) {
			if (e.error) {
				updatedHeading.text = 'error: ' + e.error;
				Ti.API.info("Code translation: " + translateErrorCode(e.code));
				return;
			}

			magneticHeading = e.heading.magneticHeading;
			accuracy = e.heading.accuracy;
			trueHeading = e.heading.trueHeading;
			timestamp = e.heading.timestamp;

			Titanium.API.info('geo - heading updated: ' + new Date(timestamp));
		};
		Titanium.Geolocation.addEventListener('heading', headingCallback);
		headingAdded = true;
	} else {
		Titanium.API.info("No Compass on device");
	}

	//
	//  SET ACCURACY - THE FOLLOWING VALUES ARE SUPPORTED
	//
	// Titanium.Geolocation.ACCURACY_BEST
	// Titanium.Geolocation.ACCURACY_NEAREST_TEN_METERS
	// Titanium.Geolocation.ACCURACY_HUNDRED_METERS
	// Titanium.Geolocation.ACCURACY_KILOMETER
	// Titanium.Geolocation.ACCURACY_THREE_KILOMETERS
	//
	Titanium.Geolocation.accuracy = Titanium.Geolocation.ACCURACY_BEST;

	//
	//  SET DISTANCE FILTER.  THIS DICTATES HOW OFTEN AN EVENT FIRES BASED ON THE DISTANCE THE DEVICE MOVES
	//  THIS VALUE IS IN METERS
	//
	Titanium.Geolocation.distanceFilter = 3;

	//
	// GET CURRENT POSITION - THIS FIRES ONCE
	//
	win.addEventListener('open', function() {
		win.openedflag = 1;
		Titanium.Geolocation.getCurrentPosition(function(e) {
			if (!e.success || e.error) {
				currentLocation.text = 'error: ' + JSON.stringify(e.error);
				Ti.API.info("Code translation: " + translateErrorCode(e.code));
				alert('error ' + JSON.stringify(e.error));
				return;
			}

			longitude = e.coords.longitude;
			latitude = e.coords.latitude;
			altitude = e.coords.altitude;
			heading = e.coords.heading;
			accuracy = e.coords.accuracy;
			speed = e.coords.speed;
			timestamp = e.coords.timestamp;
			altitudeAccuracy = e.coords.altitudeAccuracy;
			Ti.API.info('speed ' + speed);

			Titanium.API.info('geo - current location: ' + new Date(timestamp) + ' long ' + longitude + ' lat ' + latitude + ' accuracy ' + accuracy);
		});
	});

	//
	// EVENT LISTENER FOR GEO EVENTS - THIS WILL FIRE REPEATEDLY (BASED ON DISTANCE FILTER)
	//
	var locationCallback = function(e) {
		//Mobileweb seems to be not firing window event for some odd reason.
		//Forcing a window open and focus event.
		if (win.openedflag == 0) {
			Ti.API.info('firing open event');
			win.fireEvent('open');
		}
		if (win.focusedflag == 0) {
			Ti.API.info('firing focus event');
			win.fireEvent('focus');
		}
		if (!e.success || e.error) {
			updatedLocation.text = 'error:' + JSON.stringify(e.error);
			updatedLatitude.text = '';
			updatedLocationAccuracy.text = '';
			updatedLocationTime.text = '';
			Ti.API.info("Code translation: " + translateErrorCode(e.code));
			return;
		}

		longitude = e.coords.longitude;
		latitude = e.coords.latitude;
		altitude = e.coords.altitude;
		heading = e.coords.heading;
		accuracy = e.coords.accuracy;
		speed = e.coords.speed;
		timestamp = e.coords.timestamp;
		altitudeAccuracy = e.coords.altitudeAccuracy;

		//Titanium.Geolocation.distanceFilter = 100; //changed after first location event

		Titanium.API.info('geo - location updated: ' + new Date(timestamp) + ' long ' + longitude + ' lat ' + latitude + ' accuracy ' + accuracy);
	};
	Titanium.Geolocation.addEventListener('location', locationCallback);
	locationAdded = true;

}

win.addEventListener('focus', function() {
	win.focusedflag = 1;
	Ti.API.info("focus event received");
	if (!headingAdded && headingCallback) {
		Ti.API.info("adding heading callback on resume");
		Titanium.Geolocation.addEventListener('heading', headingCallback);
		headingAdded = true;
	}
	if (!locationAdded && locationCallback) {
		Ti.API.info("adding location callback on resume");
		Titanium.Geolocation.addEventListener('location', locationCallback);
		locationAdded = true;
	}

});

win.addEventListener('blur', function() {
	Ti.API.info("pause event received");
	if (headingAdded) {
		Ti.API.info("removing heading callback on pause");
		Titanium.Geolocation.removeEventListener('heading', headingCallback);
		headingAdded = false;
	}
	if (locationAdded) {
		Ti.API.info("removing location callback on pause");
		Titanium.Geolocation.removeEventListener('location', locationCallback);
		locationAdded = false;
	}
});

<commit_msg>Making this to be a fully functional geo unit. Need to edit a few current event listeners, and convert them to callable funtions, since they need to be called from two windows, and not a listener on a single window running an anonymous function.<commit_after>// declare variables for position
var longitude = 0;
var latitude = 0;
var altitude = 0;
var heading = 0;
var accuracy = 0;
var speed = 0;
var timestamp = 0;
var altitudeAccuracy = 0;
var magneticHeading = 0;
var trueHeading = 0;

Ti.Geolocation.preferredProvider = "gps";

if (isIPhone3_2_Plus()) {
	//NOTE: starting in 3.2+, you'll need to set the applications
	//purpose property for using Location services on iPhone
	Ti.Geolocation.purpose = "Teppy TrekTracker";
}

function translateErrorCode(code) {
	if (code == null) {
		return null;
	}
	switch (code) {
		case Ti.Geolocation.ERROR_LOCATION_UNKNOWN:
			return "Location unknown";
		case Ti.Geolocation.ERROR_DENIED:
			return "Access denied";
		case Ti.Geolocation.ERROR_NETWORK:
			return "Network error";
		case Ti.Geolocation.ERROR_HEADING_FAILURE:
			return "Failure to detect heading";
		case Ti.Geolocation.ERROR_REGION_MONITORING_DENIED:
			return "Region monitoring access denied";
		case Ti.Geolocation.ERROR_REGION_MONITORING_FAILURE:
			return "Region monitoring access failure";
		case Ti.Geolocation.ERROR_REGION_MONITORING_DELAYED:
			return "Region monitoring setup delayed";
	}
}

// state vars used by resume/pause
var headingAdded = false;
var locationAdded = false;

//
//  SHOW CUSTOM ALERT IF DEVICE HAS GEO TURNED OFF
//
if (Titanium.Geolocation.locationServicesEnabled === false) {
	Titanium.UI.createAlertDialog({
		title : 'Teppy Trek',
		message : 'Your device has geo turned off - turn it on.'
	}).show();
} else {
	if (Titanium.Platform.name != 'android') {
		if (win.openedflag == 0) {
			Ti.API.info('firing open event');
			win.fireEvent('open');
		}
		if (win.focusedflag == 0) {
			Ti.API.info('firing focus event');
			win.fireEvent('focus');
		}
		var authorization = Titanium.Geolocation.locationServicesAuthorization;
		Ti.API.info('Authorization: ' + authorization);
		if (authorization == Titanium.Geolocation.AUTHORIZATION_DENIED) {
			Ti.UI.createAlertDialog({
				title : 'Teppy Trek',
				message : 'You have disallowed Teppy Trek from running geolocation services.'
			}).show();
		} else if (authorization == Titanium.Geolocation.AUTHORIZATION_RESTRICTED) {
			Ti.UI.createAlertDialog({
				title : 'Teppy Trek',
				message : 'Your system has disallowed Teppy Trek from running geolocation services.'
			}).show();
		}
	}

	//
	// IF WE HAVE COMPASS GET THE HEADING
	//
	if (Titanium.Geolocation.hasCompass) {
		//
		//  TURN OFF ANNOYING COMPASS INTERFERENCE MESSAGE
		//
		Titanium.Geolocation.showCalibration = false;

		//
		// SET THE HEADING FILTER (THIS IS IN DEGREES OF ANGLE CHANGE)
		// EVENT WON'T FIRE UNLESS ANGLE CHANGE EXCEEDS THIS VALUE
		Titanium.Geolocation.headingFilter = 1;

		//
		//  GET CURRENT HEADING - THIS FIRES ONCE
		//
		Ti.Geolocation.getCurrentHeading(function(e) {
			if (e.error) {
				currentHeading.text = 'error: ' + e.error;
				Ti.API.info("Code translation: " + translateErrorCode(e.code));
				return;
			}

			magneticHeading = e.heading.magneticHeading;
			accuracy = e.heading.accuracy;
			trueHeading = e.heading.trueHeading;
			timestamp = e.heading.timestamp;

			Titanium.API.info('geo - current heading: ' + new Date(timestamp));
		});

		//
		// EVENT LISTENER FOR COMPASS EVENTS - THIS WILL FIRE REPEATEDLY (BASED ON HEADING FILTER)
		//
		var headingCallback = function(e) {
			if (e.error) {
				updatedHeading.text = 'error: ' + e.error;
				Ti.API.info("Code translation: " + translateErrorCode(e.code));
				return;
			}

			magneticHeading = e.heading.magneticHeading;
			accuracy = e.heading.accuracy;
			trueHeading = e.heading.trueHeading;
			timestamp = e.heading.timestamp;

			Titanium.API.info('geo - heading updated: ' + new Date(timestamp));
		};
		Titanium.Geolocation.addEventListener('heading', headingCallback);
		headingAdded = true;
	} else {
		Titanium.API.info("No Compass on device");
	}

	//
	//  SET ACCURACY - THE FOLLOWING VALUES ARE SUPPORTED
	//
	// Titanium.Geolocation.ACCURACY_BEST
	// Titanium.Geolocation.ACCURACY_NEAREST_TEN_METERS
	// Titanium.Geolocation.ACCURACY_HUNDRED_METERS
	// Titanium.Geolocation.ACCURACY_KILOMETER
	// Titanium.Geolocation.ACCURACY_THREE_KILOMETERS
	//
	Titanium.Geolocation.accuracy = Titanium.Geolocation.ACCURACY_BEST;

	//
	//  SET DISTANCE FILTER.  THIS DICTATES HOW OFTEN AN EVENT FIRES BASED ON THE DISTANCE THE DEVICE MOVES
	//  THIS VALUE IS IN METERS
	//
	Titanium.Geolocation.distanceFilter = 3;

	//
	// GET CURRENT POSITION - THIS FIRES ONCE
	//
	// ---------------------- > from here I have to make this a callable function from the windows themselves.
	win.addEventListener('open', function() {
		win.openedflag = 1;
		Titanium.Geolocation.getCurrentPosition(function(e) {
			if (!e.success || e.error) {
				currentLocation.text = 'error: ' + JSON.stringify(e.error);
				Ti.API.info("Code translation: " + translateErrorCode(e.code));
				alert('error ' + JSON.stringify(e.error));
				return;
			}

			longitude = e.coords.longitude;
			latitude = e.coords.latitude;
			altitude = e.coords.altitude;
			heading = e.coords.heading;
			accuracy = e.coords.accuracy;
			speed = e.coords.speed;
			timestamp = e.coords.timestamp;
			altitudeAccuracy = e.coords.altitudeAccuracy;
			Ti.API.info('speed ' + speed);

			Titanium.API.info('geo - current location: ' + new Date(timestamp) + ' long ' + longitude + ' lat ' + latitude + ' accuracy ' + accuracy);
		});
	});

	//
	// EVENT LISTENER FOR GEO EVENTS - THIS WILL FIRE REPEATEDLY (BASED ON DISTANCE FILTER)
	//
	var locationCallback = function(e) {
		//Mobileweb seems to be not firing window event for some odd reason.
		//Forcing a window open and focus event.
		if (win.openedflag == 0) {
			Ti.API.info('firing open event');
			win.fireEvent('open');
		}
		if (win.focusedflag == 0) {
			Ti.API.info('firing focus event');
			win.fireEvent('focus');
		}
		if (!e.success || e.error) {
			updatedLocation.text = 'error:' + JSON.stringify(e.error);
			updatedLatitude.text = '';
			updatedLocationAccuracy.text = '';
			updatedLocationTime.text = '';
			Ti.API.info("Code translation: " + translateErrorCode(e.code));
			return;
		}

		longitude = e.coords.longitude;
		latitude = e.coords.latitude;
		altitude = e.coords.altitude;
		heading = e.coords.heading;
		accuracy = e.coords.accuracy;
		speed = e.coords.speed;
		timestamp = e.coords.timestamp;
		altitudeAccuracy = e.coords.altitudeAccuracy;

		Titanium.API.info('geo - location updated: ' + new Date(timestamp) + ' long ' + longitude + ' lat ' + latitude + ' accuracy ' + accuracy);
	};
	Titanium.Geolocation.addEventListener('location', locationCallback);
	locationAdded = true;

}

// ---------------------- > from here I have to make these callable functions from the windows themselves.
// the variables used as window properties for these is:
// win.openedflag = 0 ;
// win.focusedflag = 0;

win.addEventListener('focus', function() {
	win.focusedflag = 1;
	Ti.API.info("focus event received");
	if (!headingAdded && headingCallback) {
		Ti.API.info("adding heading callback on resume");
		Titanium.Geolocation.addEventListener('heading', headingCallback);
		headingAdded = true;
	}
	if (!locationAdded && locationCallback) {
		Ti.API.info("adding location callback on resume");
		Titanium.Geolocation.addEventListener('location', locationCallback);
		locationAdded = true;
	}

});

win.addEventListener('blur', function() {
	Ti.API.info("pause event received");
	if (headingAdded) {
		Ti.API.info("removing heading callback on pause");
		Titanium.Geolocation.removeEventListener('heading', headingCallback);
		headingAdded = false;
	}
	if (locationAdded) {
		Ti.API.info("removing location callback on pause");
		Titanium.Geolocation.removeEventListener('location', locationCallback);
		locationAdded = false;
	}
});

<|endoftext|>


<commit_before>var _ = require('lib/underscore')._;
var Choice = require('models/choice');
var Question = require('models/question');
var Option = require('models/option');
var progressBarView = require('ui/common/components/ProgressBar');

var Answer = new Ti.App.joli.model({
  table : 'answers',
  columns : {
    id : 'INTEGER PRIMARY KEY',
    content : 'TEXT',
    response_id : 'INTEGER',
    question_id : 'INTEGER',
    web_id : 'INTEGER',
    updated_at : 'TEXT',
    image : 'TEXT'
  },

  methods : {
    createRecord : function(answerData, responseID) {
      answerData.response_id = responseID;
      var question = Question.findOneById(answerData['question_id']);
      var optionIds = [];

      if (question.isMultiChoiceQuestion()) {
        optionIds = answerData['content'];
        answerData['content'] = "";
      } else if (question.isPhotoQuestion()) {
        var image = answerData['content'];
        answerData['content'] = "";
        answerData['image'] = image;
      }
      answerData['updated_at'] = parseInt(new Date().getTime()/1000, 10);
      var answer = this.newRecord(answerData);
      answer.save();

      _(optionIds).each(function(option_id) {
        Choice.createRecord(answer.id, option_id);
      });
    },

    validate : function(answerData, status) {
      var question = Question.findOneById(answerData.question_id);
      var errors = {};
      if (answerData.content && !_.isEmpty(answerData.content)) {
        if (question.max_length && (answerData.content.length >= question.max_length))
          errors['max_length'] = L("error_max_length");
        if (question.min_value && answerData.content < question.min_value)
          errors['min_value'] = L("error_min_value");
        if (question.max_value && answerData.content > question.max_value)
          errors['max_value'] = L("error_max_value");
        if (question.type === 'NumericQuestion' && isNaN(answerData.content))
          errors['content'] = L("error_content");
      } else if (status === "complete" && question.mandatory)
        errors['mandatory'] = L("error_mandatory");
      return errors;
    }
  },
  objectMethods : {

    update : function(content) {
      Ti.API.info("CONTENT IS ");
      Ti.API.info(content);
      Ti.API.info("updating answer");

      var question = this.question();
      var updated_at;
      if (question.isMultiChoiceQuestion()) {
        var optionIds = content;

        var existing_optionIDs = _(Choice.findBy('answer_id', this.id)).map(function(choice) {
          return choice.option_id;
        });
        updated_at = optionIds.sort() === existing_optionIDs.sort() ? this.updated_at : parseInt(new Date().getTime()/1000, 10);

        this.destroyChoices();
        var that = this;
        _(optionIds).each(function(option_id) {
          Choice.createRecord(that.id, option_id);
        });

        this.set('content', '');
        this.set('updated_at', updated_at);
      } else if (question.isPhotoQuestion()) {
        var image = content;
        updated_at = image === this.image ? this.updated_at : parseInt(new Date().getTime()/1000, 10);
        this.set('content', '');
        this.set('image', image);
        this.set('updated_at', updated_at);
      } else {
        updated_at = content === this.content ? this.updated_at : parseInt(new Date().getTime()/1000, 10);
        this.set('content', content);
        this.set('updated_at', updated_at);
      }

      this.save();
      Ti.API.info("answer saved with content" + this.content);
    },

    hasChoices : function() {
      return Question.findOneById(this.question_id).isMultiChoiceQuestion();
    },

    isImage : function() {
      return Question.findOneById(this.question_id).isPhotoQuestion();
    },

    optionIDs : function() {
      return _(Choice.findBy('answer_id', this.id)).map(function(choice) {
        return choice.option_id;
      });
    },

    question : function() {
      return Question.findOneById(this.question_id);
    },

    contentForDisplay : function() {
      if (this.isImage()) {
        return '';
      } else if (this.hasChoices()) {
        return _(this.optionIDs()).map(function(optionId) {
          return Option.findOneById(optionId).content;
        }).join(', ');
      } else {
        return this.content;
      }
    },

    destroyChoices : function() {
      return _(Choice.findBy('answer_id', this.id)).each(function(choice) {
        choice.destroy();
      });
    },

    destroyImage : function () {
      if (this.isImage() && this.image)
        Ti.Filesystem.getFile(this.image).deleteFile();
    },

    destroyAll : function() {
      this.destroyChoices();
      this.destroyImage();
      this.destroy();
    }
  }
});

Ti.App.joli.models.initialize();
module.exports = Answer;

<commit_msg>Fix an off-by-one error when validating max-length<commit_after>var _ = require('lib/underscore')._;
var Choice = require('models/choice');
var Question = require('models/question');
var Option = require('models/option');
var progressBarView = require('ui/common/components/ProgressBar');

var Answer = new Ti.App.joli.model({
  table : 'answers',
  columns : {
    id : 'INTEGER PRIMARY KEY',
    content : 'TEXT',
    response_id : 'INTEGER',
    question_id : 'INTEGER',
    web_id : 'INTEGER',
    updated_at : 'TEXT',
    image : 'TEXT'
  },

  methods : {
    createRecord : function(answerData, responseID) {
      answerData.response_id = responseID;
      var question = Question.findOneById(answerData['question_id']);
      var optionIds = [];

      if (question.isMultiChoiceQuestion()) {
        optionIds = answerData['content'];
        answerData['content'] = "";
      } else if (question.isPhotoQuestion()) {
        var image = answerData['content'];
        answerData['content'] = "";
        answerData['image'] = image;
      }
      answerData['updated_at'] = parseInt(new Date().getTime()/1000, 10);
      var answer = this.newRecord(answerData);
      answer.save();

      _(optionIds).each(function(option_id) {
        Choice.createRecord(answer.id, option_id);
      });
    },

    validate : function(answerData, status) {
      var question = Question.findOneById(answerData.question_id);
      var errors = {};
      if (answerData.content && !_.isEmpty(answerData.content)) {
        if (question.max_length && (answerData.content.length > question.max_length))
          errors['max_length'] = L("error_max_length");
        if (question.min_value && answerData.content < question.min_value)
          errors['min_value'] = L("error_min_value");
        if (question.max_value && answerData.content > question.max_value)
          errors['max_value'] = L("error_max_value");
        if (question.type === 'NumericQuestion' && isNaN(answerData.content))
          errors['content'] = L("error_content");
      } else if (status === "complete" && question.mandatory)
        errors['mandatory'] = L("error_mandatory");
      return errors;
    }
  },
  objectMethods : {

    update : function(content) {
      Ti.API.info("CONTENT IS ");
      Ti.API.info(content);
      Ti.API.info("updating answer");

      var question = this.question();
      var updated_at;
      if (question.isMultiChoiceQuestion()) {
        var optionIds = content;

        var existing_optionIDs = _(Choice.findBy('answer_id', this.id)).map(function(choice) {
          return choice.option_id;
        });
        updated_at = optionIds.sort() === existing_optionIDs.sort() ? this.updated_at : parseInt(new Date().getTime()/1000, 10);

        this.destroyChoices();
        var that = this;
        _(optionIds).each(function(option_id) {
          Choice.createRecord(that.id, option_id);
        });

        this.set('content', '');
        this.set('updated_at', updated_at);
      } else if (question.isPhotoQuestion()) {
        var image = content;
        updated_at = image === this.image ? this.updated_at : parseInt(new Date().getTime()/1000, 10);
        this.set('content', '');
        this.set('image', image);
        this.set('updated_at', updated_at);
      } else {
        updated_at = content === this.content ? this.updated_at : parseInt(new Date().getTime()/1000, 10);
        this.set('content', content);
        this.set('updated_at', updated_at);
      }

      this.save();
      Ti.API.info("answer saved with content" + this.content);
    },

    hasChoices : function() {
      return Question.findOneById(this.question_id).isMultiChoiceQuestion();
    },

    isImage : function() {
      return Question.findOneById(this.question_id).isPhotoQuestion();
    },

    optionIDs : function() {
      return _(Choice.findBy('answer_id', this.id)).map(function(choice) {
        return choice.option_id;
      });
    },

    question : function() {
      return Question.findOneById(this.question_id);
    },

    contentForDisplay : function() {
      if (this.isImage()) {
        return '';
      } else if (this.hasChoices()) {
        return _(this.optionIDs()).map(function(optionId) {
          return Option.findOneById(optionId).content;
        }).join(', ');
      } else {
        return this.content;
      }
    },

    destroyChoices : function() {
      return _(Choice.findBy('answer_id', this.id)).each(function(choice) {
        choice.destroy();
      });
    },

    destroyImage : function () {
      if (this.isImage() && this.image)
        Ti.Filesystem.getFile(this.image).deleteFile();
    },

    destroyAll : function() {
      this.destroyChoices();
      this.destroyImage();
      this.destroy();
    }
  }
});

Ti.App.joli.models.initialize();
module.exports = Answer;

<|endoftext|>


<commit_before>function mapQuery(view) {
	Ti.Geolocation.purpose = Ti.Geolocation.ACTIVITYTYPE_OTHER;
	var osname = Ti.Platform.osname;
	var Cloud = require('ti.cloud');
	var region = view.getRegion();
	Ti.API.info('region' + region.latitude);
	Ti.API.info('region' + region.longitude);
	Ti.API.info(region);
	var lat = region.latitude;
	var lon = region.longitude;
	var longdelt = region.longitudeDelta;

	var radians = longdelt*3.14/180;
	var today = new Date();

	Cloud.debug = true;
	Ti.API.info(view);
	jobs = Cloud.Objects.query({
		classname : 'jobs',
		limit : 100,

		where : {
			"coordinates":{"$nearSphere":[lon, lat], "$maxDistance" : radians },
			"claimed" : false,
			"expiration" : {"$gt": today}
			}
	}, function(e) {
		if (e.success) {

			// var jobs = e.jobs;
			var annot = new Array();
			for (var i = 0; i < e.jobs.length; i++) {
				// alert('Success:\n' + 'Count: ' + e.jobs.length + e.jobs[i].description+e.jobs[i].coordinates[0][0]);
				if (osname == 'android') {
					annot.push(MapModule.createAnnotation({
						latitude : e.jobs[i].coordinates[0][1],
						longitude : e.jobs[i].coordinates[0][0],
						title : e.jobs[i].description,
						subtitle : e.jobs[i].time_estimate + " hours \n$" + e.jobs[i].wage + "\nexpires " + e.jobs[i].expiration,
						animate : true,
						leftView : Ti.UI.createButton({
							title : 'contact Poster'
						})
					}));
				} else {
					annot.push(Titanium.Map.createAnnotation({
						latitude : e.jobs[i].coordinates[0][1],
						longitude : e.jobs[i].coordinates[0][0],
						title : e.jobs[i].description,
						subtitle : e.jobs[i].time_estimate + " hours \n$" + e.jobs[i].wage + "\nexpires " + e.jobs[i].expiration,
						animate : true,
						leftView : Ti.UI.createView({
							title : 'contact Poster'
						})
					}));
				}
				var email = e.jobs[i].user.email;
				var description = e.jobs[i].description;
				annot[i].addEventListener('click', function(b) {
					var emailDialog = Ti.UI.createEmailDialog()
					emailDialog.subject = "Interest in job posting: " + description;
					emailDialog.toRecipients = [email];
					emailDialog.open();
				});
				Ti.API.info(e.jobs[i].user.email);
			}
			Ti.API.info(annot);
			Ti.API.info(view);
			view.addAnnotations(annot);
			var viewAnnot = view.getAnnotations();
			alert('viewAnnot ' + viewAnnot[0].title);
		} else {
			alert('Error:\n' + ((e.error && e.message) || JSON.stringify(e)));
		}
	});

}

exports.createMapWindow = function() {
	var win = Ti.UI.createWindow({
		backgroundColor : "white",
		layout : 'vertical',
		navBarHidden : true
	});

	var osname = Ti.Platform.osname;

	var latitude;
	var longitude;
	var	latitudeDelta = 0.1;
	var	longitudeDelta = 0.1;

	var loc = Ti.Geolocation.getCurrentPosition(function(e) {
		if (e.error) {
			alert('Cannot get your current location, please manually set your location.');
			latitude = 0;
			longitude = 0;
			return;
		} else {
			alert('Success getting location\nLatitude' + e.coords.latitude + '\nLongitude ' + e.coords.longitude);
			latitude = e.coords.latitude;
			longitude = e.coords.longitude;
			Ti.API.info('getCurrentPos Map.js')
			Ti.API.info(latitude);
			Ti.API.info(longitude);

			return;
		}
	});

	Ti.API.info(latitude);
	Ti.API.info(longitude);

// alert(JSON.stringify(jobs));
var annot = new Array();

<<<<<<< HEAD
	var map = MapModule.createView({
		height : '90%',
		userLocation : true,
		mapType : MapModule.NORMAL_TYPE,
		animate : true,
		region : {
			latitude : latitude,
			longitude : longitude,
			latitudeDelta : 0.1,
			longitudeDelta : 0.1
		},
		// height : Titanium.UI.Fill,
		width : Titanium.UI.Fill,
		top : 0,
		left : 0,
		// annotations :
=======
	// alert(JSON.stringify(jobs));
	var annot = new Array();

	if (osname == 'android') {

		var MapModule = require('ti.map');

		var osu = MapModule.createAnnotation({
			latitude : 44.5646,
			longitude : -123.2757,
			title : "Oregon State University",
			subtitle : 'Corvallis, OR',
			pincolor : MapModule.ANNOTATION_RED,
			animate : true,
			//leftButton : '../images/appcelerator_small.png',
			leftView : Ti.UI.createButton({
				title : 'Detail'
			}),
			myid : 1 // Custom property to uniquely identify this annotation.
		});

		var map = MapModule.createView({
			height : '90%',
			userLocation : true,
			mapType : MapModule.NORMAL_TYPE,
			animate : true,
			region : {
				latitude : lat,
				longitude : lon,
				latitudeDelta : 0.1,
				longitudeDelta : 0.1
			},
			// height : Titanium.UI.Fill,
			width : Titanium.UI.Fill,
			top : 0,
			left : 0,
			// annotations :
		});
	} else {

		var osu = Titanium.Map.createAnnotation({
			latitude : 44.5646,
			longitude : -123.2757,
			title : "Oregon State University",
			subtitle : 'Corvallis, OR',
			pincolor : Titanium.Map.ANNOTATION_RED,
			animate : true,
			// leftView: Ti.UI.createButton({title: 'Detail'}),
			// leftView: Ti.UI.createButton({title: 'Detail'}),
			leftButton : Titanium.UI.iPhone.SystemButton.INFO_LIGHT,
			myid : 1 // Custom property to uniquely identify this annotation.
		});

		var map = Titanium.Map.createView({
			height : '95%',
			userLocation : true,
			mapType : Titanium.Map.STANDARD_TYPE,
			animate : true,
			region : {
				latitude : lat,
				longitude : lon,
				latitudeDelta : 0.1,
				longitudeDelta : 0.1
			},
			top : 0,
			left : 0,
			annotations : [osu]
		});
	}
	win.add(map);

	mapQuery(map);

	var backBtn = Ti.UI.createButton({
		title : 'Back',
		width : Ti.UI.FILL,
		height : Ti.UI.FILL
	});

	backBtn.addEventListener('click', function() {
		win.close();
	});

<commit_msg>Pixed problem with backBtn of Map.js<commit_after>function mapQuery(view) {
	Ti.Geolocation.purpose = Ti.Geolocation.ACTIVITYTYPE_OTHER;
	var osname = Ti.Platform.osname;
	var Cloud = require('ti.cloud');
	var region = view.getRegion();
	Ti.API.info('region' + region.latitude);
	Ti.API.info('region' + region.longitude);
	Ti.API.info(region);
	var lat = region.latitude;
	var lon = region.longitude;
	var longdelt = region.longitudeDelta;

	var radians = longdelt*3.14/180;
	var today = new Date();

	Cloud.debug = true;
	Ti.API.info(view);
	jobs = Cloud.Objects.query({
		classname : 'jobs',
		limit : 100,

		where : {
			"coordinates":{"$nearSphere":[lon, lat], "$maxDistance" : radians },
			"claimed" : false,
			"expiration" : {"$gt": today}
			}
	}, function(e) {
		if (e.success) {

			// var jobs = e.jobs;
			var annot = new Array();
			for (var i = 0; i < e.jobs.length; i++) {
				// alert('Success:\n' + 'Count: ' + e.jobs.length + e.jobs[i].description+e.jobs[i].coordinates[0][0]);
				if (osname == 'android') {
					annot.push(MapModule.createAnnotation({
						latitude : e.jobs[i].coordinates[0][1],
						longitude : e.jobs[i].coordinates[0][0],
						title : e.jobs[i].description,
						subtitle : e.jobs[i].time_estimate + " hours \n$" + e.jobs[i].wage + "\nexpires " + e.jobs[i].expiration,
						animate : true,
						leftView : Ti.UI.createButton({
							title : 'contact Poster'
						})
					}));
				} else {
					annot.push(Titanium.Map.createAnnotation({
						latitude : e.jobs[i].coordinates[0][1],
						longitude : e.jobs[i].coordinates[0][0],
						title : e.jobs[i].description,
						subtitle : e.jobs[i].time_estimate + " hours \n$" + e.jobs[i].wage + "\nexpires " + e.jobs[i].expiration,
						animate : true,
						leftView : Ti.UI.createView({
							title : 'contact Poster'
						})
					}));
				}
				var email = e.jobs[i].user.email;
				var description = e.jobs[i].description;
				annot[i].addEventListener('click', function(b) {
					var emailDialog = Ti.UI.createEmailDialog()
					emailDialog.subject = "Interest in job posting: " + description;
					emailDialog.toRecipients = [email];
					emailDialog.open();
				});
				Ti.API.info(e.jobs[i].user.email);
			}
			Ti.API.info(annot);
			Ti.API.info(view);
			view.addAnnotations(annot);
			var viewAnnot = view.getAnnotations();
			alert('viewAnnot ' + viewAnnot[0].title);
		} else {
			alert('Error:\n' + ((e.error && e.message) || JSON.stringify(e)));
		}
	});

}

exports.createMapWindow = function() {

	var lat;
	var lon;
	var	latitudeDelta = 0.1;
	var	longitudeDelta = 0.1;

	var loc = Ti.Geolocation.getCurrentPosition(function(e) {
		if (e.error) {
			alert('Cannot get your current location, please manually set your location.');
			lat = 0;
			lon = 0;
			makeMaps(lat, lon);
		} else {
			alert('Success getting location\nLatitude' + e.coords.latitude + '\nLongitude ' + e.coords.longitude);
			lat = e.coords.latitude;
			lon = e.coords.longitude;
			Ti.API.info('getCurrentPos Map.js')
			Ti.API.info(lat);
			Ti.API.info(lon);
			makeMaps(lat, lon);

		}

	});

	Ti.API.info(lat);
	Ti.API.info(lon);
}

var makeMaps = function(lat, lon){
	var win = Ti.UI.createWindow({
		backgroundColor : "white",
		layout : 'vertical',
		navBarHidden : true
	});
	var osname = Ti.Platform.osname;
	// alert(JSON.stringify(jobs));
	var annot = new Array();

	if (osname == 'android') {

		var MapModule = require('ti.map');

		var osu = MapModule.createAnnotation({
			latitude : 44.5646,
			longitude : -123.2757,
			title : "Oregon State University",
			subtitle : 'Corvallis, OR',
			pincolor : MapModule.ANNOTATION_RED,
			animate : true,
			//leftButton : '../images/appcelerator_small.png',
			leftView : Ti.UI.createButton({
				title : 'Detail'
			}),
			myid : 1 // Custom property to uniquely identify this annotation.
		});

		var map = MapModule.createView({
			height : '90%',
			userLocation : true,
			mapType : MapModule.NORMAL_TYPE,
			animate : true,
			region : {
				latitude : lat,
				longitude : lon,
				latitudeDelta : 0.1,
				longitudeDelta : 0.1
			},
			// height : Titanium.UI.Fill,
			width : Titanium.UI.Fill,
			top : 0,
			left : 0,
			// annotations :
		});
	} else {

		var osu = Titanium.Map.createAnnotation({
			latitude : 44.5646,
			longitude : -123.2757,
			title : "Oregon State University",
			subtitle : 'Corvallis, OR',
			pincolor : Titanium.Map.ANNOTATION_RED,
			animate : true,
			// leftView: Ti.UI.createButton({title: 'Detail'}),
			// leftView: Ti.UI.createButton({title: 'Detail'}),
			leftButton : Titanium.UI.iPhone.SystemButton.INFO_LIGHT,
			myid : 1 // Custom property to uniquely identify this annotation.
		});

		var map = Titanium.Map.createView({
			height : '95%',
			userLocation : true,
			mapType : Titanium.Map.STANDARD_TYPE,
			animate : true,
			region : {
				latitude : lat,
				longitude : lon,
				latitudeDelta : 0.1,
				longitudeDelta : 0.1
			},
			top : 0,
			left : 0,
			annotations : [osu]
		});
	}
	win.add(map);

	mapQuery(map);

	win.open();

	var backBtn = Ti.UI.createButton({
		title : 'Back',
		width : Ti.UI.FILL,
		height : Ti.UI.FILL
	});

	backBtn.addEventListener('click', function() {
		win.close();
	});
	win.add(backBtn);
}
<|endoftext|>


<commit_before>// # Ghost Mobile Interactions

/*global window, document, $, FastClick */

(function () {
    'use strict';

    FastClick.attach(document.body);

    // ### Show content preview when swiping left on content list
    $('.manage').on('click', '.content-list ol li', function (event) {
        if (window.matchMedia('(max-width: 800px)').matches) {
            event.preventDefault();
            event.stopPropagation();
            $('.content-list').animate({right: '100%', left: '-100%', 'margin-right': '15px'}, 300);
            $('.content-preview').animate({right: '0', left: '0', 'margin-left': '0'}, 300);
        }
    });

    // ### Hide content preview
    $('.manage').on('click', '.content-preview .button-back', function (event) {
        if (window.matchMedia('(max-width: 800px)').matches) {
            event.preventDefault();
            event.stopPropagation();
            $('.content-list').animate({right: '0', left: '0', 'margin-right': '0'}, 300);
            $('.content-preview').animate({right: '-100%', left: '100%', 'margin-left': '15px'}, 300);
        }
    });

    // ### Show settings options page when swiping left on settings menu link
    $('.settings').on('click', '.settings-menu li', function (event) {
        if (window.matchMedia('(max-width: 800px)').matches) {
            event.preventDefault();
            event.stopPropagation();
            $('.settings-sidebar').animate({right: '100%', left: '-102%', 'margin-right': '15px'}, 300);
            $('.settings-content').animate({right: '0', left: '0', 'margin-left': '0'}, 300);
            $('.settings-content .button-back, .settings-content .button-save').css('display', 'inline-block');
        }
    });

    // ### Hide settings options page
    $('.settings').on('click', '.settings-content .button-back', function (event) {
        if (window.matchMedia('(max-width: 800px)').matches) {
            event.preventDefault();
            event.stopPropagation();
            $('.settings-sidebar').animate({right: '0', left: '0', 'margin-right': '0'}, 300);
            $('.settings-content').animate({right: '-100%', left: '100%', 'margin-left': '15'}, 300);
            $('.settings-content .button-back, .settings-content .button-save').css('display', 'none');
        }
    });

    // ### Toggle the sidebar menu
    $('[data-off-canvas]').on('click', function (event) {
        if (window.matchMedia('(max-width: 650px)').matches) {
            event.preventDefault();
            $('body').toggleClass('off-canvas');
        }
    });

}());<commit_msg>abstract mobile interactions js<commit_after>// # Ghost Mobile Interactions

/*global window, document, $, FastClick */

(function () {
    'use strict';

    FastClick.attach(document.body);

    // ### general wrapper to handle conditional screen size actions
    function responsiveAction(event, mediaCondition, cb) {
        if (!window.matchMedia(mediaCondition).matches) {
            return;
        }

        event.preventDefault();
        event.stopPropagation();
        cb();
    }

    // ### Show content preview when swiping left on content list
    $('.manage').on('click', '.content-list ol li', function (event) {
        responsiveAction(event, '(max-width: 800px)', function () {
            $('.content-list').animate({right: '100%', left: '-100%', 'margin-right': '15px'}, 300);
            $('.content-preview').animate({right: '0', left: '0', 'margin-left': '0'}, 300);
        });
    });

    // ### Hide content preview
    $('.manage').on('click', '.content-preview .button-back', function (event) {
        responsiveAction(event, '(max-width: 800px)', function () {
            $('.content-list').animate({right: '0', left: '0', 'margin-right': '0'}, 300);
            $('.content-preview').animate({right: '-100%', left: '100%', 'margin-left': '15px'}, 300);
        });
    });

    // ### Show settings options page when swiping left on settings menu link
    $('.settings').on('click', '.settings-menu li', function (event) {
        responsiveAction(event, '(max-width: 800px)', function () {
            $('.settings-sidebar').animate({right: '100%', left: '-102%', 'margin-right': '15px'}, 300);
            $('.settings-content').animate({right: '0', left: '0', 'margin-left': '0'}, 300);
            $('.settings-content .button-back, .settings-content .button-save').css('display', 'inline-block');
        });
    });

    // ### Hide settings options page
    $('.settings').on('click', '.settings-content .button-back', function (event) {
        responsiveAction(event, '(max-width: 800px)', function () {
            $('.settings-sidebar').animate({right: '0', left: '0', 'margin-right': '0'}, 300);
            $('.settings-content').animate({right: '-100%', left: '100%', 'margin-left': '15'}, 300);
            $('.settings-content .button-back, .settings-content .button-save').css('display', 'none');
        });
    });

    // ### Toggle the sidebar menu
    $('[data-off-canvas]').on('click', function (event) {
        responsiveAction(event, '(max-width: 650px)', function () {
            $('body').toggleClass('off-canvas');
        });
    });

}());
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import classnames from 'classnames'
import orderBy from 'lodash/orderBy'
import moment from 'moment'

import { Alert, Button, Modal, Panel } from 'react-bootstrap'

import ContentWrapper from '~/components/Layout/ContentWrapper'
import PageHeader from '~/components/Layout/PageHeader'
import Loading from '~/components/Util/Loading'
import About from './About'

import { fetchStatus, getHost, revertPendingFileChanges } from './util'

import style from './style.scss'

export default class GhostView extends Component {
  state = {
    loading: true,
    data: null,
    error: null,
    showAbout: false,
    showSyncHelp: false
  }

  showAbout = () => {
    this.setState({ showAbout: true })
  }

  hideAbout = () => {
    this.setState({ showAbout: false })
  }

  showSyncHelp = () => {
    this.setState({ showSyncHelp: true })
  }

  hideSyncHelp = () => {
    this.setState({ showSyncHelp: false })
  }

  fetch() {
    this.setState({ loading: true })

    fetchStatus()
      .then(data => {
        this.setState({ data, loading: false, error: null })
      })
      .catch(error => {
        this.setState({ error: error.message || 'Unknown', loading: false })
      })
  }

  componentDidMount() {
    this.fetch()
  }

  renderRevision({ folder, file, revision, created_on: createdOn }, index) {
    const mostRecent = index === 0 ? '(most recent)' : ''
    const timeAgo = moment.utc(createdOn).fromNow()

    return (
      <li key={`${folder}/${file}/${revision}`}>
        Revision {revision} – Edited {timeAgo} {mostRecent}

    )
  }

  revertFile(folder, file) {
    return revertPendingFileChanges(folder, file).then(() => this.fetch())
  }

  renderFile(folder, file, data) {
    data = orderBy(data, ['created_on'], ['desc'])

    const undo = () => {
      if (confirm('Are you sure you want to revert these changes? Changes will be lost and this is not reversible.')) {
        this.revertFile(folder, file)
      }
    }

    const undoLink = (

        Revert changes

    )

    return (
      <li key={`${folder}/${file}`}>

            {file} {undoLink}

{data.map((datum, i) => this.renderRevision(Object.assign({ folder }, datum), i))}

    )
  }

  renderFolder(folder, data) {
    const files = Object.keys(data).sort()

    return (
      <Panel collapsible="true" defaultExpanded>
        <Panel.Heading>{folder}</Panel.Heading>
        <Panel.Body>

{files.map(file => this.renderFile(folder, file, data[file]))}

        </Panel.Body>
      </Panel>
    )
  }

  renderContent() {
    const { data, showSyncHelp } = this.state
    const folders = Object.keys(data).sort()

    if (!folders.length) {
      return (
        <Alert bsStyle="success">

You don't have any ghost content in your DB, DB is in sync with the bot source code.

            Don't know what is ghost content?
            <Button bsSize="small" bsStyle="link" onClick={this.showAbout}>
              Read about this feature
            </Button>.

        </Alert>
      )
    }

    return (

        <Alert bsStyle="warning">

            Below is the list of ghost content present in the DB. You need to eventually sync it up with the bot source
            code.
            <Button bsSize="small" bsStyle="info" onClick={this.showSyncHelp}>
              Show me how to do it
            </Button>

            Don't know what is ghost content?
            <Button bsSize="small" bsStyle="link" onClick={this.showAbout}>
              Read about this feature
            </Button>

        </Alert>
        {showSyncHelp && (
          <Alert bsStyle="info" onDismiss={this.hideSyncHelp}>

              To pull the pending ghost content run in your bot's project folder:

              ./node_modules/.bin/botpress ghost-sync {getHost()}

          </Alert>
        )}

{folders.map(folder => this.renderFolder(folder, data[folder]))}

    )
  }

  renderBody() {
    const { loading, error } = this.state

    if (loading) {
      return <Loading />
    }

    if (error) {
      return (
        <Alert bsStyle="danger">

Error fetching status dta: {error}.

            <Button bsStyle="info" onClick={() => this.fetch()}>
              Try again
            </Button>

        </Alert>
      )
    }

    return this.renderContent()
  }

  render() {
    return (
      <ContentWrapper>
        <PageHeader>Version Control</PageHeader>
        {this.renderBody()}

        <Modal show={this.state.showAbout} onHide={this.hideAbout}>
          <Modal.Header closeButton>
            <Modal.Title>Version Control</Modal.Title>
          </Modal.Header>
          <Modal.Body>
            <About />
          </Modal.Body>
          <Modal.Footer>
            <Button onClick={this.hideAbout}>Close</Button>
          </Modal.Footer>
        </Modal>
      </ContentWrapper>
    )
  }
}
<commit_msg>feat(cloud-roles): ghost-content read-only mode<commit_after>import React, { Component } from 'react'
import { connect } from 'react-redux'
import orderBy from 'lodash/orderBy'
import moment from 'moment'

import { Alert, Button, Modal, Panel } from 'react-bootstrap'

import ContentWrapper from '~/components/Layout/ContentWrapper'
import PageHeader from '~/components/Layout/PageHeader'
import PermissionsChecker from '~/components/Layout/PermissionsChecker'
import Loading from '~/components/Util/Loading'
import About from './About'

import { fetchStatus, getHost, revertPendingFileChanges } from './util'

import style from './style.scss'

class GhostView extends Component {
  state = {
    loading: true,
    data: null,
    error: null,
    showAbout: false,
    showSyncHelp: false
  }

  showAbout = () => {
    this.setState({ showAbout: true })
  }

  hideAbout = () => {
    this.setState({ showAbout: false })
  }

  showSyncHelp = () => {
    this.setState({ showSyncHelp: true })
  }

  hideSyncHelp = () => {
    this.setState({ showSyncHelp: false })
  }

  fetch() {
    this.setState({ loading: true })

    fetchStatus()
      .then(data => {
        this.setState({ data, loading: false, error: null })
      })
      .catch(error => {
        this.setState({ error: error.message || 'Unknown', loading: false })
      })
  }

  componentDidMount() {
    this.fetch()
  }

  renderRevision({ folder, file, revision, created_on: createdOn }, index) {
    const mostRecent = index === 0 ? '(most recent)' : ''
    const timeAgo = moment.utc(createdOn).fromNow()

    return (
      <li key={`${folder}/${file}/${revision}`}>
        Revision {revision} – Edited {timeAgo} {mostRecent}

    )
  }

  revertFile(folder, file) {
    return revertPendingFileChanges(folder, file).then(() => this.fetch())
  }

  renderFile(folder, file, data) {
    data = orderBy(data, ['created_on'], ['desc'])

    const undo = () => {
      if (confirm('Are you sure you want to revert these changes? Changes will be lost and this is not reversible.')) {
        this.revertFile(folder, file)
      }
    }

    return (
      <li key={`${folder}/${file}`}>

            {file}
            <PermissionsChecker user={this.props.user} op="write" res="bot.ghost_content">
              {' '}

                Revert changes

            </PermissionsChecker>

{data.map((datum, i) => this.renderRevision(Object.assign({ folder }, datum), i))}

    )
  }

  renderFolder(folder, data) {
    const files = Object.keys(data).sort()

    return (
      <Panel collapsible="true" defaultExpanded key={folder}>
        <Panel.Heading>{folder}</Panel.Heading>
        <Panel.Body>

{files.map(file => this.renderFile(folder, file, data[file]))}

        </Panel.Body>
      </Panel>
    )
  }

  renderContent() {
    const { data, showSyncHelp } = this.state
    const folders = Object.keys(data).sort()

    if (!folders.length) {
      return (
        <Alert bsStyle="success">

You don't have any ghost content in your DB, DB is in sync with the bot source code.

            Don't know what is ghost content?
            <Button bsSize="small" bsStyle="link" onClick={this.showAbout}>
              Read about this feature
            </Button>.

        </Alert>
      )
    }

    return (

        <Alert bsStyle="warning">

            Below is the list of ghost content present in the DB. You need to eventually sync it up with the bot source
            code.
            <Button bsSize="small" bsStyle="info" onClick={this.showSyncHelp}>
              Show me how to do it
            </Button>

            Don't know what is ghost content?
            <Button bsSize="small" bsStyle="link" onClick={this.showAbout}>
              Read about this feature
            </Button>

        </Alert>
        {showSyncHelp && (
          <Alert bsStyle="info" onDismiss={this.hideSyncHelp}>

              To pull the pending ghost content run in your bot's project folder:

              ./node_modules/.bin/botpress ghost-sync {getHost()}

          </Alert>
        )}

{folders.map(folder => this.renderFolder(folder, data[folder]))}

    )
  }

  renderBody() {
    const { loading, error } = this.state

    if (loading) {
      return <Loading />
    }

    if (error) {
      return (
        <Alert bsStyle="danger">

Error fetching status dta: {error}.

            <Button bsStyle="info" onClick={() => this.fetch()}>
              Try again
            </Button>

        </Alert>
      )
    }

    return this.renderContent()
  }

  render() {
    return (
      <ContentWrapper>
        <PageHeader>Version Control</PageHeader>
        {this.renderBody()}

        <Modal show={this.state.showAbout} onHide={this.hideAbout}>
          <Modal.Header closeButton>
            <Modal.Title>Version Control</Modal.Title>
          </Modal.Header>
          <Modal.Body>
            <About />
          </Modal.Body>
          <Modal.Footer>
            <Button onClick={this.hideAbout}>Close</Button>
          </Modal.Footer>
        </Modal>
      </ContentWrapper>
    )
  }
}

const mapStateToProps = state => ({
  user: state.user
})

export default connect(mapStateToProps)(GhostView)
<|endoftext|>