<commit_before>"use strict";

const fs = require("fs");
const path = require("path");
const http = require("http");

const express = require("express");
const morgan = require("morgan");
const compression = require("compression");
const responseTime = require("response-time");
const cors = require("cors");

const mongodb = require("mongodb").MongoClient;
const JSONStream = require("JSONStream")

const debug = require("debug")(`mqttdb:httpd:${process.id || process.pid}`);
//const debug = console.log.bind(console);

const config = require("config").get("mqttdb");

// default config
const appPort = config.http.port || 3000;
const appHost = config.http.host || "127.0.0.1";

const dbURL = config.db.url || "mongodb://localhost/mqttdb";
const dbOptions = config.db.options || {};
const dbCollection = config.db.collection || "mqttdb";

// TODO: no longer required. maybe remove this
module.exports = () => {

// TODO: "Topology was destroyed" errors not handled, reconnect

mongodb.connect(dbURL, dbOptions)
.then((db) => new Promise((resolve, reject) => {
	debug("MongoDB connected");

	// db.collection() requires a callback in strict-mode
	// TODO: think again. maybe there's a reason to it
	db.collection(dbCollection, {strict: true}, (err, coll) => {
		if (err) {
			reject(err);
		} else {
			resolve(coll);
		}
	});
}))
.then((coll) => {
	const router = express.Router();

	// /combine/topic1,topic2,...              - generate JSON array ESI template
	// /topic                                  - retrieve full data set
	// /topic/past/300000                      - retrieve latest data
	// /topic/diff/1483228800000               - retrieve new data since timestamp
	// /topic/diff/1483228800000/1483230800000 - retrieve data between timestamps

	// no support for mqtt topics starting with combine/
	// no full support for mqtt topics ending in /past/nnn or /diff/nnn
	// /combine topic character set restricted to /[a-z0-9_/-]/i

	// allow any Origin
	router.use(cors({origin: true}));

	// content is always JSON
	router.use((req, res, next) => {
		res.set("Content-Type", "application/json; charset=utf-8");
		next();
	});

	// generate ESI template
	router.get("/combine/*", (req, res) => {
		debug("Combine:", req.params[0]);

		// TODO: force compression even on short response
		// because compression middleware adds Vary: Accept-Encoding
		// without compressing the response, varnish un-gzips all sub-requests
		// see https://github.com/expressjs/compression#threshold

		// Cache-Control for the combined set. no Last-Modified
		// v-maxage controls varnish cache TTL for the template only
		res.set({
			"Surrogate-Control": "ESI/1.0",
			"Cache-Control": "max-age=10, v-maxage=3600, public"
		});

		// TODO: think again about ESI/XSS. allow only a restricted character set for now
		res.send(`[${req.params[0].split(",").map(key => `<esi:include src="/${key.replace(/[^a-z0-9_/-]+/gi, "")}"/>`).join(",")}]`);
	});

	// retrieve data
	router.get([
		"/:topic(*)/:cmd(diff)/:base([0-9]+)/:end([0-9]+)",
		"/:topic(*)/:cmd(diff|past)/:base([0-9]+)",
		"/:topic(*)"
	], (req, res, next) => {
		let query = {topic: req.params.topic};
		let base = undefined;

		switch (req.params.cmd) {
			case "diff":
				debug("Diff:", req.params.topic, req.params.base, req.params.end);
				base = new Date(parseInt(req.params.base));
				query.createdAt = {$gt: base};
				if (req.params.end) {
					query.createdAt.$lt = new Date(parseInt(req.params.end));
				}
				break;

			case "past":
				debug("Past:", req.params.topic, req.params.base);
				query.createdAt = {$gt: new Date(Date.now() - parseInt(req.params.base))};
				break;

			default:
				debug("Full:", query.topic);
				break;
		}

		let cursor = coll.find(query, {topic: 0, _id: 0}).sort({createdAt: 1});

		cursor.hasNext()
		.then((hasNext) => {
			if (hasNext) {
				// client loops over cached diffs, so the proxy can keep them
				res.set("Cache-Control", "max-age=120, s-maxage=300, public");

				// TODO: no Last-Modified header. maybe fetch last record first
				// or rewrite frontend to use reverse sort order

				// TODO: writing every record as separate HTTP chunk. performance?
				// maybe put a corking buffer between the pipes
				// varnish un-chunks & gzips the response, anyway

				cursor.stream({transform: doc => {
					try {
						return [doc.createdAt.getTime(), doc.message]
					} catch(err) {
						debug("Error in result stream:", err);
						return undefined;
					}
				}})
				.pipe(JSONStream.stringify())
				.pipe(res);
			} else {
				// debug("No results");

				// enable conditional requests on empty diffs
				// TODO: think again: why?
				// - conditional headers are bigger than []
				// + varnish can re-use the same cache object
				base && res.set("Last-Modified", base.toUTCString());

				// short negative-cache TTL
				res.set("Cache-Control", "s-maxage=10");
				res.send("[]");
			}
		})
		.catch(next);
	});

	// return errors as JSON
	router.use((err, req, res, next) => {
		debug("Error in response:", err);

		if (res.headersSent) {
			return next(err)
		}

		res.status(500).set("Cache-Control", "no-cache").json({error: err.toString()});
	});

	return router;
})
.then((router) => {
	const app = express();

	app.disable("etag"); // use Last-Modified only
	app.disable("query parser");
	app.disable("x-powered-by");
	app.enable("strict routing");

	// record the response time
	app.use(responseTime());

	// compress some responses
	app.use(compression());

	// write access log
	// TODO: think about logrotate & make file path configurable
	app.use(morgan("combined", {
		stream: fs.createWriteStream(path.join(__dirname, "log", "access.log"), {flags: "a"})
	}));

	// mount mqttdb router
	app.use("/", router);

	// TODO: cluster IPC disconnect should kill the server(s) after timeout
	return Promise.all((Array.isArray(appHost) ? appHost : [appHost]).map(host => new Promise((resolve, reject) => app.listen(appPort, host, resolve).on("error", reject))));

/*
	return Promise.all((Array.isArray(appHost) ? appHost : [appHost]).map(host => new Promise((resolve, reject) => {
		const server = http.createServer(app);

		server.on("listening", resolve);

		server.on("error", (err) => {
			if (err.code == "EADDRINUSE") {
				let retry = 1000 + Math.floor(Math.random() * 1000);
				debug(`Address ${host}:${appPort} already in use. Retrying in ${retry}ms`);
				setTimeout(() => {
					server.close(() => server.listen(appPort, host));
				}, retry);
			} else {
				// TODO: think again. "error" could occur after "listening"
				reject(err);
			}
		})

		return server.listen(appPort, host);
	})));
*/
})
.then(() => {
	debug("All listeners started");
})
.catch((err) => {
	if (err.code == "EADDRINUSE") {
		// ignore EADDRINUSE
		debug(`Address ${err.address}:${err.port} already in use`);
	} else {
		debug("Fatal error");
		debug(err.toString());
		console.error(err);
		process.exit(1);
	}
});

};

// run stand-alone if not require'd
if (require.main === module) {
	module.exports();
} else {
	debug(`Worker #${process.id || 0} started with PID ${process.pid}`);
}
<commit_msg>empty response: use "ETag: empty-json-array" instead of Last-Modified<commit_after>"use strict";

const fs = require("fs");
const path = require("path");
const http = require("http");

const express = require("express");
const morgan = require("morgan");
const compression = require("compression");
const responseTime = require("response-time");
const cors = require("cors");

const mongodb = require("mongodb").MongoClient;
const JSONStream = require("JSONStream")

const debug = require("debug")(`mqttdb:httpd:${process.id || process.pid}`);
//const debug = console.log.bind(console);

const config = require("config").get("mqttdb");

// default config
const appPort = config.http.port || 3000;
const appHost = config.http.host || "127.0.0.1";

const dbURL = config.db.url || "mongodb://localhost/mqttdb";
const dbOptions = config.db.options || {};
const dbCollection = config.db.collection || "mqttdb";

// TODO: no longer required. maybe remove this
module.exports = () => {

// TODO: "Topology was destroyed" errors not handled, reconnect

mongodb.connect(dbURL, dbOptions)
.then((db) => new Promise((resolve, reject) => {
	debug("MongoDB connected");

	// db.collection() requires a callback in strict-mode
	// TODO: think again. maybe there's a reason to it
	db.collection(dbCollection, {strict: true}, (err, coll) => {
		if (err) {
			reject(err);
		} else {
			resolve(coll);
		}
	});
}))
.then((coll) => {
	const router = express.Router();

	// /combine/topic1,topic2,...              - generate JSON array ESI template
	// /topic                                  - retrieve full data set
	// /topic/past/300000                      - retrieve latest data
	// /topic/diff/1483228800000               - retrieve new data since timestamp
	// /topic/diff/1483228800000/1483230800000 - retrieve data between timestamps

	// no support for mqtt topics starting with combine/
	// no full support for mqtt topics ending in /past/nnn or /diff/nnn
	// /combine topic character set restricted to /[a-z0-9_/-]/i

	// allow any Origin
	router.use(cors({origin: true}));

	// content is always JSON
	router.use((req, res, next) => {
		res.set("Content-Type", "application/json; charset=utf-8");
		next();
	});

	// generate ESI template
	router.get("/combine/*", (req, res) => {
		debug("Combine:", req.params[0]);

		// TODO: force compression even on short response
		// because compression middleware adds Vary: Accept-Encoding
		// without compressing the response, varnish un-gzips all sub-requests
		// see https://github.com/expressjs/compression#threshold

		// Cache-Control for the combined set. no Last-Modified
		// v-maxage controls varnish cache TTL for the template only
		res.set({
			"Surrogate-Control": "ESI/1.0",
			"Cache-Control": "max-age=10, v-maxage=3600, public"
		});

		// TODO: think again about ESI/XSS. allow only a restricted character set for now
		res.send(`[${req.params[0].split(",").map(key => `<esi:include src="/${key.replace(/[^a-z0-9_/-]+/gi, "")}"/>`).join(",")}]`);
	});

	// retrieve data
	router.get([
		"/:topic(*)/:cmd(diff)/:base([0-9]+)/:end([0-9]+)",
		"/:topic(*)/:cmd(diff|past)/:base([0-9]+)",
		"/:topic(*)"
	], (req, res, next) => {
		let query = {topic: req.params.topic};

		switch (req.params.cmd) {
			case "diff":
				debug("Diff:", req.params.topic, req.params.base, req.params.end);
				query.createdAt = {$gt: new Date(parseInt(req.params.base))};
				if (req.params.end) {
					query.createdAt.$lt = new Date(parseInt(req.params.end));
				}
				break;

			case "past":
				debug("Past:", req.params.topic, req.params.base);
				query.createdAt = {$gt: new Date(Date.now() - parseInt(req.params.base))};
				break;

			default:
				debug("Full:", query.topic);
				break;
		}

		let cursor = coll.find(query, {topic: 0, _id: 0}).sort({createdAt: 1});

		cursor.hasNext()
		.then((hasNext) => {
			if (hasNext) {
				// client loops over cached diffs, so the proxy can keep them
				res.set("Cache-Control", "max-age=120, s-maxage=300, public");

				// TODO: no Last-Modified header. maybe fetch last record first
				// or rewrite frontend to use reverse sort order

				// TODO: writing every record as separate HTTP chunk. performance?
				// maybe put a corking buffer between the pipes
				// varnish un-chunks & gzips the response, anyway

				cursor.stream({transform: doc => {
					try {
						return [doc.createdAt.getTime(), doc.message]
					} catch(err) {
						debug("Error in result stream:", err);
						return undefined;
					}
				}})
				.pipe(JSONStream.stringify())
				.pipe(res);
			} else {
				// debug("No results");

				// enable conditional requests on empty diffs
				// TODO: think again: why?
				// - conditional headers are bigger than []
				// + varnish can re-use the same cache object
				res.set("ETag", "empty-json-array");

				// short negative-cache TTL
				res.set("Cache-Control", "s-maxage=10");
				res.send("[]");
			}
		})
		.catch(next);
	});

	// return errors as JSON
	router.use((err, req, res, next) => {
		debug("Error in response:", err);

		if (res.headersSent) {
			return next(err)
		}

		res.status(500).set("Cache-Control", "no-cache").json({error: err.toString()});
	});

	return router;
})
.then((router) => {
	const app = express();

	app.disable("etag"); // use Last-Modified only
	app.disable("query parser");
	app.disable("x-powered-by");
	app.enable("strict routing");

	// record the response time
	app.use(responseTime());

	// compress some responses
	app.use(compression());

	// write access log
	// TODO: think about logrotate & make file path configurable
	app.use(morgan("combined", {
		stream: fs.createWriteStream(path.join(__dirname, "log", "access.log"), {flags: "a"})
	}));

	// mount mqttdb router
	app.use("/", router);

	// TODO: cluster IPC disconnect should kill the server(s) after timeout
	return Promise.all((Array.isArray(appHost) ? appHost : [appHost]).map(host => new Promise((resolve, reject) => app.listen(appPort, host, resolve).on("error", reject))));

/*
	return Promise.all((Array.isArray(appHost) ? appHost : [appHost]).map(host => new Promise((resolve, reject) => {
		const server = http.createServer(app);

		server.on("listening", resolve);

		server.on("error", (err) => {
			if (err.code == "EADDRINUSE") {
				let retry = 1000 + Math.floor(Math.random() * 1000);
				debug(`Address ${host}:${appPort} already in use. Retrying in ${retry}ms`);
				setTimeout(() => {
					server.close(() => server.listen(appPort, host));
				}, retry);
			} else {
				// TODO: think again. "error" could occur after "listening"
				reject(err);
			}
		})

		return server.listen(appPort, host);
	})));
*/
})
.then(() => {
	debug("All listeners started");
})
.catch((err) => {
	if (err.code == "EADDRINUSE") {
		// ignore EADDRINUSE
		debug(`Address ${err.address}:${err.port} already in use`);
	} else {
		debug("Fatal error");
		debug(err.toString());
		console.error(err);
		process.exit(1);
	}
});

};

// run stand-alone if not require'd
if (require.main === module) {
	module.exports();
} else {
	debug(`Worker #${process.id || 0} started with PID ${process.pid}`);
}
<|endoftext|>


<commit_before>/**
 * Creates an element
 * @param  {string} element
 */

export const addElement = (element) => document.createElement(element)

/**
 * Removes an element from DOM
 * @param  {string} element
 */

export const removeElement = (element) => element.parentNode.removeChild(element)

/**
 * Adds an element inside another element as last child
 * @param  {node} element
 * @param  {node} parent
 */

export const append = (element, parent) => {
  let parentNode = document.querySelector(parent);
  return parentNode.appendChild(element)
}

/**
 * Adds an element inside another element as first child
 * @param  {node} element
 * @param  {node} parent
 */

export const prepend = (element, parent) => parent.insertBefore(element, parent.firstChild)

/**
 * Adds a class to an element
 * @param  {string} className
 * @param  {node} element
 */

export const addClass = (className, element) => {
  if (element.classList) {
    return element.classList.add(className);
  } else {
    return element.className += ' ' + className;
  }
}

/**
 * Removes a class from an element
 * @param  {string} className
 * @param  {node} element
 */

export const removeClass = (className, element) => {
  if (element.classList) {
    return element.classList.remove(className);
  } else {
    return element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
  }
}

/**
 * Adds an attribute or more attributes to an element
 * @param  {object} attributes
 * @param  {node} element
 */

export const addAttribute = (element, attributes) => {
  return attributes.map( function(attr) {
    return element.setAttribute(attr.name, attr.value)
  } )
}

/**
 * Removes an attribute from element
 * @param  {string} attributeName
 * @param  {node} element
 */

export const removeAttribute = (attributeName, element) => element.removeAttribute(attributeName)
<commit_msg>Added utils elementChecker to dom module<commit_after>import { elementCheck } from '../utils/script.js'

/**
 * Creates an element
 * @param  {string} element
 */

export const addElement = (element) => document.createElement(element)

/**
 * Removes an element from DOM
 * @param  {string} element
 */

export const removeElement = (element) => element.parentNode.removeChild(element)

/**
 * Adds an element inside another element as last child
 * @param  {node} element
 * @param  {node} parent
 */

export const append = (element, parent) => {
  let parentNode = document.querySelector(parent);
  return parentNode.appendChild(element)
}

/**
 * Adds an element inside another element as first child
 * @param  {node} element
 * @param  {node} parent
 */

export const prepend = (element, parent) => parent.insertBefore(element, parent.firstChild)

/**
 * Adds a class to an element
 * @param  {string} className
 * @param  {node} element
 */

export const addClass = (className, element) => {
  if (element.classList) {
    return element.classList.add(className);
  } else {
    return element.className += ' ' + className;
  }
}

/**
 * Removes a class from an element
 * @param  {string} className
 * @param  {node} element
 */

export const removeClass = (className, element) => {
  // done late night, to be tested
  let myElement = elementCheck( element );

  if (element.classList) {
    return element.classList.remove(className);
  } else {
    return element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
  }
}

/**
 * Adds an attribute or more attributes to an element
 * @param  {object} attributes
 * @param  {node} element
 */

export const addAttribute = (element, attributes) => {
  return attributes.map( function(attr) {
    return element.setAttribute(attr.name, attr.value)
  } )
}

/**
 * Removes an attribute from element
 * @param  {string} attributeName
 * @param  {node} element
 */

export const removeAttribute = (attributeName, element) => element.removeAttribute(attributeName)
<|endoftext|>


<commit_before>'use strict';

/*!
 * snakeskin.github.io
 * https://github.com/SnakeskinTpl/snakeskin.github.io
 *
 * Released under the MIT license
 * https://github.com/SnakeskinTpl/snakeskin.github.io/blob/master/LICENSE
 */

$(() => {
	$('.b-article').find('h2, h3, h4, h5, h6').each((i, el) => {
		el.id = `${$(el).closest('.b-article').attr('id')}--${el.textContent.trim().replace(/\s|\.|\$/g, '_')}`;
	});

	let rootTarget;

	const
		prev = $('.b-articles__prev'),
		next = $('.b-articles__next');

	prev.on('click', () => {
		if (!rootTarget) {
			return;
		}

		location.hash = rootTarget.prev('.b-article').attr('id');
	});

	next.on('click', () => {
		if (!rootTarget) {
			return;
		}

		location.hash = rootTarget.next('.b-article').attr('id');
	});

	$(document)
		.on('click', 'h2[id], h3[id], h4[id], h5[id], h6[id]', (e) => {
			location.hash = e.target.id;
		})

		.on('keypress', (e) => {
			if (!e.altKey || !{37: true, 39: true}[e.keyCode]) {
				return;
			}

			e.preventDefault();
			if (e.keyCode === 37 && !prev.hasClass('b-articles__nav-cont_hidden_true')) {
				prev.trigger('click');

			} else if (e.keyCode === 39 && !next.hasClass('b-articles__nav-cont_hidden_true')) {
				next.trigger('click');
			}
		});

	const
		contents = $('.b-contents');

	function emptyHash() {
		let
			hash = decodeURIComponent(location.hash),
			target = hash && $(hash);

		function setArticle(target) {
			if (!target.length) {
				target = $('.b-article:eq(0)');
				hash = target.attr('id');
			}

			$('.b-article').removeClass('b-article_active_true');
			target.addClass('b-article_active_true');
			rootTarget = target;

			const
				id = target.attr('id'),
				link = contents.find(`.b-contents__link[href='#${id}']`);

			contents
				.find('.b-contents__link')
				.removeClass('b-contents__link_active_true')
				.closest('li')
				.find('.b-contents__sub')
				.remove();

			link
				.addClass('b-contents__link_active_true');

			let
				sub = $('
'),
				pos = 2;

			target.find('h2, h3').each((i, el) => {
				const
					rank = Number(/\d+/.exec(el.tagName.toLowerCase())[0]);

				if (rank > pos) {
					const tmp = $('

');
					sub.append(tmp);
					sub = tmp.find('ul');

				} else if (rank < pos) {
					sub = sub.parent().closest('ul');
				}

				if (pos !== rank) {
					pos = rank;
				}

				sub.append(
					$(`

							${el.textContent}

					`)
				);
			});

			console.log(sub[0]);

			link
				.closest('li')
				.append(sub.is('.b-contents__sub') ? sub : sub.closest('.b-contents__sub'));

			if (id === $('.b-article:eq(0)').attr('id')) {
				prev.addClass('b-articles__nav-cont_hidden_true');

			} else {
				prev.removeClass('b-articles__nav-cont_hidden_true');
			}

			if (id === $('.b-article:eq(-1)').attr('id')) {
				next.addClass('b-articles__nav-cont_hidden_true');

			} else {
				next.removeClass('b-articles__nav-cont_hidden_true');
			}
		}

		const
			win = $(window),
			duration = 250;

		if (!hash || target.hasClass('b-article')) {
			setArticle(target);
			win.scrollTo(0, duration);

		} else {
			setArticle(target.closest('.b-article'));
			target.length && win.scrollTo(target.offset().top, duration);
		}

		if (hash !== location.hash) {
			location.hash = hash;
		}
	}

	$(window).on('hashchange', (emptyHash(), emptyHash));
});
<commit_msg>Removed console.log<commit_after>'use strict';

/*!
 * snakeskin.github.io
 * https://github.com/SnakeskinTpl/snakeskin.github.io
 *
 * Released under the MIT license
 * https://github.com/SnakeskinTpl/snakeskin.github.io/blob/master/LICENSE
 */

$(() => {
	$('.b-article').find('h2, h3, h4, h5, h6').each((i, el) => {
		el.id = `${$(el).closest('.b-article').attr('id')}--${el.textContent.trim().replace(/\s|\.|\$/g, '_')}`;
	});

	let rootTarget;

	const
		prev = $('.b-articles__prev'),
		next = $('.b-articles__next');

	prev.on('click', () => {
		if (!rootTarget) {
			return;
		}

		location.hash = rootTarget.prev('.b-article').attr('id');
	});

	next.on('click', () => {
		if (!rootTarget) {
			return;
		}

		location.hash = rootTarget.next('.b-article').attr('id');
	});

	$(document)
		.on('click', 'h2[id], h3[id], h4[id], h5[id], h6[id]', (e) => {
			location.hash = e.target.id;
		})

		.on('keypress', (e) => {
			if (!e.altKey || !{37: true, 39: true}[e.keyCode]) {
				return;
			}

			e.preventDefault();
			if (e.keyCode === 37 && !prev.hasClass('b-articles__nav-cont_hidden_true')) {
				prev.trigger('click');

			} else if (e.keyCode === 39 && !next.hasClass('b-articles__nav-cont_hidden_true')) {
				next.trigger('click');
			}
		});

	const
		contents = $('.b-contents');

	function emptyHash() {
		let
			hash = decodeURIComponent(location.hash),
			target = hash && $(hash);

		function setArticle(target) {
			if (!target.length) {
				target = $('.b-article:eq(0)');
				hash = target.attr('id');
			}

			$('.b-article').removeClass('b-article_active_true');
			target.addClass('b-article_active_true');
			rootTarget = target;

			const
				id = target.attr('id'),
				link = contents.find(`.b-contents__link[href='#${id}']`);

			contents
				.find('.b-contents__link')
				.removeClass('b-contents__link_active_true')
				.closest('li')
				.find('.b-contents__sub')
				.remove();

			link
				.addClass('b-contents__link_active_true');

			let
				sub = $('
'),
				pos = 2;

			target.find('h2, h3').each((i, el) => {
				const
					rank = Number(/\d+/.exec(el.tagName.toLowerCase())[0]);

				if (rank > pos) {
					const tmp = $('

');
					sub.append(tmp);
					sub = tmp.find('ul');

				} else if (rank < pos) {
					sub = sub.parent().closest('ul');
				}

				if (pos !== rank) {
					pos = rank;
				}

				sub.append(
					$(`

							${el.textContent}

					`)
				);
			});

			link
				.closest('li')
				.append(sub.is('.b-contents__sub') ? sub : sub.closest('.b-contents__sub'));

			if (id === $('.b-article:eq(0)').attr('id')) {
				prev.addClass('b-articles__nav-cont_hidden_true');

			} else {
				prev.removeClass('b-articles__nav-cont_hidden_true');
			}

			if (id === $('.b-article:eq(-1)').attr('id')) {
				next.addClass('b-articles__nav-cont_hidden_true');

			} else {
				next.removeClass('b-articles__nav-cont_hidden_true');
			}
		}

		const
			win = $(window),
			duration = 250;

		if (!hash || target.hasClass('b-article')) {
			setArticle(target);
			win.scrollTo(0, duration);

		} else {
			setArticle(target.closest('.b-article'));
			target.length && win.scrollTo(target.offset().top, duration);
		}

		if (hash !== location.hash) {
			location.hash = hash;
		}
	}

	$(window).on('hashchange', (emptyHash(), emptyHash));
});
<|endoftext|>


<commit_before>import React from 'react'
import { View, StyleSheet, Text } from 'react-native'

import FontAwesome from 'react-native-vector-icons/FontAwesome';

const PostTextBox = (props) => {
  const { children } = props
  return (
          <View style={styles.postTextContainer}>
            <Text style={styles.postText }> {children.get('message')} </Text>
            <View style={styles.postReplyContainer}>
              <Text >
                <FontAwesome style={styles.postReplyIcon} name="commenting-o"  />
              </Text>
              <Text style={styles.postReplyNumber}> {children.get('replies').count()} </Text>
            </View>
          </View>
  )
}

var styles = StyleSheet.create({
  container: {
    padding : 15,
    marginBottom :6,
    flexDirection : 'row'
  },
  postTextContainer : {
    flex : 4
  },
  postText : {
    fontSize: 25,
    color: 'white',
  },
  postRatingContainer : {
    flex : 1,
    flexDirection : 'column',
    justifyContent : 'center',
    alignItems : 'center'
  },
  postRatingButtons : {
    fontSize : 50,
    color : '#4F8EF7'
  },
  postRatingValue : {
    fontSize : 30,
    color : '#4F8EF7'
  },
  postReplyContainer : {
    flexDirection : 'column',
  },
  postReplyIcon : {
    fontSize : 10,
  },
  postReplyNumber : {
    fontSize : 10,
  }
});

export default PostTextBox
<commit_msg>post.get ist not a function error<commit_after>import React from 'react'
import { View, StyleSheet, Text } from 'react-native'

import FontAwesome from 'react-native-vector-icons/FontAwesome';

const PostTextBox = (props) => {
  const { children } = props
  return (
          <View style={styles.postTextContainer}>
            <Text style={styles.postText }> {children.get('message')} </Text>
            <View style={styles.postReplyContainer}>
              <Text >
                <FontAwesome style={styles.postReplyIcon} name="commenting-o"  />
              </Text>
              <Text style={styles.postReplyNumber}> {children.get('replies')} </Text>
            </View>
          </View>
  )
}

var styles = StyleSheet.create({
  container: {
    padding : 15,
    marginBottom :6,
    flexDirection : 'row'
  },
  postTextContainer : {
    flex : 4
  },
  postText : {
    fontSize: 25,
    color: 'white',
  },
  postRatingContainer : {
    flex : 1,
    flexDirection : 'column',
    justifyContent : 'center',
    alignItems : 'center'
  },
  postRatingButtons : {
    fontSize : 50,
    color : '#4F8EF7'
  },
  postRatingValue : {
    fontSize : 30,
    color : '#4F8EF7'
  },
  postReplyContainer : {
    flexDirection : 'column',
  },
  postReplyIcon : {
    fontSize : 10,
  },
  postReplyNumber : {
    fontSize : 10,
  }
});

export default PostTextBox
<|endoftext|>


<commit_before>import { ADD_ENTITY } from '../constants/ActionTypes';

function cmsAppState(state = [], action) {
  switch (action.type) {
    case ADD_ENTITY:
      return [
        ...state, {
          id: action.id,
          text: action.text,
          published: action.published
        }
      ];
    default:
      return state;
  }
}

import expect from 'expect';
import deepFreeze from 'deep-freeze';

const testAddEntity = () => {
  const stateBefore = [];
  const action = {
    type: ADD_ENTITY,
    id: 0,
    text: 'Cms String',
    published: false
  };
  const stateAfter = [
    {
      id: 0,
      text: 'Cms String',
      published: false
    }
  ];

  deepFreeze(stateBefore);
  deepFreeze(action);

  expect(
    cmsAppState(stateBefore, action)
  ).toEqual(stateAfter);
  console.log('Passed!');
};

testAddEntity();
export default cmsAppState;
<commit_msg>Add action to toggle entity<commit_after>import { ADD_ENTITY } from '../constants/ActionTypes';

function cmsAppState(state = [], action) {
  switch (action.type) {
    case ADD_ENTITY:
      return [
        ...state, {
          id: action.id,
          text: action.text,
          published: action.published
        }
      ];
    case 'TOGGLE_ENTITY':
      return state.map(entity => {
        if (entity.id !== action.id) {
          return entity;
        }
        return {
          ...entity,
          published: !entity.published
        };
      });
    default:
      return state;
  }
}
export default cmsAppState;

import expect from 'expect';
import deepFreeze from 'deep-freeze';

const testAddEntity = () => {
  const stateBefore = [];
  const action = {
    type: ADD_ENTITY,
    id: 0,
    text: 'Cms String',
    published: false
  };
  const stateAfter = [
    {
      id: 0,
      text: 'Cms String',
      published: false
    }
  ];

  deepFreeze(stateBefore);
  deepFreeze(action);

  expect(
    cmsAppState(stateBefore, action)
  ).toEqual(stateAfter);
};

const testToggleEntity = () => {
  const stateBefore = [
    {
      id: 0,
      text: 'Some entity',
      published: false
    },
    {
      id: 1,
      text: 'Another entity',
      published: false
    }
  ];
  const action = {
    type: 'TOGGLE_ENTITY',
    id: 1
  };
  const stateAfter = [
    {
      id: 0,
      text: 'Some entity',
      published: false
    },
    {
      id: 1,
      text: 'Another entity',
      published: true
    }
  ];

  deepFreeze(stateBefore);
  deepFreeze(action);

  expect(
    cmsAppState(stateBefore, action)
  ).toEqual(stateAfter);
};

testAddEntity();
testToggleEntity();
console.log('Passed!');
<|endoftext|>


<commit_before>// Don't change or delete this line! It waits until the DOM has loaded, then calls
// the start function. More info:
// https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded
document.addEventListener('DOMContentLoaded', start)

function start () {
  // The first example is done for you. This will change the background colour of the first div
  // when you mouse over it.
  one()
  two()
  three()
// Your turn! Create a new function called `two`, then call it from here.
}

function one () {
  // First, we have to find the element:
  var one = document.getElementById('one')

  // Next, we add an event listener to it:
  one.addEventListener('mouseenter', makeBlue)

  // Finally, we add one to make the colour white again
  one.addEventListener('mouseleave', makeWhite)
}
// CREATE FUNCTION two HERE
function two() {
  two.addEventListener('mouseenter', makeGreen)
  two.addEventListener('mouseleave', makeWhite)
}
// CREATE FUNCTION three HERE
function three() {
  three.addEventListener('mouseenter', makeRed)
  three.addEventListener('mouseleave', makeWhite)

}
// CREATE FUNCTION four HERE

// Changes the background color of event's target
function makeBlue (evt) {
  evt.target.style.backgroundColor = 'blue'
}

function makeWhite (evt) {
  evt.target.style.backgroundColor = 'white'
}
<commit_msg>assignment 5.2 completed<commit_after>// Don't change or delete this line! It waits until the DOM has loaded, then calls
// the start function. More info:
// https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded
document.addEventListener('DOMContentLoaded', start)

function start () {
  // The first example is done for you. This will change the background colour of the first div
  // when you mouse over it.
  one()
  two()
  three()
// Your turn! Create a new function called `two`, then call it from here.
}

function one () {
  // First, we have to find the element:
  var one = document.getElementById('one')

  // Next, we add an event listener to it:
  one.addEventListener('mouseenter', makeBlue)

  // Finally, we add one to make the colour white again
  one.addEventListener('mouseleave', makeWhite)
}
// CREATE FUNCTION two HERE
function two() {
  two.addEventListener('mouseenter', makeGreen)
  two.addEventListener('mouseleave', makeWhite)
}
// CREATE FUNCTION three HERE
function three() {
  three.addEventListener('mouseenter', makeRed)
  three.addEventListener('mouseleave', makeWhite)

}
// CREATE FUNCTION four HERE
function four() {
  four.addEventListener('click', makeBlack)
  four.addEventListener('click', makeWhite)

}
// Changes the background color of event's target
function makeBlue (evt) {
  evt.target.style.backgroundColor = 'blue'
}

function makeWhite (evt) {
  evt.target.style.backgroundColor = 'white'
}
<|endoftext|>


<commit_before>/**
    _moon.Drawer_, a control designed for use with [moon.Drawers](#moon.Drawers),
    consists of two drawers and a [moon.DrawerHandle](#moon.DrawerHandle).
    The main drawer is populated with any child components that are specified in
    the constructor; the optional second drawer (_controlDrawer_) is populated
    with components passed into the _controlDrawerComponents_ property.

    If the second drawer has no components, the main drawer will take up the full
    height of the containing view; otherwise, its height will be equal to the
    height of the containing view minus the height of the _controlDrawerComponents_.

    A call to _toggleDrawer()_ will open the _controlDrawer_ if
    _controlDrawerComponents_ is non-empty; otherwise, it will open the main
    drawer.

    The control's child components may be of any kind.

        {
            name: "musicDrawer",
            kind: "moon.Drawer",
            handle: {kind: "moon.DrawerHandle", content: "Handle"},
            components: [
                {content: "Drawer Content"}
            ],
            controlDrawerComponents: [
                {content: "Controls"}
            ]
        }
*/
enyo.kind({
	name: "moon.Drawer",
	kind:"enyo.Control",
	//* @protected
	classes: "moon-drawer moon-neutral",
	//* @public
	published: {
		//* Components that are to be placed in the control drawer
		controlDrawerComponents: null,
		//* Typically set to an instance of _moon.DrawerHandle_
		handle: null,
		//* Visibility state of the main drawer
		open: false,
		//* Visibility state of the control drawer
		controlsOpen: false
	},
	events: {
		//* Fires when either the main drawer or the control drawer is activated.
		onActivate: "",
		//* Fires when either the main drawer or the control drawer is deactivated.
		onDeactivate: ""
	},
	//* @protected
	handlers: {
		//* Handler for initial rendering event
		onDrawersRendered: "drawersRendered",
		//* Handler for initial resizing event to size drawers to full screen
		onDrawersResized: "drawersResized"
	},
	components: [
		{name: "client", kind: "moon.FullScreenDrawer", resizeContainer:false},
		{name: "controlDrawer", kind: "enyo.Drawer", resizeContainer:false}
	],
	create: function() {
		this.inherited(arguments);
		this.$.controlDrawer.createComponents(this.controlDrawerComponents, {owner:this.owner});
		//* Todo: remove padding on client
		this.$.client.$.client.addClass('moon-drawer-client');
		this.$.controlDrawer.$.client.addClass('moon-drawer-partial-client');
	},
	drawersRendered: function(inSender, inEvent) {
		this.$.client.setDrawerProps({height: this.calcDrawerHeight(inEvent.drawersHeight)});
		this.openChanged();
		if (!this.controlsOpen) {
			this.$.controlDrawer.setAnimated(false);
			this.$.controlDrawer.setOpen(this.controlsOpen);
			this.$.controlDrawer.setAnimated(true);
		}
	},
	calcDrawerHeight: function(drawersHeight) {
		var clientHeight = drawersHeight;
		if (this.controlDrawerComponents == null) {
			return clientHeight;
		} else {
			this.controlDrawerHeight = (this.controlDrawerHeight) ? this.controlDrawerHeight : this.$.controlDrawer.hasNode().getBoundingClientRect().height;
			return (clientHeight - this.controlDrawerHeight);
		}
	},
	toggleDrawer: function() {
		if (this.controlDrawerComponents == null) {
			this.setOpen(!this.open);
		} else {
			this.setControlsOpen(!this.controlsOpen);
		}
		return true;
	},
	openChanged: function() {
		this.$.client.setOpen(this.open);
		if (this.open) {
			this.doActivate();
			enyo.Spotlight.spot(this.$.client);
		} else {
			this.doDeactivate();
		}
	},
	controlsOpenChanged: function() {
		this.$.controlDrawer.setOpen(this.controlsOpen);
		if (this.controlsOpen) {
			this.doActivate();
			enyo.Spotlight.spot(this.$.controlDrawer);
		} else {
			if (this.$.client.getOpen()) {
				this.$.client.setOpen(false);
			}
			this.doDeactivate();
		}
	},
	drawersResized: function(inSender, inEvent) {
		this.$.client.setDrawerProps({height: this.calcDrawerHeight(inEvent.drawersHeight)});
		this.setOpen(false);
		this.setControlsOpen(false);
	}
});

//* @protected

/**
    _moon.FullScreenDrawer_ is a content-free drawer that fills the client's
    full screen area.  It is intended for use inside of `moon.Drawer`.  Users
    should not instantiate _moon.FullScreenDrawer_ directly.
*/
enyo.kind({
	name: "moon.FullScreenDrawer",
	kind: "enyo.Drawer",
	//* @protected
	handlers: {
		onResizeDrawer: "resizeDrawer"
	},
	open: false,
	//* @public
	published: {
		/**
			An object which holds the client dimensions for fullscreen drawer.
			like, `drawer.setDrawerProps({height:100px});`.  This property is intended
			to be used internally by `moon.Drawer`.
		*/
		drawerProps: null
	},
	//* @protected
	initComponents: function() {
		this.inherited(arguments);
		this.$.client.setShowing(true);
	},
	openChanged: function() {
		this.$.client.show();
		if (this.hasNode()) {
			if (this.$.animator.isAnimating()) {
				this.$.animator.reverse();
			} else {
				var v = this.orient == "v";
				var d = v ? "height" : "width";
				var p = v ? "top" : "left";
				var s = this.drawerProps.height;
				// unfixing the height/width is needed to properly
				// measure the scrollHeight/Width DOM property, but
				// can cause a momentary flash of content on some browsers
				this.applyStyle(d, null);

				if (this.animated) {
					this.$.animator.play({
						startValue: this.open ? 0 : s,
						endValue: this.open ? s : 0,
						dimension: d,
						position: p
					});
				} else {
					// directly run last frame if not animating
					this.animatorEnd();
				}
			}
		}
	},
	animatorEnd: function() {
		if (!this.open) {
			this.$.client.hide();
		} else {
			// save changes to this.domCssText --> see ENYO-1561
			this.$.client.domCssText = enyo.Control.domStylesToCssText(this.$.client.domStyles);
		}
		if (this.container) {
			this.container.resized();
		}
	},
	resizeDrawer: function(inSender, inProps) {
		this.drawerProps = inProps;
		if ((this.open) && (!this.$.animator.isAnimating())) {
			this.applyStyle("height", inProps.height + "px");
		}
		return true;
	},
	drawerPropsChanged: function(){
		this.$.client.applyStyle("height", this.drawerProps.height + "px");
		this.$.client.resized();
		this.$.client.setShowing(this.open);
	}
});
<commit_msg>API comment cleanup. Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com)<commit_after>/**
    _moon.Drawer_, a control designed for use with [moon.Drawers](#moon.Drawers),
    consists of two drawers and a [moon.DrawerHandle](#moon.DrawerHandle).
    The main drawer is populated with any child components that are specified in
    the constructor; the optional second drawer (_controlDrawer_) is populated
    with components passed into the _controlDrawerComponents_ property.

    If the second drawer has no components, the main drawer will take up the full
    height of the containing view; otherwise, its height will be equal to the
    height of the containing view minus the height of the _controlDrawerComponents_.

    A call to _toggleDrawer()_ will open the _controlDrawer_ if
    _controlDrawerComponents_ is non-empty; otherwise, it will open the main
    drawer.

    The control's child components may be of any kind.

        {
            name: "musicDrawer",
            kind: "moon.Drawer",
            handle: {kind: "moon.DrawerHandle", content: "Handle"},
            components: [
                {content: "Drawer Content"}
            ],
            controlDrawerComponents: [
                {content: "Controls"}
            ]
        }
*/
enyo.kind({
	name: "moon.Drawer",
	kind:"enyo.Control",
	//* @protected
	classes: "moon-drawer moon-neutral",
	//* @public
	published: {
		//* Components that are to be placed in the control drawer
		controlDrawerComponents: null,
		//* Typically set to an instance of _moon.DrawerHandle_
		handle: null,
		//* Visibility state of the main drawer
		open: false,
		//* Visibility state of the control drawer
		controlsOpen: false
	},
	events: {
		//* Fires when either the main drawer or the control drawer is activated.
		onActivate: "",
		//* Fires when either the main drawer or the control drawer is deactivated.
		onDeactivate: ""
	},
	//* @protected
	handlers: {
		//* Handler for initial rendering event
		onDrawersRendered: "drawersRendered",
		//* Handler for initial resizing event to size drawers to full screen
		onDrawersResized: "drawersResized"
	},
	components: [
		{name: "client", kind: "moon.FullScreenDrawer", resizeContainer:false},
		{name: "controlDrawer", kind: "enyo.Drawer", resizeContainer:false}
	],
	create: function() {
		this.inherited(arguments);
		this.$.controlDrawer.createComponents(this.controlDrawerComponents, {owner:this.owner});
		//* Todo: remove padding on client
		this.$.client.$.client.addClass('moon-drawer-client');
		this.$.controlDrawer.$.client.addClass('moon-drawer-partial-client');
	},
	drawersRendered: function(inSender, inEvent) {
		this.$.client.setDrawerProps({height: this.calcDrawerHeight(inEvent.drawersHeight)});
		this.openChanged();
		if (!this.controlsOpen) {
			this.$.controlDrawer.setAnimated(false);
			this.$.controlDrawer.setOpen(this.controlsOpen);
			this.$.controlDrawer.setAnimated(true);
		}
	},
	calcDrawerHeight: function(drawersHeight) {
		var clientHeight = drawersHeight;
		if (this.controlDrawerComponents == null) {
			return clientHeight;
		} else {
			this.controlDrawerHeight = (this.controlDrawerHeight) ? this.controlDrawerHeight : this.$.controlDrawer.hasNode().getBoundingClientRect().height;
			return (clientHeight - this.controlDrawerHeight);
		}
	},
	toggleDrawer: function() {
		if (this.controlDrawerComponents == null) {
			this.setOpen(!this.open);
		} else {
			this.setControlsOpen(!this.controlsOpen);
		}
		return true;
	},
	openChanged: function() {
		this.$.client.setOpen(this.open);
		if (this.open) {
			this.doActivate();
			enyo.Spotlight.spot(this.$.client);
		} else {
			this.doDeactivate();
		}
	},
	controlsOpenChanged: function() {
		this.$.controlDrawer.setOpen(this.controlsOpen);
		if (this.controlsOpen) {
			this.doActivate();
			enyo.Spotlight.spot(this.$.controlDrawer);
		} else {
			if (this.$.client.getOpen()) {
				this.$.client.setOpen(false);
			}
			this.doDeactivate();
		}
	},
	drawersResized: function(inSender, inEvent) {
		this.$.client.setDrawerProps({height: this.calcDrawerHeight(inEvent.drawersHeight)});
		this.setOpen(false);
		this.setControlsOpen(false);
	}
});

//* @protected

/**
    _moon.FullScreenDrawer_ is a content-free drawer that fills the client's
    full screen area.  It is only intended for use inside of `moon.Drawer`.
    Users should not instantiate _moon.FullScreenDrawer_ directly.
*/
enyo.kind({
	name: "moon.FullScreenDrawer",
	kind: "enyo.Drawer",
	//* @protected
	handlers: {
		onResizeDrawer: "resizeDrawer"
	},
	open: false,
	//* @public
	published: {
		/**
			An object that holds the client dimensions for the fullscreen drawer,
			e.g.: _drawer.setDrawerProps({height:100px});_.  This property is only
			intended to be used internally by _moon.Drawer_.
		*/
		drawerProps: null
	},
	//* @protected
	initComponents: function() {
		this.inherited(arguments);
		this.$.client.setShowing(true);
	},
	openChanged: function() {
		this.$.client.show();
		if (this.hasNode()) {
			if (this.$.animator.isAnimating()) {
				this.$.animator.reverse();
			} else {
				var v = this.orient == "v";
				var d = v ? "height" : "width";
				var p = v ? "top" : "left";
				var s = this.drawerProps.height;
				// unfixing the height/width is needed to properly
				// measure the scrollHeight/Width DOM property, but
				// can cause a momentary flash of content on some browsers
				this.applyStyle(d, null);

				if (this.animated) {
					this.$.animator.play({
						startValue: this.open ? 0 : s,
						endValue: this.open ? s : 0,
						dimension: d,
						position: p
					});
				} else {
					// directly run last frame if not animating
					this.animatorEnd();
				}
			}
		}
	},
	animatorEnd: function() {
		if (!this.open) {
			this.$.client.hide();
		} else {
			// save changes to this.domCssText --> see ENYO-1561
			this.$.client.domCssText = enyo.Control.domStylesToCssText(this.$.client.domStyles);
		}
		if (this.container) {
			this.container.resized();
		}
	},
	resizeDrawer: function(inSender, inProps) {
		this.drawerProps = inProps;
		if ((this.open) && (!this.$.animator.isAnimating())) {
			this.applyStyle("height", inProps.height + "px");
		}
		return true;
	},
	drawerPropsChanged: function(){
		this.$.client.applyStyle("height", this.drawerProps.height + "px");
		this.$.client.resized();
		this.$.client.setShowing(this.open);
	}
});
<|endoftext|>


<commit_before>"use strict";

describe("morph", function() {
  var mockConfigurator = {
    configurationFor: function(name) {
      return {};
    },
    getRetrievers: function(name) {
      return {};
    },
    getConfAndDelegate: function(name, obj) {
      obj.attributes = {
        "pos" : {
          "long" : "Part of Speech",
          "short" : "pos",
          "values" : {
            "noun" : {
              "long" : "noun",
              "short" : "noun",
              "postag" : "n",
              "style" : {
                "color" : "black"
              }
            },
            "adj" : {
              "long" : "adjective",
              "short" : "adj",
              "postag" : "a",
              "style" : {
                "color" : "blue"
              }
            }
          }
        },
        "pers" : {
          "long" : "Person",
          "short" : "pers",
          "values" : {
            "1st" : {
              "long" : "first person",
              "short" : "1st",
              "postag" : "1"
            },
            "2nd" : {
              "long" : "second person",
              "short" : "2nd",
              "postag" : "2"
            }
          }
        }
      };
      obj.postagSchema = ['pos', 'pers'];
      obj.styledThrough = 'pos';
      obj.conf = {};
    }
  };

  var createTokens = function() {
    return {
      '01': {
        id: '01',
        string: 't1',
        morphology: {
          lemma: 'lemma1',
          postag: 'n-'
        }
      },
      '02': {
        id: '02',
        string: 't2',
        morphology: {
          lemma: 'lemma2',
          postag: 'a1'
        }
      }
    };
  };

  beforeEach(module("arethusa.core", function($provide) {
    $provide.value('configurator', mockConfigurator);
  }));

  beforeEach(module("arethusa.morph"));

  var morph;
  var state;
  beforeEach(inject(function(_morph_, _state_) {
    state = _state_;
    state.tokens = createTokens();
    morph = _morph_;
    morph.init();
  }));

  describe('this.postagToAttributes', function() {
    it('expands a postag to full key value pairs', function() {
      var form = { postag: 'a1' };
      var res  = { postag: 'a1', attributes: { pos: 'adj', pers: '1st' }};
      morph.postagToAttributes(form);
      expect(form).toEqual(res);
    });
  });
});
<commit_msg>Add spec for morph.attributesToPostag();<commit_after>"use strict";

describe("morph", function() {
  var mockConfigurator = {
    configurationFor: function(name) {
      return {};
    },
    getRetrievers: function(name) {
      return {};
    },
    getConfAndDelegate: function(name, obj) {
      obj.attributes = {
        "pos" : {
          "long" : "Part of Speech",
          "short" : "pos",
          "values" : {
            "noun" : {
              "long" : "noun",
              "short" : "noun",
              "postag" : "n",
              "style" : {
                "color" : "black"
              }
            },
            "adj" : {
              "long" : "adjective",
              "short" : "adj",
              "postag" : "a",
              "style" : {
                "color" : "blue"
              }
            }
          }
        },
        "pers" : {
          "long" : "Person",
          "short" : "pers",
          "values" : {
            "1st" : {
              "long" : "first person",
              "short" : "1st",
              "postag" : "1"
            },
            "2nd" : {
              "long" : "second person",
              "short" : "2nd",
              "postag" : "2"
            }
          }
        }
      };
      obj.postagSchema = ['pos', 'pers'];
      obj.styledThrough = 'pos';
      obj.conf = {};
    }
  };

  var createTokens = function() {
    return {
      '01': {
        id: '01',
        string: 't1',
        morphology: {
          lemma: 'lemma1',
          postag: 'n-'
        }
      },
      '02': {
        id: '02',
        string: 't2',
        morphology: {
          lemma: 'lemma2',
          postag: 'a1'
        }
      }
    };
  };

  beforeEach(module("arethusa.core", function($provide) {
    $provide.value('configurator', mockConfigurator);
  }));

  beforeEach(module("arethusa.morph"));

  var morph;
  var state;
  beforeEach(inject(function(_morph_, _state_) {
    state = _state_;
    state.tokens = createTokens();
    morph = _morph_;
    morph.init();
  }));

  describe('this.postagToAttributes', function() {
    it('expands a postag to full key value pairs', function() {
      var form = { postag: 'a1' };
      var res  = angular.extend({}, form, { attributes: { pos: 'adj', pers: '1st' }});
      morph.postagToAttributes(form);
      expect(form).toEqual(res);
    });
  });

  describe('this.attributesToPostag', function() {
    it('creates a postag string out of attributes', function() {
      var attrs = { pos: 'noun', pers: '2nd' };
      var res = 'n2';
      var postag = morph.attributesToPostag(attrs);
      expect(postag).toEqual(res);
    });

    it('fills with - according to the postagSchema, when an attribute is undefined', function() {
      var attrs = { pers: '1st' };
      var res = '-1';
      var postag = morph.attributesToPostag(attrs);
      expect(postag).toEqual(res);
    });
  });
});
<|endoftext|>


<commit_before>/**
 * bootbox.js [v4.x/master branch]
 *
 * http://bootboxjs.com/license.txt
 */
// @see https://github.com/makeusabrew/bootbox/issues/71
window.bootbox = window.bootbox || (function(document, $, undefined) {
  "use strict";

  // the base DOM structure needed to create a modal
  var templates = {
    dialog:
      "
" +
        "
" +
          "
" +
            "
" +
              "

" +
            "
" +
            "

" +
            "

" +
          "
" +
        "
" +
      "
",
    closeButton:
      "<button type='button' class='bootbox-close-button close'>×</button>",
    form:
      "<form class='bootbox-form'></form>",
    inputs: {
      text:
        "<input class='bootbox-input input-block-level' autocomplete=off type=text />"
    }
  };

  // cache a reference to the jQueryfied body element
  var appendTo = $("body");

  var defaults = {
    // default language
    locale: "en",
    // show backdrop or not
    backdrop: true,
    // animate the modal in/out
    animate: true,
    // additional class string applied to the top level dialog
    className: null,
    // show the modal header or not
    header: true,
    // whether or not to include a close button, if a header is present
    closeButton: true,
    // show the dialog immediately by default
    show: true
  };

  // our public object; augmented after our private API
  var exports = {};

  /**
   * @private
   */
  function _t(key) {
    return locales[defaults.locale][key] || locales.en[key];
  }

  // @TODO inline within exports.dialog? closing over dialog & callbacks is neater...
  function processCallback(e, dialog, callback) {
    // by default we assume a callback will get rid of the dialog,
    // although they are given the opportunity to override this
    var preserveDialog = false;

    // so, if the callback can be invoked and it *explicitly returns false*
    // then we'll set a flag to keep the dialog active...
    if ($.isFunction(callback)) {
      preserveDialog = (callback(e) === false);
    }

    // ... otherwise we'll bin it
    if (!preserveDialog) {
      dialog.modal("hide");
    }
  }

  function sanitize(options) {
    var buttons = options.buttons;
    var total;
    var key;
    var keyIndex;
    var button;

    total = (function getKeyLength(obj) {
      // @TODO defer to Object.keys(x).length if available?
      var k, t = 0;
      for (k in obj) {
        t ++;
      }
      return t;
    }(buttons));

    if (typeof options !== "object") {
      throw new Error("Please supply an object of options");
    }

    if (!options.message) {
      throw new Error("Please specify a message");
    }

    // make sure any supplied options take precedence over defaults
    options = $.extend({}, defaults, options);

    if (!options.buttons) {
      options.buttons = {};
    }

    if (!options.title) {
      // @FIXME gah; we need to pad the header a bit...
      options.title = " ";
    }

    // we only support Bootstrap's "static" and false backdrop args
    // supporting true would mean you could dismiss the dialog without
    // explicitly interacting with it
    options.backdrop = options.backdrop ? "static" : false;

    keyIndex = 0;

    for (key in buttons) {
      keyIndex ++;

      button = buttons[key];

      if (!button.label) {
        throw new Error("Button with key " + key + " requires a label");
      }

      if (!button.className) {
        if (total <= 2 && keyIndex === total) {
          // always add a primary to the main option in a two-button dialog
          button.className = "btn-primary";
        } else {
          button.className = "btn-default";
        }
      }
    }

    return options;
  }

  function mapArguments(args, properties) {
    var argn = args.length;
    var options = {};

    if (argn < 1 || argn > 2) {
      throw new Error("Invalid argument length");
    }

    if (argn === 2 || typeof args[0] === "string") {
      options[properties[0]] = args[0];
      options[properties[1]] = args[1];
    } else {
      options = args[0];
    }

    return options;
  }

  exports.alert = function() {
    var options;
    var defaults;

    defaults = {
      buttons: {
        ok: {
          label: _t("OK")
        }
      }
    };

    options = $.extend(true, defaults, mapArguments(arguments, ["message", "callback"]));

    /**
     * overrides
     */
    options.buttons.ok.callback = options.onEscape = function() {
      if ($.isFunction(options.callback)) {
        return options.callback();
      }
      return true;
    };

    return exports.dialog(options);
  };

  exports.confirm = function() {
    var options;
    var defaults;

    defaults = {
      buttons: {
        cancel: {
          label: _t("CANCEL")
        },
        confirm: {
          label: _t("CONFIRM")
        }
      }
    };

    options = $.extend(true, defaults, mapArguments(arguments, ["message", "callback"]));

    /**
     * overrides; undo anything the user tried to set they shouldn't have
     */
    options.buttons.cancel.callback = options.onEscape = function() {
      return options.callback(false);
    };

    options.buttons.confirm.callback = function() {
      return options.callback(true);
    };

    // confirm specific validation
    if (!$.isFunction(options.callback)) {
      throw new Error("Confirm method requires callback");
    }

    return exports.dialog(options);
  };

  exports.prompt = function() {
    var options;
    var defaults;
    var dialog;
    var form;
    var input;

    // we have to create our form first otherwise
    // its value is undefined when gearing up our options
    // @TODO this could be solved by allowing message to
    // be a function instead...
    form = $(templates.form);

    defaults = {
      buttons: {
        cancel: {
          label: _t("CANCEL")
        },
        confirm: {
          label: _t("CONFIRM")
        }
      },
      value: ""
    };

    options = $.extend(true, defaults, mapArguments(arguments, ["title", "callback"]));

    /**
     * overrides; undo anything the user tried to set they shouldn't have
     */
    options.message = form;

    options.buttons.cancel.callback = options.onEscape = function() {
      return options.callback(null);
    };

    options.buttons.confirm.callback = function() {
      return options.callback(input.val());
    };

    // prompt specific validation
    if (!options.title) {
      throw new Error("This method requires a title");
    }

    if (!$.isFunction(options.callback)) {
      throw new Error("This method requires a callback");
    }

    // create the input
    input = $(templates.inputs.text);
    input.val(options.value);

    // now place it in our form
    form.append(input);

    form.on("submit", function(e) {
      e.preventDefault();
      // @TODO can we actually click *the* button object instead?
      // e.g. buttons.confirm.click() or similar
      dialog.find(".btn-primary").click();
    });

    dialog = exports.dialog(options);

    // clear the existing handler focusing the submit button...
    dialog.off("shown.bs.modal");

    // ...and replace it with one focusing our input, if possible
    dialog.on("shown.bs.modal", function() {
      input.focus();
    });

    // @TODO this needs to respect whether the user asked for the dialog
    // to be shown or not, not just assumed...
    dialog.modal("show");

    return dialog;
  };

  exports.dialog = function(options) {
    options = sanitize(options);

    var dialog = $(templates.dialog);
    var buttons = options.buttons;
    var button;
    var key;
    var buttonStr = "";
    var callbacks = {
      // always assume an onEscape for now
      // @TODO make this optional
      // @TODO namespace this in case a user passes a button called 'escape'
      "escape": options.onEscape
    };

    // @TODO hasOwnProperty
    for (key in buttons) {
      button = buttons[key];

      // @TODO I don't like this string appending to itself; bit dirty. Needs reworking
      // can we just build up button elements instead? slower but neater. Then button
      // can just become a template too
      buttonStr += "<button data-bb-handler='" + key + "' type='button' class='btn " + button.className + "'>" + button.label + "</button>";
      callbacks[key] = button.callback;
    }

    if (options.animate === true) {
      dialog.addClass("fade");
    }

    if (options.className) {
      dialog.addClass(options.className);
    }

    if (options.closeButton) {
      dialog.find(".modal-header").prepend(templates.closeButton);
    }

    if (options.title) {
      dialog.find(".modal-title").html(options.title);
    }

    // required bits last
    dialog.find(".modal-body").html(options.message);
    dialog.find(".modal-footer").html(buttonStr);

    /**
     * Bootstrap event listeners; used handle extra
     * setup & teardown required after the underlying
     * modal has performed certain actions
     */

    dialog.on("hidden.bs.modal", function(e) {
      // ensure we don't accidentally intercept hidden events triggered
      // by children of the current dialog. We shouldn't anymore now BS
      // namespaces its events; but still worth doing
      if (e.target === this) {
        dialog.remove();
      }
    });

    dialog.on("shown.bs.modal", function() {
      dialog.find(".btn-primary:first").focus();
    });

    /**
     * Bootbox event listeners; experimental and may not last
     * just an attempt to decouple some behaviours from their
     * respective triggers
     */

    dialog.on("escape.close.bb", function(e) {
      // @NOTE
      // if we declared processCallback locally we could take
      // advantage of our scope to just make the following...
      // processCallback(e, "escape");
      // and the one a bit further:
      // processCallback(e, $(this).data("bb-handler")
      // worth considering...
      processCallback(e, dialog, callbacks.escape);
    });

    /**
     * Standard jQuery event listeners; used to handle user
     * interaction with our dialog
     */

    dialog.on("click", ".modal-footer button", function(e) {
      e.preventDefault();

      var callbackKey = $(this).data("bb-handler");

      processCallback(e, dialog, callbacks[callbackKey]);

    });

    dialog.on("click", ".modal-header .close", function(e) {
      e.preventDefault();
      processCallback(e, dialog, callbacks.escape);
    });

    dialog.on("keyup", function(e) {
      // @TODO make conditional
      if (e.which === 27) {
        dialog.trigger("escape.close.bb");
      }
    });

    // the remainder of this method simply deals with adding our
    // dialogent to the DOM, augmenting it with Bootstrap's modal
    // functionality and then giving the resulting object back
    // to our caller

    appendTo.append(dialog);

    dialog.modal({
      backdrop: options.backdrop,
      keyboard: false,
      show: false
    });

    if (options.show) {
      dialog.modal("show");
    }

    // @TODO should we return the raw element here or should
    // we wrap it in an object on which we can expose some neater
    // methods, e.g. var d = bootbox.alert(); d.hide(); instead
    // of d.modal("hide");

   /*
    function BBDialog(elem) {
      this.elem = elem;
    }

    BBDialog.prototype = {
      hide: function() {
        return this.elem.modal("hide");
      },
      show: function() {
        return this.elem.modal("show");
      }
    };
    */

    return dialog;

  };

  exports.setDefaults = function(values) {
    $.extend(defaults, values);
  };

  exports.hideAll = function() {
    $(".bootbox").modal("hide");
  };

  /**
   * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are
   * unlikely to be required. If this gets too large it can be split out into separate JS files.
   */
  var locales = {
    br : {
      OK      : "OK",
      CANCEL  : "Cancelar",
      CONFIRM : "Sim"
    },
    da : {
      OK      : "OK",
      CANCEL  : "Annuller",
      CONFIRM : "Accepter"
    },
    de : {
      OK      : "OK",
      CANCEL  : "Abbrechen",
      CONFIRM : "Akzeptieren"
    },
    en : {
      OK      : "OK",
      CANCEL  : "Cancel",
      CONFIRM : "OK"
    },
    es : {
      OK      : "OK",
      CANCEL  : "Cancelar",
      CONFIRM : "Aceptar"
    },
    fi : {
      OK      : "OK",
      CANCEL  : "Peruuta",
      CONFIRM : "OK"
    },
    fr : {
      OK      : "OK",
      CANCEL  : "Annuler",
      CONFIRM : "D'accord"
    },
    it : {
      OK      : "OK",
      CANCEL  : "Annulla",
      CONFIRM : "Conferma"
    },
    nl : {
      OK      : "OK",
      CANCEL  : "Annuleren",
      CONFIRM : "Accepteren"
    },
    pl : {
      OK      : "OK",
      CANCEL  : "Anuluj",
      CONFIRM : "Potwierdź"
    },
    ru : {
      OK      : "OK",
      CANCEL  : "Отмена",
      CONFIRM : "Применить"
    },
    zh_CN : {
      OK      : "OK",
      CANCEL  : "取消",
      CONFIRM : "确认"
    },
    zh_TW : {
      OK      : "OK",
      CANCEL  : "取消",
      CONFIRM : "確認"
    }
  };

  return exports;

}(document, window.jQuery));
<commit_msg>add simple wrapper function to handle merging options<commit_after>/**
 * bootbox.js [v4.x/master branch]
 *
 * http://bootboxjs.com/license.txt
 */
// @see https://github.com/makeusabrew/bootbox/issues/71
window.bootbox = window.bootbox || (function(document, $, undefined) {
  "use strict";

  // the base DOM structure needed to create a modal
  var templates = {
    dialog:
      "
" +
        "
" +
          "
" +
            "
" +
              "

" +
            "
" +
            "

" +
            "

" +
          "
" +
        "
" +
      "
",
    closeButton:
      "<button type='button' class='bootbox-close-button close'>×</button>",
    form:
      "<form class='bootbox-form'></form>",
    inputs: {
      text:
        "<input class='bootbox-input input-block-level' autocomplete=off type=text />"
    }
  };

  // cache a reference to the jQueryfied body element
  var appendTo = $("body");

  var defaults = {
    // default language
    locale: "en",
    // show backdrop or not
    backdrop: true,
    // animate the modal in/out
    animate: true,
    // additional class string applied to the top level dialog
    className: null,
    // show the modal header or not
    header: true,
    // whether or not to include a close button, if a header is present
    closeButton: true,
    // show the dialog immediately by default
    show: true
  };

  // our public object; augmented after our private API
  var exports = {};

  /**
   * @private
   */
  function _t(key) {
    return locales[defaults.locale][key] || locales.en[key];
  }

  // @TODO inline within exports.dialog? closing over dialog & callbacks is neater...
  function processCallback(e, dialog, callback) {
    // by default we assume a callback will get rid of the dialog,
    // although they are given the opportunity to override this
    var preserveDialog = false;

    // so, if the callback can be invoked and it *explicitly returns false*
    // then we'll set a flag to keep the dialog active...
    if ($.isFunction(callback)) {
      preserveDialog = (callback(e) === false);
    }

    // ... otherwise we'll bin it
    if (!preserveDialog) {
      dialog.modal("hide");
    }
  }

  function sanitize(options) {
    var buttons = options.buttons;
    var total;
    var key;
    var keyIndex;
    var button;

    total = (function getKeyLength(obj) {
      // @TODO defer to Object.keys(x).length if available?
      var k, t = 0;
      for (k in obj) {
        t ++;
      }
      return t;
    }(buttons));

    if (typeof options !== "object") {
      throw new Error("Please supply an object of options");
    }

    if (!options.message) {
      throw new Error("Please specify a message");
    }

    // make sure any supplied options take precedence over defaults
    options = $.extend({}, defaults, options);

    if (!options.buttons) {
      options.buttons = {};
    }

    if (!options.title) {
      // @FIXME gah; we need to pad the header a bit...
      options.title = " ";
    }

    // we only support Bootstrap's "static" and false backdrop args
    // supporting true would mean you could dismiss the dialog without
    // explicitly interacting with it
    options.backdrop = options.backdrop ? "static" : false;

    keyIndex = 0;

    for (key in buttons) {
      keyIndex ++;

      button = buttons[key];

      if (!button.label) {
        throw new Error("Button with key " + key + " requires a label");
      }

      if (!button.className) {
        if (total <= 2 && keyIndex === total) {
          // always add a primary to the main option in a two-button dialog
          button.className = "btn-primary";
        } else {
          button.className = "btn-default";
        }
      }
    }

    return options;
  }

  function mapArguments(args, properties) {
    var argn = args.length;
    var options = {};

    if (argn < 1 || argn > 2) {
      throw new Error("Invalid argument length");
    }

    if (argn === 2 || typeof args[0] === "string") {
      options[properties[0]] = args[0];
      options[properties[1]] = args[1];
    } else {
      options = args[0];
    }

    return options;
  }

  function mergeArguments(defaults, args, properties) {
    return $.extend(true, {}, defaults, mapArguments(args, properties));
  }

  exports.alert = function() {
    var options;
    var defaults;

    defaults = {
      buttons: {
        ok: {
          label: _t("OK")
        }
      }
    };

    options = mergeArguments(defaults, arguments, ["message", "callback"]);

    /**
     * overrides
     */
    options.buttons.ok.callback = options.onEscape = function() {
      if ($.isFunction(options.callback)) {
        return options.callback();
      }
      return true;
    };

    return exports.dialog(options);
  };

  exports.confirm = function() {
    var options;
    var defaults;

    defaults = {
      buttons: {
        cancel: {
          label: _t("CANCEL")
        },
        confirm: {
          label: _t("CONFIRM")
        }
      }
    };

    options = mergeArguments(defaults, arguments, ["message", "callback"]);

    /**
     * overrides; undo anything the user tried to set they shouldn't have
     */
    options.buttons.cancel.callback = options.onEscape = function() {
      return options.callback(false);
    };

    options.buttons.confirm.callback = function() {
      return options.callback(true);
    };

    // confirm specific validation
    if (!$.isFunction(options.callback)) {
      throw new Error("Confirm method requires callback");
    }

    return exports.dialog(options);
  };

  exports.prompt = function() {
    var options;
    var defaults;
    var dialog;
    var form;
    var input;

    // we have to create our form first otherwise
    // its value is undefined when gearing up our options
    // @TODO this could be solved by allowing message to
    // be a function instead...
    form = $(templates.form);

    defaults = {
      buttons: {
        cancel: {
          label: _t("CANCEL")
        },
        confirm: {
          label: _t("CONFIRM")
        }
      },
      value: ""
    };

    options = mergeArguments(defaults, arguments, ["title", "callback"]);

    /**
     * overrides; undo anything the user tried to set they shouldn't have
     */
    options.message = form;

    options.buttons.cancel.callback = options.onEscape = function() {
      return options.callback(null);
    };

    options.buttons.confirm.callback = function() {
      return options.callback(input.val());
    };

    // prompt specific validation
    if (!options.title) {
      throw new Error("This method requires a title");
    }

    if (!$.isFunction(options.callback)) {
      throw new Error("This method requires a callback");
    }

    // create the input
    input = $(templates.inputs.text);
    input.val(options.value);

    // now place it in our form
    form.append(input);

    form.on("submit", function(e) {
      e.preventDefault();
      // @TODO can we actually click *the* button object instead?
      // e.g. buttons.confirm.click() or similar
      dialog.find(".btn-primary").click();
    });

    dialog = exports.dialog(options);

    // clear the existing handler focusing the submit button...
    dialog.off("shown.bs.modal");

    // ...and replace it with one focusing our input, if possible
    dialog.on("shown.bs.modal", function() {
      input.focus();
    });

    // @TODO this needs to respect whether the user asked for the dialog
    // to be shown or not, not just assumed...
    dialog.modal("show");

    return dialog;
  };

  exports.dialog = function(options) {
    options = sanitize(options);

    var dialog = $(templates.dialog);
    var buttons = options.buttons;
    var button;
    var key;
    var buttonStr = "";
    var callbacks = {
      // always assume an onEscape for now
      // @TODO make this optional
      // @TODO namespace this in case a user passes a button called 'escape'
      "escape": options.onEscape
    };

    // @TODO hasOwnProperty
    for (key in buttons) {
      button = buttons[key];

      // @TODO I don't like this string appending to itself; bit dirty. Needs reworking
      // can we just build up button elements instead? slower but neater. Then button
      // can just become a template too
      buttonStr += "<button data-bb-handler='" + key + "' type='button' class='btn " + button.className + "'>" + button.label + "</button>";
      callbacks[key] = button.callback;
    }

    if (options.animate === true) {
      dialog.addClass("fade");
    }

    if (options.className) {
      dialog.addClass(options.className);
    }

    if (options.closeButton) {
      dialog.find(".modal-header").prepend(templates.closeButton);
    }

    if (options.title) {
      dialog.find(".modal-title").html(options.title);
    }

    // required bits last
    dialog.find(".modal-body").html(options.message);
    dialog.find(".modal-footer").html(buttonStr);

    /**
     * Bootstrap event listeners; used handle extra
     * setup & teardown required after the underlying
     * modal has performed certain actions
     */

    dialog.on("hidden.bs.modal", function(e) {
      // ensure we don't accidentally intercept hidden events triggered
      // by children of the current dialog. We shouldn't anymore now BS
      // namespaces its events; but still worth doing
      if (e.target === this) {
        dialog.remove();
      }
    });

    dialog.on("shown.bs.modal", function() {
      dialog.find(".btn-primary:first").focus();
    });

    /**
     * Bootbox event listeners; experimental and may not last
     * just an attempt to decouple some behaviours from their
     * respective triggers
     */

    dialog.on("escape.close.bb", function(e) {
      // @NOTE
      // if we declared processCallback locally we could take
      // advantage of our scope to just make the following...
      // processCallback(e, "escape");
      // and the one a bit further:
      // processCallback(e, $(this).data("bb-handler")
      // worth considering...
      processCallback(e, dialog, callbacks.escape);
    });

    /**
     * Standard jQuery event listeners; used to handle user
     * interaction with our dialog
     */

    dialog.on("click", ".modal-footer button", function(e) {
      e.preventDefault();

      var callbackKey = $(this).data("bb-handler");

      processCallback(e, dialog, callbacks[callbackKey]);

    });

    dialog.on("click", ".modal-header .close", function(e) {
      e.preventDefault();
      processCallback(e, dialog, callbacks.escape);
    });

    dialog.on("keyup", function(e) {
      // @TODO make conditional
      if (e.which === 27) {
        dialog.trigger("escape.close.bb");
      }
    });

    // the remainder of this method simply deals with adding our
    // dialogent to the DOM, augmenting it with Bootstrap's modal
    // functionality and then giving the resulting object back
    // to our caller

    appendTo.append(dialog);

    dialog.modal({
      backdrop: options.backdrop,
      keyboard: false,
      show: false
    });

    if (options.show) {
      dialog.modal("show");
    }

    // @TODO should we return the raw element here or should
    // we wrap it in an object on which we can expose some neater
    // methods, e.g. var d = bootbox.alert(); d.hide(); instead
    // of d.modal("hide");

   /*
    function BBDialog(elem) {
      this.elem = elem;
    }

    BBDialog.prototype = {
      hide: function() {
        return this.elem.modal("hide");
      },
      show: function() {
        return this.elem.modal("show");
      }
    };
    */

    return dialog;

  };

  exports.setDefaults = function(values) {
    $.extend(defaults, values);
  };

  exports.hideAll = function() {
    $(".bootbox").modal("hide");
  };

  /**
   * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are
   * unlikely to be required. If this gets too large it can be split out into separate JS files.
   */
  var locales = {
    br : {
      OK      : "OK",
      CANCEL  : "Cancelar",
      CONFIRM : "Sim"
    },
    da : {
      OK      : "OK",
      CANCEL  : "Annuller",
      CONFIRM : "Accepter"
    },
    de : {
      OK      : "OK",
      CANCEL  : "Abbrechen",
      CONFIRM : "Akzeptieren"
    },
    en : {
      OK      : "OK",
      CANCEL  : "Cancel",
      CONFIRM : "OK"
    },
    es : {
      OK      : "OK",
      CANCEL  : "Cancelar",
      CONFIRM : "Aceptar"
    },
    fi : {
      OK      : "OK",
      CANCEL  : "Peruuta",
      CONFIRM : "OK"
    },
    fr : {
      OK      : "OK",
      CANCEL  : "Annuler",
      CONFIRM : "D'accord"
    },
    it : {
      OK      : "OK",
      CANCEL  : "Annulla",
      CONFIRM : "Conferma"
    },
    nl : {
      OK      : "OK",
      CANCEL  : "Annuleren",
      CONFIRM : "Accepteren"
    },
    pl : {
      OK      : "OK",
      CANCEL  : "Anuluj",
      CONFIRM : "Potwierdź"
    },
    ru : {
      OK      : "OK",
      CANCEL  : "Отмена",
      CONFIRM : "Применить"
    },
    zh_CN : {
      OK      : "OK",
      CANCEL  : "取消",
      CONFIRM : "确认"
    },
    zh_TW : {
      OK      : "OK",
      CANCEL  : "取消",
      CONFIRM : "確認"
    }
  };

  return exports;

}(document, window.jQuery));
<|endoftext|>


<commit_before>/*
 *	Factlink
 *	A simple proxy server which will proxy requests.
 *
 *	Used for implementing Factlink JavaScript on client-sites.
 */

// The actual server
var server = require('express').createServer();
server.configure(function(){
        server.set('views', __dirname + '/views');
});

/**
 *	We execute our requests using Restler
 *  because it follows redirects if needed
 */
var restler = require('restler');

var fs = require('fs');
config_path = process.env.CONFIG_PATH || '../config/';
global.config = require('./read_config').read_conf(config_path,fs,server.settings.env);

const PROXY_URL 	= "http://"+global.config['proxy']['hostname']+':'+global.config['proxy']['port'];
const STATIC_URL 	= "http://"+global.config['static']['hostname']+':'+global.config['static']['port'];

const API_LOCATION = global.config['core']['hostname']+':'+global.config['core']['port'];
const LIB_LOCATION = global.config['static']['hostname']+':'+global.config['static']['port'] + "/lib";

const PORT 				= parseInt(global.config['proxy']['port'],10);

// Use Jade as templating engine
server.set('view engine', 'jade');

/**
 *	Regular get requests
 */
server.get('/parse', function(req, res) {

	console.info("\nGET /parse");

	var site = req.query.url;

  errorhandler = function(data) {
		console.error("Failed on: " + site);

		res.render('something_went_wrong', {
			layout: false,
			locals: {
				static_url: STATIC_URL,
				proxy_url: PROXY_URL,
				site: site
			}
		});
	};

  // add protocol http:// if no protocol is defined:
	protocol_regex = new RegExp("^(?=.*://)");
	http_regex = new RegExp("^(?=http(s?)://)");
	if (http_regex.test(site) === false) {
	  if (protocol_regex.test(site) === false) {
	    site = "http://" + site;
	  }else {
	    errorhandler({});
	    return;
    }
	}

 	console.info("Serving: " + site);

	var request = restler.get( site );

	request.on('complete', function(data) {
		res.write( injectFactlinkJs(data,site) );
		res.end();
	});

	request.on("error", errorhandler);

});

/**
 * Add base url and inject proxy.js, and return the proxied site
 */
function injectFactlinkJs(html_in,site){
	var html = html_in.replace('', '<base href="' + site + '" />');
  set_urls = '';
  load_proxy_js = '';
	html = html.replace('', set_urls + load_proxy_js + '');
	return html;
}

/**
 * Handling forms
 * --------------
 * Forms get posted with a hidden 'factlinkPostUrl' field,
 * which is added by the proxy. This is the 'action' URL which
 * the form normally posts to.
 *
 */
server.get('/submit', function(req, res) {

	// factlinkPostUrl is injected in the posted form by proxy.js
	var site = req.query.factlinkPostUrl;
	console.info('Serving (as form): ' + site);

	// Set form hash and remove our temporary stored URL so we only post the original hash
	var form_hash = req.query;
	delete form_hash['factlinkPostUrl'];

	var request = restler.post( site, { "query": form_hash } );

	request.on('complete', function(data) {
		res.write( injectFactlinkJs(data,site) );
		res.end();
	});

	request.on('error', function(data) {
		var error_page = "An error occured when trying to visit " + site + ".

Please check the URL or do a web-search.
form submit error
";
		res.write( error_page );
		res.end();
	});
});

function render_page(pagename) {
  return function(req, res) {
	  res.render(pagename, {
		  layout: false,
		  locals: {
			  static_url: STATIC_URL,
			  proxy_url: PROXY_URL,
			  url: req.query.url
		  }
	  });
  };
}

/**
 *	Render the header with the url bar
 */
server.get('/header', render_page('header'));
server.get('/', render_page('index'));

server.listen(PORT);
console.info('\nStarted Factlink proxy on port ' + PORT);<commit_msg>Added core url<commit_after>/*
 *	Factlink
 *	A simple proxy server which will proxy requests.
 *
 *	Used for implementing Factlink JavaScript on client-sites.
 */

// The actual server
var server = require('express').createServer();
server.configure(function(){
        server.set('views', __dirname + '/views');
});

/**
 *	We execute our requests using Restler
 *  because it follows redirects if needed
 */
var restler = require('restler');

var fs = require('fs');
config_path = process.env.CONFIG_PATH || '../config/';
global.config = require('./read_config').read_conf(config_path,fs,server.settings.env);

const PROXY_URL 	= "http://"+global.config['proxy']['hostname']+':'+global.config['proxy']['port'];
const STATIC_URL 	= "http://"+global.config['static']['hostname']+':'+global.config['static']['port'];

const API_LOCATION = global.config['core']['hostname']+':'+global.config['core']['port'];
const LIB_LOCATION = global.config['static']['hostname']+':'+global.config['static']['port'] + "/lib";

const PORT 				= parseInt(global.config['proxy']['port'],10);

// Use Jade as templating engine
server.set('view engine', 'jade');

/**
 *	Regular get requests
 */
server.get('/parse', function(req, res) {

	console.info("\nGET /parse");

	var site = req.query.url;

  errorhandler = function(data) {
		console.error("Failed on: " + site);

		res.render('something_went_wrong', {
			layout: false,
			locals: {
				static_url: STATIC_URL,
				proxy_url: PROXY_URL,
				site: site
			}
		});
	};

  // add protocol http:// if no protocol is defined:
	protocol_regex = new RegExp("^(?=.*://)");
	http_regex = new RegExp("^(?=http(s?)://)");
	if (http_regex.test(site) === false) {
	  if (protocol_regex.test(site) === false) {
	    site = "http://" + site;
	  }else {
	    errorhandler({});
	    return;
    }
	}

 	console.info("Serving: " + site);

	var request = restler.get( site );

	request.on('complete', function(data) {
		res.write( injectFactlinkJs(data,site) );
		res.end();
	});

	request.on("error", errorhandler);

});

/**
 * Add base url and inject proxy.js, and return the proxied site
 */
function injectFactlinkJs(html_in,site){
	var html = html_in.replace('', '<base href="' + site + '" />');
  set_urls = '';
  load_proxy_js = '';
	html = html.replace('', set_urls + load_proxy_js + '');
	return html;
}

/**
 * Handling forms
 * --------------
 * Forms get posted with a hidden 'factlinkPostUrl' field,
 * which is added by the proxy. This is the 'action' URL which
 * the form normally posts to.
 *
 */
server.get('/submit', function(req, res) {

	// factlinkPostUrl is injected in the posted form by proxy.js
	var site = req.query.factlinkPostUrl;
	console.info('Serving (as form): ' + site);

	// Set form hash and remove our temporary stored URL so we only post the original hash
	var form_hash = req.query;
	delete form_hash['factlinkPostUrl'];

	var request = restler.post( site, { "query": form_hash } );

	request.on('complete', function(data) {
		res.write( injectFactlinkJs(data,site) );
		res.end();
	});

	request.on('error', function(data) {
		var error_page = "An error occured when trying to visit " + site + ".

Please check the URL or do a web-search.
form submit error
";
		res.write( error_page );
		res.end();
	});
});

function render_page(pagename) {
  return function(req, res) {
	  res.render(pagename, {
		  layout: false,
		  locals: {
			  static_url: STATIC_URL,
			  proxy_url: PROXY_URL,
				core_url: API_LOCATION,
			  url: req.query.url
		  }
	  });
  };
}

/**
 *	Render the header with the url bar
 */
server.get('/header', render_page('header'));
server.get('/', render_page('index'));

server.listen(PORT);
console.info('\nStarted Factlink proxy on port ' + PORT);<|endoftext|>