<commit_before>var URL = require('url');
var queue = require('queue-async');
var typeis = require('type-is').is;
var agent = require('../utils/agent');
var debug = require('debug')('raja:dom');
var CacheDebounce = require('cache-debounce');

module.exports = DomProxy;

function DomProxy(raja, opts) {
	if (!(this instanceof DomProxy)) return new DomProxy(raja, opts);
	this.raja = raja;
	var Dom = this.Dom = opts.dom;

	Dom.settings.allow = "all";

	var ioreg;

	var self = this;

	Dom.use(domProxyPluginMeta, 'before');

	if (opts.disable) return;

	Dom.Handler.prototype.init = function() {
		this.opts.params = stealSession;
		this.listener = listener.bind(this);
		raja.on('message', this.listener);
	};

	var _finish = Dom.Handler.prototype.finish;
	Dom.Handler.prototype.finish = function(user, res) {
		res.set('X-Raja', opts.namespace);
		for (var name in user.headers) {
			if (name in {'Vary': 1, 'X-Grant': 1, 'ETag': 1}) {
				res.set(name, user.headers[name]);
			}
		}
		_finish.call(this, user, res);
	};

	Dom.Handler.prototype.loadLocal = raja.proxies.local.get.bind(raja.proxies.local);

	Dom.Handler.prototype.loadRemote = function(url, opts, cb) {
		raja.proxies.remote.get(url, opts, function(err, res) {
			cb(err, res && res.data);
		});
	};

	var _buildAuthored = Dom.Handler.prototype.buildAuthored;
	Dom.Handler.prototype.buildAuthored = CacheDebounce(function(resource, view, url, req, res, cb) {
		_buildAuthored.call(this, resource, view, url, req, res, cb);
	}, function(resource) {
		return resource.key;
	});

	var _buildUsed = Dom.Handler.prototype.buildUsed;
	Dom.Handler.prototype.buildUsed = CacheDebounce(function(resource, author, url, req, res, cb) {
		_buildUsed.call(this, resource, author, url, req, res, cb);
	}, function(resource) {
		return resource.key;
	});

	Dom.author(domProxyPlugin, 'before');
	Dom.use(domProxyPlugin, 'before');
	Dom.use(domProxyBusyPlugin, 'before');
	Dom.use(domProxyDonePlugin, 'after');

	function listener(msg) {
		var h = this;
		var reload = false;
		var lastKey = msg.parents.slice(-1).pop();
		msg.parents.forEach(function(key) {
			raja.store.get(key, function(err, resource) {
				if (err) return raja.error(err);
				if (!resource) return;
				if (msg.key == key && msg.mtime > resource.mtime && (!resource.itime || msg.mtime > resource.itime)) {
					resource.itime = msg.mtime;
				}
				var contentType = resource.headers && resource.headers['Content-Type'];
				if (lastKey != resource.key && contentType && (typeis(contentType, '*/javascript') || typeis(contentType, 'text/html'))) {
					debug("Detected change in", key);
					reload = true;
				}
				if (reload && resource.page && !resource.page.locked) { // avoid multiple releases at once
					debug("Detected change releases page", resource.key);
					Dom.pool.release(resource.page);
				}
			});
		});
	}

	function domProxyPluginMeta(page, resource, req, res) {
		page.when('ready', function(cb) {
			this.run(function(key, client, ns, done) {
				var node = document.getElementById('script');
				if (!node) node = document.createElement('script');
				node.id = 'raja';
				node.setAttribute('data-client', client);
				node.setAttribute('data-namespace', ns);
				node.setAttribute('data-room', key);
				node.type = "text/javascript";
				node.textContent = "(function(r) {if (r && r.ready) r.ready();})(window.raja);";
				if (!node.parentNode) {
					var tn = document.createTextNode("\n");
					document.head.insertBefore(node, document.head.firstChild);
					document.head.insertBefore(tn, node);
				}
				done();
			}, resource.key, opts.client, opts.namespace, cb);
		});
	}

	function domProxyPlugin(page, resource) {
		page.once('unload', function() {}); // keep it running !
		page.on('request', function(req) {
			var uriObj = URL.parse(req.uri);
			if (raja.client.pool.some(function(obj) {
				if (obj.host == uriObj.host) return true;
			})) {
				debug("allow io request", req.uri);
				if (ioreg && ioreg.test(uriObj.pathname)) {
					req.ignore = true;
				}
			} else if (uriObj.host != URL.parse(resource.url).host) {
				req.cancel = true;
			}
			if (!resource.resources) {
				return;
			}
//			if (resource.existings && resource.existings[req.uri]) {
//				// copy back because some of them won't have response because of the cache
//				resource.depend(req.uri);
//			}

			req.headers['Cache-Control'] = 'no-cache';
		});
		page.on('response', function(res) {
			if (!res.uri || res.uri == "about:blank") return;
			if (res.uri == resource.url) return; // ignore view and author
			if (!resource.resources) return;
			if (res.status >= 200 && res.status < 400) {
				if (!ioreg) {
					var iopath = raja.io && raja.io.socket && raja.io.socket.io.opts.path;
					if (iopath) ioreg = new RegExp("^" + iopath);
				}
				var uriObj = URL.parse(res.uri);
				if (ioreg && ioreg.test(uriObj.pathname)) {
					if (typeis(res.mime, 'javascript') == false) resource.live = true;
				} else {
					var headers = raja.headers(res);
					if (URL.parse(resource.url).host == uriObj.host) {
						headers['X-Raja'] = raja.opts.namespace;
					}
					resource.depend(raja.create(res.uri, headers));

					// this covers the case where a resource finishes loading after idle
					if (resource.valid) {
						resource.itime = new Date();
						resource.valid = false;
						resource.save();
					}
				}
			}
		});
	}

	function domProxyBusyPlugin(page, resource) {
		page.on('busy', function() {
			if (resource.itime && resource.itime > resource.mtime) {
				debug("busy page invalidates resource", resource.key);
				delete resource.itime;
				// update resources to make sure instances loaded on server receive messages
				resource.valid = false;
				resource.save();
			}
		});
	}

	function domProxyDonePlugin(page, resource, req, res) {
		var output = resource.output;
		resource.output = function(page, cb) {
			resource.output = output;
			if (!resource.itime || resource.itime <= resource.mtime) {
				resource.valid = true;
				resource.mtime = new Date();
			} else if (!resource.mtime) {
				resource.mtime = new Date();
			}
			output.call(resource, page, cb);
		};
	}

	var _get = Dom.Handler.prototype.get;
	Dom.Handler.prototype.get = function(url, depend, havingHeaders, cb) {
		if (!cb) {
			cb = havingHeaders;
			havingHeaders = null;
		}
		if (!cb) {
			cb = depend;
			depend = null;
		}
		var obj = {url: url, builder: 'dom'};
		if (havingHeaders) obj.headers = raja.headers(havingHeaders);
		else obj.headers = {};
		obj.headers['X-Raja'] = opts.namespace;
		debug('get', obj);
		raja.upsert(obj, function(err, resource) {
			if (err) return cb(err);
			if (!resource.headers) resource.headers = {};
			if (!resource.resources) resource.resources = {};
			if (depend) resource.depend(depend);
			if (!resource.output) resource.output = function(page, cb) {
				if (!page) return cb(new Error("resource.page was unset before output of\n" + resource.key));
				page.when('idle', function(wcb) {
					this.html(function(err, str) {
						cb(err, str);
						wcb();
					});
				});
			};
			cb(null, resource);
		});
	};
}

function mergeHeader(headers, name, val) {
	var header = headers[name];
	header = header ? header.split(',') : [];
	if (header.indexOf(val) < 0) header.push(val);
	headers[name] = header.join(',');
}

function stealSession(opts, req) {
	var session = req.session;
	var store = req.sessionStore;
	if (!store) return;
	// 1) generate a new session
	var decoy = {sessionStore: store};
	store.generate(decoy);
	// 2) give it same data
	for (var key in session) {
		if (decoy.session[key] != null || key == 'passport') continue;
		decoy.session[key] = session[key];
	}
	decoy.session.save();
	debug("hosted session", decoy.sessionID, decoy.session);
	// 3) set that session cookie into opts.cookies
	opts.cookies = agent.sessionCookie(req, decoy.sessionID, true);
}

<commit_msg>DOM proxy: author resource has no host - page has<commit_after>var URL = require('url');
var queue = require('queue-async');
var typeis = require('type-is').is;
var agent = require('../utils/agent');
var debug = require('debug')('raja:dom');
var CacheDebounce = require('cache-debounce');

module.exports = DomProxy;

function DomProxy(raja, opts) {
	if (!(this instanceof DomProxy)) return new DomProxy(raja, opts);
	this.raja = raja;
	var Dom = this.Dom = opts.dom;

	Dom.settings.allow = "all";

	var ioreg;

	var self = this;

	Dom.use(domProxyPluginMeta, 'before');

	if (opts.disable) return;

	Dom.Handler.prototype.init = function() {
		this.opts.params = stealSession;
		this.listener = listener.bind(this);
		raja.on('message', this.listener);
	};

	var _finish = Dom.Handler.prototype.finish;
	Dom.Handler.prototype.finish = function(user, res) {
		res.set('X-Raja', opts.namespace);
		for (var name in user.headers) {
			if (name in {'Vary': 1, 'X-Grant': 1, 'ETag': 1}) {
				res.set(name, user.headers[name]);
			}
		}
		_finish.call(this, user, res);
	};

	Dom.Handler.prototype.loadLocal = raja.proxies.local.get.bind(raja.proxies.local);

	Dom.Handler.prototype.loadRemote = function(url, opts, cb) {
		raja.proxies.remote.get(url, opts, function(err, res) {
			cb(err, res && res.data);
		});
	};

	var _buildAuthored = Dom.Handler.prototype.buildAuthored;
	Dom.Handler.prototype.buildAuthored = CacheDebounce(function(resource, view, url, req, res, cb) {
		_buildAuthored.call(this, resource, view, url, req, res, cb);
	}, function(resource) {
		return resource.key;
	});

	var _buildUsed = Dom.Handler.prototype.buildUsed;
	Dom.Handler.prototype.buildUsed = CacheDebounce(function(resource, author, url, req, res, cb) {
		_buildUsed.call(this, resource, author, url, req, res, cb);
	}, function(resource) {
		return resource.key;
	});

	Dom.author(domProxyPlugin, 'before');
	Dom.use(domProxyPlugin, 'before');
	Dom.use(domProxyBusyPlugin, 'before');
	Dom.use(domProxyDonePlugin, 'after');

	function listener(msg) {
		var h = this;
		var reload = false;
		var lastKey = msg.parents.slice(-1).pop();
		msg.parents.forEach(function(key) {
			raja.store.get(key, function(err, resource) {
				if (err) return raja.error(err);
				if (!resource) return;
				if (msg.key == key && msg.mtime > resource.mtime && (!resource.itime || msg.mtime > resource.itime)) {
					resource.itime = msg.mtime;
				}
				var contentType = resource.headers && resource.headers['Content-Type'];
				if (lastKey != resource.key && contentType && (typeis(contentType, '*/javascript') || typeis(contentType, 'text/html'))) {
					debug("Detected change in", key);
					reload = true;
				}
				if (reload && resource.page && !resource.page.locked) { // avoid multiple releases at once
					debug("Detected change releases page", resource.key);
					Dom.pool.release(resource.page);
				}
			});
		});
	}

	function domProxyPluginMeta(page, resource, req, res) {
		page.when('ready', function(cb) {
			this.run(function(key, client, ns, done) {
				var node = document.getElementById('script');
				if (!node) node = document.createElement('script');
				node.id = 'raja';
				node.setAttribute('data-client', client);
				node.setAttribute('data-namespace', ns);
				node.setAttribute('data-room', key);
				node.type = "text/javascript";
				node.textContent = "(function(r) {if (r && r.ready) r.ready();})(window.raja);";
				if (!node.parentNode) {
					var tn = document.createTextNode("\n");
					document.head.insertBefore(node, document.head.firstChild);
					document.head.insertBefore(tn, node);
				}
				done();
			}, resource.key, opts.client, opts.namespace, cb);
		});
	}

	function domProxyPlugin(page, resource) {
		var host = URL.parse(page.uri).host;
		page.once('unload', function() {}); // keep it running !
		page.on('request', function(req) {
			var uriObj = URL.parse(req.uri);
			if (raja.client.pool.some(function(obj) {
				if (obj.host == uriObj.host) return true;
			})) {
				debug("allow io request", req.uri);
				if (ioreg && ioreg.test(uriObj.pathname)) {
					req.ignore = true;
				}
			} else if (uriObj.host != host) {
				req.cancel = true;
			}
			if (!resource.resources) {
				return;
			}
//			if (resource.existings && resource.existings[req.uri]) {
//				// copy back because some of them won't have response because of the cache
//				resource.depend(req.uri);
//			}

			req.headers['Cache-Control'] = 'no-cache';
		});
		page.on('response', function(res) {
			if (!res.uri || res.uri == "about:blank") return;
			if (res.uri == resource.url) return; // ignore self
			if (!resource.resources) return;
			if (res.status >= 200 && res.status < 400) {
				if (!ioreg) {
					var iopath = raja.io && raja.io.socket && raja.io.socket.io.opts.path;
					if (iopath) ioreg = new RegExp("^" + iopath);
				}
				var uriObj = URL.parse(res.uri);
				if (ioreg && ioreg.test(uriObj.pathname)) {
					if (typeis(res.mime, 'javascript') == false) resource.live = true;
				} else {
					var headers = raja.headers(res);
					if (host == uriObj.host) {
						headers['X-Raja'] = raja.opts.namespace;
					}
					resource.depend(raja.create(res.uri, headers));

					// this covers the case where a resource finishes loading after idle
					if (resource.valid) {
						resource.itime = new Date();
						resource.valid = false;
						resource.save();
					}
				}
			}
		});
	}

	function domProxyBusyPlugin(page, resource) {
		page.on('busy', function() {
			if (resource.itime && resource.itime > resource.mtime) {
				debug("busy page invalidates resource", resource.key);
				delete resource.itime;
				// update resources to make sure instances loaded on server receive messages
				resource.valid = false;
				resource.save();
			}
		});
	}

	function domProxyDonePlugin(page, resource, req, res) {
		var output = resource.output;
		resource.output = function(page, cb) {
			resource.output = output;
			if (!resource.itime || resource.itime <= resource.mtime) {
				resource.valid = true;
				resource.mtime = new Date();
			} else if (!resource.mtime) {
				resource.mtime = new Date();
			}
			output.call(resource, page, cb);
		};
	}

	var _get = Dom.Handler.prototype.get;
	Dom.Handler.prototype.get = function(url, depend, havingHeaders, cb) {
		if (!cb) {
			cb = havingHeaders;
			havingHeaders = null;
		}
		if (!cb) {
			cb = depend;
			depend = null;
		}
		var obj = {url: url, builder: 'dom'};
		if (havingHeaders) obj.headers = raja.headers(havingHeaders);
		else obj.headers = {};
		obj.headers['X-Raja'] = opts.namespace;
		debug('get', obj);
		raja.upsert(obj, function(err, resource) {
			if (err) return cb(err);
			if (!resource.headers) resource.headers = {};
			if (!resource.resources) resource.resources = {};
			if (depend) resource.depend(depend);
			if (!resource.output) resource.output = function(page, cb) {
				if (!page) return cb(new Error("resource.page was unset before output of\n" + resource.key));
				page.when('idle', function(wcb) {
					this.html(function(err, str) {
						cb(err, str);
						wcb();
					});
				});
			};
			cb(null, resource);
		});
	};
}

function mergeHeader(headers, name, val) {
	var header = headers[name];
	header = header ? header.split(',') : [];
	if (header.indexOf(val) < 0) header.push(val);
	headers[name] = header.join(',');
}

function stealSession(opts, req) {
	var session = req.session;
	var store = req.sessionStore;
	if (!store) return;
	// 1) generate a new session
	var decoy = {sessionStore: store};
	store.generate(decoy);
	// 2) give it same data
	for (var key in session) {
		if (decoy.session[key] != null || key == 'passport') continue;
		decoy.session[key] = session[key];
	}
	decoy.session.save();
	debug("hosted session", decoy.sessionID, decoy.session);
	// 3) set that session cookie into opts.cookies
	opts.cookies = agent.sessionCookie(req, decoy.sessionID, true);
}

<|endoftext|>


<commit_before>//TODO: The set user id and set context functions wont be scoped to each request in express.js for example.

trace = require('tracejs').trace;
http = require('http');
fs = require('fs');
path = require('path');

var defaultErrorHash = {
  apiKey : "",
  notifier : {
    name : "Bugsnag Node Notifier",
    version : "",
    url : "www.bugsnag.com"
  }
}
var releaseStage;
var userIdLambda;
var contextLambda;
var projectDirectory;
var applicationVersion = "unknown";
var onUncaughtException = function(err) {
  console.log(err);
  process.exit(1);
}

getPackageVersion = function(packageJSON) {
  try {
    contents = fs.readFileSync(packageJSON, 'UTF-8');
  } catch(e) {
    return "unknown";
  }
  packageInfo = JSON.parse(contents);
  return packageInfo.version;
}

exports.handleExceptions = function() {
  process.on('uncaughtException', function(err) {
    exports.notify(err);
    onUncaughtException(err);
  });
}

// Send a test notification
exports.testNotification = function() {
  exports.notify(new Error("Test error "));
}

// Notify about a caught error
exports.notify = function(error) {
  if(defaultErrorHash.apiKey == "") {
    console.log("Bugsnag: No apiKey set - not notifying.");
    return;
  } else {
    console.log("Bugnsag: Notifying bugsnag of error");
  }
  var stacktrace = trace(error);
  var errorList = [{
    userId: userIdLambda(),
    appVersion: applicationVersion,
    releaseStage: releaseStage,
    metaData: {
      environment: process.env
    },
    exceptions: [{
      errorClass: "Error",
      message: stacktrace.first_line,
      stacktrace: []
    }]
  }];
  errorList[0].metadata.environment.memoryUsage = process.memoryUsage();

  for(var i = 0, len = stacktrace.frames.length; i < len; ++i) {
    errorList[0].exceptions[0].stacktrace[i] = {
      file: stacktrace.frames[i].filename,
      lineNumber: stacktrace.frames[i].line,
      method: stacktrace.frames[i].named_location
    }
    if( projectDirectory != "" &&
        stacktrace.frames[i].filename.indexOf(projectDirectory) == 0) {
      if ( stacktrace.frames[i].filename.indexOf("node_modules") == -1 ) {
        errorList[0].exceptions[0].stacktrace[i].inProject = true;
      }
      errorList[0].exceptions[0].stacktrace[i].file = stacktrace.frames[i].filename.substr(projectDirectory.length + 1);
    }
  }

  if ( contextLambda !== undefined ) {
    errorList[0].context = contextLambda();
  }

  defaultErrorHash.errors = errorList;

  var payload = JSON.stringify(defaultErrorHash);
  var options = {
    host: 'api.bugsnag.com',
    port: 8000,
    path: '/notify',
    method: 'POST',
    headers: {
      "Content-Type": 'application/json',
      "Content-Length": payload.length
    }
  };

  var req = http.request(options, function(response) {});
  req.write(payload, 'utf8');
  req.end();
}

// Register to process uncaught exceptions properly
exports.register = function(apiKey, options) {
  options = (options === undefined ? {} : options)

  Error.stackTraceLimit = Infinity;
  defaultErrorHash.apiKey = apiKey;
  releaseStage = (options.releaseStage === undefined ? "release" : options.releaseStage);
  contextLambda = (options.context === undefined ? undefined : options.context);
  userIdLambda = (options.userId === undefined ? function() {return "unknown";} : options.userId);
  applicationVersion = (options.applicationVersion === undefined ? undefined : options.applicationVersion);
  if ( options.packageJSON !== undefined ) {
    applicationVersion = getPackageVersion(options.packageJSON);
  }
  if( applicationVersion === undefined || applicationVersion == "unknown" ) {
    applicationVersion = getPackageVersion(path.join(__dirname, '../../package.json'));
  }

  projectDirectory = (options.projectDirectory === undefined ? path.join(__dirname, "../..") : options.projectDirectory);
  defaultErrorHash.notifier.version = getPackageVersion(path.join(__dirname,'package.json'))
}

// Set a lambda function to detail the context of the call
exports.setContext = function(lambda) {
  contextLambda = lambda
}

// Set a lambda function to detail the user affected
exports.setUserId = function(lambda) {
  userIdLambda = lambda;
}

// Set a lambda function to detail what happens once an uncaught exception is processed. Defaults to exit(1)
exports.setUncaughtExceptionHandler = function(lambda) {
  onUncaughtException = lambda;
}

// Set the application version
exports.setApplicationVersion = function(version) {
  applicationVersion = version;
}<commit_msg>Better error class and message parsing<commit_after>//TODO: The set user id and set context functions wont be scoped to each request in express.js for example.

trace = require('tracejs').trace;
http = require('http');
fs = require('fs');
path = require('path');

var defaultErrorHash = {
  apiKey : "",
  notifier : {
    name : "Bugsnag Node Notifier",
    version : "",
    url : "www.bugsnag.com"
  }
}
var releaseStage;
var userIdLambda;
var contextLambda;
var projectDirectory;
var applicationVersion = "unknown";
var onUncaughtException = function(err) {
  console.log(err);
  process.exit(1);
}

getPackageVersion = function(packageJSON) {
  try {
    contents = fs.readFileSync(packageJSON, 'UTF-8');
  } catch(e) {
    return "unknown";
  }
  packageInfo = JSON.parse(contents);
  return packageInfo.version;
}

exports.handleExceptions = function() {
  process.on('uncaughtException', function(err) {
    exports.notify(err);
    onUncaughtException(err);
  });
}

// Send a test notification
exports.testNotification = function() {
  exports.notify(new Error("Test error "));
}

// Notify about a caught error
exports.notify = function(error) {
  if(defaultErrorHash.apiKey == "") {
    console.log("Bugsnag: No apiKey set - not notifying.");
    return;
  } else {
    console.log("Bugnsag: Notifying bugsnag of error");
  }
  var stacktrace = trace(error);
  var errorList = [{
    userId: userIdLambda(),
    appVersion: applicationVersion,
    releaseStage: releaseStage,
    metaData: {
      environment: process.env
    },
    exceptions: [{
      errorClass: stacktrace.first_line.split(": ")[0],
      message: stacktrace.first_line.split(": ")[1],
      stacktrace: []
    }]
  }];
  errorList[0].metadata.environment.memoryUsage = process.memoryUsage();

  for(var i = 0, len = stacktrace.frames.length; i < len; ++i) {
    errorList[0].exceptions[0].stacktrace[i] = {
      file: stacktrace.frames[i].filename,
      lineNumber: stacktrace.frames[i].line,
      method: stacktrace.frames[i].named_location
    }
    if( projectDirectory != "" &&
        stacktrace.frames[i].filename.indexOf(projectDirectory) == 0) {
      if ( stacktrace.frames[i].filename.indexOf("node_modules") == -1 ) {
        errorList[0].exceptions[0].stacktrace[i].inProject = true;
      }
      errorList[0].exceptions[0].stacktrace[i].file = stacktrace.frames[i].filename.substr(projectDirectory.length + 1);
    }
  }

  if ( contextLambda !== undefined ) {
    errorList[0].context = contextLambda();
  }

  defaultErrorHash.errors = errorList;

  var payload = JSON.stringify(defaultErrorHash);
  var options = {
    host: 'api.bugsnag.com',
    port: 8000,
    path: '/notify',
    method: 'POST',
    headers: {
      "Content-Type": 'application/json',
      "Content-Length": payload.length
    }
  };

  var req = http.request(options, function(response) {});
  req.write(payload, 'utf8');
  req.end();
}

// Register to process uncaught exceptions properly
exports.register = function(apiKey, options) {
  options = (options === undefined ? {} : options)

  Error.stackTraceLimit = Infinity;
  defaultErrorHash.apiKey = apiKey;
  releaseStage = (options.releaseStage === undefined ? "release" : options.releaseStage);
  contextLambda = (options.context === undefined ? undefined : options.context);
  userIdLambda = (options.userId === undefined ? function() {return "unknown";} : options.userId);
  applicationVersion = (options.applicationVersion === undefined ? undefined : options.applicationVersion);
  if ( options.packageJSON !== undefined ) {
    applicationVersion = getPackageVersion(options.packageJSON);
  }
  if( applicationVersion === undefined || applicationVersion == "unknown" ) {
    applicationVersion = getPackageVersion(path.join(__dirname, '../../package.json'));
  }

  projectDirectory = (options.projectDirectory === undefined ? path.join(__dirname, "../..") : options.projectDirectory);
  defaultErrorHash.notifier.version = getPackageVersion(path.join(__dirname,'package.json'))
}

// Set a lambda function to detail the context of the call
exports.setContext = function(lambda) {
  contextLambda = lambda
}

// Set a lambda function to detail the user affected
exports.setUserId = function(lambda) {
  userIdLambda = lambda;
}

// Set a lambda function to detail what happens once an uncaught exception is processed. Defaults to exit(1)
exports.setUncaughtExceptionHandler = function(lambda) {
  onUncaughtException = lambda;
}

// Set the application version
exports.setApplicationVersion = function(version) {
  applicationVersion = version;
}<|endoftext|>


<commit_before>/*global Quintus:false */

/*global Quintus:false */
/**
Quintus HTML5 Game Engine - TMX Loader module

Module responsible for loading Tiled TMX files

@module Quintus.Input
*/

/**
 * Quintus TMX Loading module
 *
 * @class Quintus.TMX
 */
Quintus.TMX = function(Q) {

 // Add TMX file loading support to Quintus
 Q.assetTypes['tmx'] = 'TMX';

 // Load a TMX file as a parsed XML DOM
 Q.loadAssetTMX =  function(key,src,callback,errorCallback) {

   // Piggyback on loadAssetOther's AJAX call
   Q.loadAssetOther(key,src,function(key,responseText) {
     var parser = new DOMParser();
     var doc = parser.parseFromString(responseText, "application/xml");
     // save the asset as the parsed doc
     callback(key,doc);
   }, errorCallback);

 }

 Q._tmxExtractAssetName = function(result) {
   var source = result.getAttribute("source"),
   sourceParts = source.split("/");
   // only return the last part of the asset string
   return sourceParts[sourceParts.length - 1];
 }

 Q._tmxExtractSources = function(asset) {
   var results = asset.querySelectorAll("[source]");
   return Q._map(results,Q._tmxExtractAssetName);

 };

 Q.loadTMX = function(files,callback,options) {
   if(Q._isString(files)) {
     files = Q._normalizeArg(files);
   }

   var tmxFiles = [];
   Q._each(files,function(file) {
     if(Q._fileExtension(file) == 'tmx') {
        tmxFiles.push(file);
     }
   });

   var additionalAssets = [];

   Q.load(files,function() {
     Q._each(tmxFiles,function(tmxFile) {
       var sources = Q._tmxExtractSources(Q.asset(tmxFile));
       additionalAssets = additionalAssets.concat(sources);
     });

     if(additionalAssets.length > 0) {
       Q.load(additionalAssets,callback,options);
     } else {
       callback();
     }
   });

 };

 function attr(elem,atr) {
   var value = elem.getAttribute(atr);
   return isNaN(value) ? value : +value;
 }

 function parseProperties(elem) {
   var propElems = elem.querySelectorAll("property"),
       props = {};

   for(var i = 0; i < propElems.length; i++) {
     var propElem = propElems[i];
     props[attr(propElem,'name')] = attr(propElem,'value');
   }
   return props;
 }

 Q._tmxLoadTilesets = function(tilesets, tileProperties) {
   var gidMap = [];

   for(var t = 0; t < tilesets.length;t++) {
     var tileset = tilesets[t],
         sheetName = attr(tileset,"name");
         gid = attr(tileset,"firstgid"),
         assetName = Q._tmxExtractAssetName(tileset.querySelector("image")),
         tilesetTileProps = {};
         tilesetProps = { tileW: attr(tileset,"tilewidth"),
                          tileH: attr(tileset,"tileheight"),
                          spacingX: attr(tileset,"spacing"),
                          spacingY: attr(tileset,"spacing")
                        }

     var tiles = tileset.querySelectorAll("tile");
     for(var i = 0;i < tiles.length;i++) {
       var tile = tiles[i];
       var tileId = attr(tile,"id");
       var tileGid = gid + tileId;

       var properties = parseProperties(tile);

       if(properties.points) {
         properties.points = Q._map(properties.points.split(" "),function(pt) {
           var pts = pt.split(",");
           return [ parseFloat(pts[0]), parseFloat(pts[1]) ];
         });
       }

       // save the properties indexed by GID for creating objects
       tileProperties[tileGid] = properties;

       // save the properties indexed by tile number for the frame properties
       tilesetTileProps[tileId] = properties;
     }
     tilesetProps.frameProperties = tilesetTileProps;
     gidMap.push([ gid, sheetName ]);
     Q.sheet(sheetName, assetName,  tilesetProps);

   }
   return gidMap;
 };

 Q._tmxProcessImageLayer = function(stage,gidMap,tileProperties,layer) {
   var assetName = Q._tmxExtractAssetName(layer.querySelector("image"));
   var properties = parseProperties(layer)
   properties.asset = assetName;

   stage.insert(new Q.Repeater(properties));
 };

 // get the first entry in the gid map that gives
 // a gid offset
 Q._lookupGid = function(gid,gidMap) {
   var idx = 0;

   while(gidMap[idx+1] && gid >= gidMap[idx+1][0]) {
     idx++;
   }
   return gidMap[idx];
 }

 Q._tmxProcessTileLayer = function(stage,gidMap,tileProperties,layer) {
   var tiles = layer.querySelectorAll("tile"),
       width = attr(layer,'width'),
       height = attr(layer,'height');

   var gidDetails,gidOffset, sheetName;

   var data = [], idx=0;
   for(var y=0;y<height;y++) {
     data[y] = [];
     for(var x=0;x<width;x++) {
       var gid = attr(tiles[idx],"gid");
       if(gid == 0) {
         data[y].push(null)
       } else {
         // If we don't know what tileset this map is associated with
         // figure it out by looking up the gid of the tile w/
         // and match to the tilesef
         if(!gidOffset) {
           gidDetails = Q._lookupGid(attr(tiles[idx],"gid"),gidMap);
           gidOffset = gidDetails[0];
           sheetName = gidDetails[1];
         }
         data[y].push(gid - gidOffset);
       }
       idx++;
     }
   }

   var tileLayerProperties = Q._extend({
     tileW: Q.sheet(sheetName).tileW,
     tileH: Q.sheet(sheetName).tileH,
     sheet: sheetName,
     tiles: data
     },parseProperties(layer));

   var TileLayerClass = tileLayerProperties.Class || 'TileLayer';

   if(tileLayerProperties['collision']) {
     stage.collisionLayer(new Q[TileLayerClass](tileLayerProperties));
   } else {
     stage.insert(new Q[TileLayerClass](tileLayerProperties));
   }
 };

 Q._tmxProcessObjectLayer = function(stage,gidMap,tileProperties,layer) {
   var objects = layer.querySelectorAll("object");
   for(var i=0;i < objects.length;i++) {
     var obj = objects[i],
         gid = attr(obj,"gid"),
         x = attr(obj,'x'),
         y = attr(obj,'y'),
         w =
         properties = tileProperties[gid],
         overrideProperties = parseProperties(obj);

     if(!properties) { throw "Invalid TMX Object: missing properties for GID:" + gid }
     if(!properties['Class']) { throw "Invalid TMX Object: missing Class for GID:" + gid }

     var className = properties['Class'];
     if(!className) { throw "Invalid TMX Object Class: " + className + " GID:" + gid }

     var p = Q._extend(Q._extend({ x: x, y: y }, properties), overrideProperties);

     // Offset the sprite
     var sprite = new Q[className](p);
     sprite.p.x += sprite.p.w/2;
     sprite.p.y -= sprite.p.h/2;

     stage.insert(sprite);
   }

 };

 Q._tmxProcessors = { 'objectgroup': Q._tmxProcessObjectLayer,
                      'layer': Q._tmxProcessTileLayer,
                      'imagelayer': Q._tmxProcessImageLayer }

 Q.stageTMX = function(dataAsset,stage) {
    var data = Q._isString(dataAsset) ?  Q.asset(dataAsset) : dataAsset;

    var tileProperties = {};

    // Load Tilesets
    var tilesets = data.getElementsByTagName("tileset");
    var gidMap = Q._tmxLoadTilesets(tilesets,tileProperties);

    // Go through each of the layers
    Q._each(data.documentElement.childNodes,function(layer) {
      var layerType = layer.tagName;
      if(Q._tmxProcessors[layerType]) {
        Q._tmxProcessors[layerType](stage, gidMap, tileProperties, layer);
      }
    });
  };

};

/*
    var sheetName;
    var tp = {};

    for(t = 0; t < tilesets.length; t++) {
      sheetName = tilesets[t].getAttribute("name");
      var image = tilesets[t].querySelector("image").getAttribute("source");
      var spacing = parseInt(tilesets[t].getAttribute("spacing"));
      width = tw + spacing;
      height = th + spacing;
      console.log(sheetName + ": " + image + " " + width + " " + height);
      Q.sheet(sheetName, image, { tilew: width, tileh: height });

      var tileProps = tilesets[t].querySelectorAll("tile");
      for(i = 0; i < tileProps.length; i++) {
        var id = parseInt(tileProps[i].getAttribute("id"));
        var props = tileProps[i].querySelectorAll("property");
        for(j = 0; j < props.length; j++) {
          var propName = props[j].getAttribute("name");
          var propValue = props[j].getAttribute("value");
          if(tp[id] === undefined) {
            tp[id] = {};
          }
          tp[id][propName] = propValue;
        }
      }
    }

    return tp;
  };

  Q.loadTMXLayer = function(layer) {

  }

  Q._tmxParseTileCollisionObjects = function(tw,th,tp) {
    var tileCollisionObjects = [];
    for(i in tp) {
      if(tp[i]["shape"] !== undefined) {
        console.log("Tile " + i + " shape:");

        tileCollisionObjects[i] = {
          p: {
            w: tw,
            h: th
            cx: tw/2,
            cy: th/2
          }
        };

        tileCollisionObjects[i].p.points = [];

        var points = tp[i]["shape"].split(" "),
            xy;

        for(j = 0; j < points.length; j++) {
          tileCollisionObjects[i].p.points[j] = [];
          xy = points[j].split(",");
          var w = newLayer.tileCollisionObjects[i].p.w,
              h = newLayer.tileCollisionObjects[i].p.h,
              x = parseFloat(xy[0]),
              y = parseFloat(xy[1]);
          console.log(x + ", " + y);
          newLayer.tileCollisionObjects[i].p.points[j] = [(x * w) - w/2, (y * h) - h/2];
        }
        console.log(newLayer.tileCollisionObjects[i].p.points);
      }
    }

    return tileCollisionObjects;
  }

  /*
   Important notes about this function:

   For this to work correctly, your player object must be set up in advance, including
   the player's sprite sheet.

   When editing the map in tiled, the tileset image must be in the same directory
   as the map file.  When you're done, put the tileset image in the images
   directory and the tmx file in the data directory as normal.

   The collision layer will be the layer with the property of "collision" (just having
   this property is enough, even if it's set to 0 or false or something).  Multiple
   layers with this property will result in undefined behavior, so don't do it.

   You can use Tiled to set the player's starting position.  Create an object layer
   and place a rectangle with the upper left corner where you want the player to start,
   and set the type of that object to "Player".  Then, go back to your collision layer
   and set the "objectLayer" property to the name of the object layer you just created.
   You can also set the positions of enemies in the same manner.  Just set the type of
   the objects to names to enemy classes.

   You can designate sensor areas by adding squares to the object layer with Tiled's
   square tool.  You can also add free-floating tile objects using Tiled's Insert Tile
   tool.

   Tiles can have collision shapes (for instance, if you want to make tiles that slope).
   In Tiled, create a tile property called "shape", in the format "x,y x,y x,y ... x,y'",
   where x and y are floating point values such that 0,0 is the upper left corner and
   1,1 is the lower right corner.  Undefined behavior may result if either point is
   outside the 0 to 1 range.

   To create a tile that slopes up,
   shape should be: "0,1 1,0 1,1"
   To create a tile that slopes down,
   shape should be: "0,0 1,1 0,1"

   If no shape property exists, the collision shape will be the shape of the entire tile.

   PLANNED FEATURES:
   * Multiple tilesets per map
   * Support for Tiled's polygon object tool

   NOT PLANNED:
   * There are no plans to support Tiled's ellipse object tool
  Q.loadTMX = function(stage,dataAsset) {
    console.log("Loading TMX " + dataAsset);
    var data = Q._isString(dataAsset) ?  Q.asset(dataAsset) : dataAsset;

    var parser = new DOMParser(),
    doc = parser.parseFromString(data, "application/xml");
    console.log(doc);

    // Get width and height of map
    var tw = parseInt(doc.documentElement.getAttribute("tilewidth"));
    var th = parseInt(doc.documentElement.getAttribute("tileheight"));

    console.log(tw + " x " + th);

    // Load Tilesets
    var tilesets = doc.getElementsByTagName("tileset");
    var tp = Q._tmxLoadTilesets(tilesets);

    var tileCollisionObjects = Q._tmxParseTileCollisionObjects(tw,th,tp);

    // Load layers
    var layers = doc.querySelectorAll("layer");

    for(var l = 0; l < layers.length; l++) {
      var layer = layers[l];
      var properties = layer.querySelectorAll("property");
      var layerName = layer.getAttribute("name");

      // Do we support multiple tilesets per layer?  I'm assuming no for now.
      var tileset = tilesets[0].getAttribute("name");
      var collision = layer.querySelector("property[name=collision]");
      var objectLayerName = layer.querySelectorAll("property[name=objectLayer]");

      var data =

      if(collision) {
        console.log("inserting collision layer " + layerName);
        console.log(dataAsset + " " + sheetName + " " + layerName + " " + tw + " " + th);
        var newLayer = stage.collisionLayer(new Q.TileLayer({
          dataAsset: dataAsset,
          sheet: sheetName,
          layerIndex: l,
          tileW: tw,
          tileH: th,
          tileProperties: tp
          }));

        console.log(newLayer);
      } else {
        console.log("inserting layer " + layerName);
        var newLayer = stage.insert(new Q.TileLayer({
          dataAsset: dataAsset,
          sheet: sheetName,
          layerIndex: l,
          tileW: tw,
          tileH: th,
          tileProperties: tp,
          type: Q.SPRITE_NONE
          }));
        }

        if(objectLayerName.length > 0) {
          console.log("object layer:");
          console.log(objectLayerName);
          objectLayer = objectLayerName[0].getAttribute("value");
          console.log("  inserting object layer " + objectLayer);

          var objects = doc.documentElement.querySelector("objectgroup[name='" + objectLayer + "']").querySelectorAll("object");
          for(i = 0; i < objects.length; i++) {
            var oType = objects[i].getAttribute("type");
            var sProps = objects[i].querySelectorAll("property");
            var gid = parseInt(objects[i].getAttribute("gid"));
            var spriteProperties = new Object;
            spriteProperties['objectType'] = oType;
            for(j = 0; j < sProps.length; j++) {
              spriteProperties[sProps[j].getAttribute("name")] = sProps[j].getAttribute("value");
            }
            console.log("Creating instance of " + oType + " at " + parseInt(objects[i].getAttribute("x")) +
            ", " + parseInt(objects[i].getAttribute("y")));

            if(Q[oType] !== undefined) {
              console.log("Creating sprite " + oType + " at " + parseInt(objects[i].getAttribute("x")) +
              ", " + parseInt(objects[i].getAttribute("y")));
              var s = stage.insert(new Q[oType]({
                x: parseInt(objects[i].getAttribute("x")),
                y: parseInt(objects[i].getAttribute("y")),
                name: objects[i].getAttribute("name"),
                spriteProperties: spriteProperties
                }));
                if(oType == "Player") {
                  stage.add("viewport").follow(s);
                }
                console.log(s);
              } else if(gid) {
                console.log("Creating free floating tile " + gid + " at " +
                parseInt(objects[i].getAttribute("x")) +
                ", " + parseInt(objects[i].getAttribute("y")));

                var s = stage.insert(new Q.Sprite({
                  x: parseInt(objects[i].getAttribute("x")),
                  y: parseInt(objects[i].getAttribute("y")),
                  sheet: sheetName,
                  name: objects[i].getAttribute("name"),
                  spriteProperties: spriteProperties,
                  frame: gid - 1
                  }));

                  s.p.x += s.p.w / 2;
                  s.p.y -= s.p.h / 2;
                  console.log(s);
                } else {
                  console.log("Creating sensor at " + parseInt(objects[i].getAttribute("x")) +
                  ", " + parseInt(objects[i].getAttribute("y")));
                  var w = parseInt(objects[i].getAttribute("width")),
                  h = parseInt(objects[i].getAttribute("height"));
                  var s = stage.insert(new Q.Sprite({
                    x: parseInt(objects[i].getAttribute("x")) + w / 2,
                    y: parseInt(objects[i].getAttribute("y")) + h / 2,
                    w: w,
                    h: h,
                    name: objects[i].getAttribute("name"),
                    spriteProperties: spriteProperties,
                    sensor: true
                    }));
                    console.log(s);
                  }
                }
              }

            }
          },

        };
*/
<commit_msg>TMX comment cleanup<commit_after>/*global Quintus:false */

/*global Quintus:false */
/**
Quintus HTML5 Game Engine - TMX Loader module

Module responsible for loading Tiled TMX files

@module Quintus.Input
*/

/**
 * Quintus TMX Loading module
 *
 * @class Quintus.TMX
 */
Quintus.TMX = function(Q) {

 // Add TMX file loading support to Quintus
 Q.assetTypes['tmx'] = 'TMX';

 // Load a TMX file as a parsed XML DOM
 Q.loadAssetTMX =  function(key,src,callback,errorCallback) {

   // Piggyback on loadAssetOther's AJAX call
   Q.loadAssetOther(key,src,function(key,responseText) {
     var parser = new DOMParser();
     var doc = parser.parseFromString(responseText, "application/xml");
     // save the asset as the parsed doc
     callback(key,doc);
   }, errorCallback);

 }

 Q._tmxExtractAssetName = function(result) {
   var source = result.getAttribute("source"),
   sourceParts = source.split("/");
   // only return the last part of the asset string
   return sourceParts[sourceParts.length - 1];
 }

 Q._tmxExtractSources = function(asset) {
   var results = asset.querySelectorAll("[source]");
   return Q._map(results,Q._tmxExtractAssetName);

 };

 Q.loadTMX = function(files,callback,options) {
   if(Q._isString(files)) {
     files = Q._normalizeArg(files);
   }

   var tmxFiles = [];
   Q._each(files,function(file) {
     if(Q._fileExtension(file) == 'tmx') {
        tmxFiles.push(file);
     }
   });

   var additionalAssets = [];

   Q.load(files,function() {
     Q._each(tmxFiles,function(tmxFile) {
       var sources = Q._tmxExtractSources(Q.asset(tmxFile));
       additionalAssets = additionalAssets.concat(sources);
     });

     if(additionalAssets.length > 0) {
       Q.load(additionalAssets,callback,options);
     } else {
       callback();
     }
   });

 };

 function attr(elem,atr) {
   var value = elem.getAttribute(atr);
   return isNaN(value) ? value : +value;
 }

 function parseProperties(elem) {
   var propElems = elem.querySelectorAll("property"),
       props = {};

   for(var i = 0; i < propElems.length; i++) {
     var propElem = propElems[i];
     props[attr(propElem,'name')] = attr(propElem,'value');
   }
   return props;
 }

 Q._tmxLoadTilesets = function(tilesets, tileProperties) {
   var gidMap = [];

   for(var t = 0; t < tilesets.length;t++) {
     var tileset = tilesets[t],
         sheetName = attr(tileset,"name");
         gid = attr(tileset,"firstgid"),
         assetName = Q._tmxExtractAssetName(tileset.querySelector("image")),
         tilesetTileProps = {};
         tilesetProps = { tileW: attr(tileset,"tilewidth"),
                          tileH: attr(tileset,"tileheight"),
                          spacingX: attr(tileset,"spacing"),
                          spacingY: attr(tileset,"spacing")
                        }

     var tiles = tileset.querySelectorAll("tile");
     for(var i = 0;i < tiles.length;i++) {
       var tile = tiles[i];
       var tileId = attr(tile,"id");
       var tileGid = gid + tileId;

       var properties = parseProperties(tile);

       if(properties.points) {
         properties.points = Q._map(properties.points.split(" "),function(pt) {
           var pts = pt.split(",");
           return [ parseFloat(pts[0]), parseFloat(pts[1]) ];
         });
       }

       // save the properties indexed by GID for creating objects
       tileProperties[tileGid] = properties;

       // save the properties indexed by tile number for the frame properties
       tilesetTileProps[tileId] = properties;
     }
     tilesetProps.frameProperties = tilesetTileProps;
     gidMap.push([ gid, sheetName ]);
     Q.sheet(sheetName, assetName,  tilesetProps);

   }
   return gidMap;
 };

 Q._tmxProcessImageLayer = function(stage,gidMap,tileProperties,layer) {
   var assetName = Q._tmxExtractAssetName(layer.querySelector("image"));
   var properties = parseProperties(layer)
   properties.asset = assetName;

   stage.insert(new Q.Repeater(properties));
 };

 // get the first entry in the gid map that gives
 // a gid offset
 Q._lookupGid = function(gid,gidMap) {
   var idx = 0;

   while(gidMap[idx+1] && gid >= gidMap[idx+1][0]) {
     idx++;
   }
   return gidMap[idx];
 }

 Q._tmxProcessTileLayer = function(stage,gidMap,tileProperties,layer) {
   var tiles = layer.querySelectorAll("tile"),
       width = attr(layer,'width'),
       height = attr(layer,'height');

   var gidDetails,gidOffset, sheetName;

   var data = [], idx=0;
   for(var y=0;y<height;y++) {
     data[y] = [];
     for(var x=0;x<width;x++) {
       var gid = attr(tiles[idx],"gid");
       if(gid == 0) {
         data[y].push(null)
       } else {
         // If we don't know what tileset this map is associated with
         // figure it out by looking up the gid of the tile w/
         // and match to the tilesef
         if(!gidOffset) {
           gidDetails = Q._lookupGid(attr(tiles[idx],"gid"),gidMap);
           gidOffset = gidDetails[0];
           sheetName = gidDetails[1];
         }
         data[y].push(gid - gidOffset);
       }
       idx++;
     }
   }

   var tileLayerProperties = Q._extend({
     tileW: Q.sheet(sheetName).tileW,
     tileH: Q.sheet(sheetName).tileH,
     sheet: sheetName,
     tiles: data
     },parseProperties(layer));

   var TileLayerClass = tileLayerProperties.Class || 'TileLayer';

   if(tileLayerProperties['collision']) {
     stage.collisionLayer(new Q[TileLayerClass](tileLayerProperties));
   } else {
     stage.insert(new Q[TileLayerClass](tileLayerProperties));
   }
 };

 Q._tmxProcessObjectLayer = function(stage,gidMap,tileProperties,layer) {
   var objects = layer.querySelectorAll("object");
   for(var i=0;i < objects.length;i++) {
     var obj = objects[i],
         gid = attr(obj,"gid"),
         x = attr(obj,'x'),
         y = attr(obj,'y'),
         w =
         properties = tileProperties[gid],
         overrideProperties = parseProperties(obj);

     if(!properties) { throw "Invalid TMX Object: missing properties for GID:" + gid }
     if(!properties['Class']) { throw "Invalid TMX Object: missing Class for GID:" + gid }

     var className = properties['Class'];
     if(!className) { throw "Invalid TMX Object Class: " + className + " GID:" + gid }

     var p = Q._extend(Q._extend({ x: x, y: y }, properties), overrideProperties);

     // Offset the sprite
     var sprite = new Q[className](p);
     sprite.p.x += sprite.p.w/2;
     sprite.p.y -= sprite.p.h/2;

     stage.insert(sprite);
   }

 };

 Q._tmxProcessors = { 'objectgroup': Q._tmxProcessObjectLayer,
                      'layer': Q._tmxProcessTileLayer,
                      'imagelayer': Q._tmxProcessImageLayer }

 Q.stageTMX = function(dataAsset,stage) {
    var data = Q._isString(dataAsset) ?  Q.asset(dataAsset) : dataAsset;

    var tileProperties = {};

    // Load Tilesets
    var tilesets = data.getElementsByTagName("tileset");
    var gidMap = Q._tmxLoadTilesets(tilesets,tileProperties);

    // Go through each of the layers
    Q._each(data.documentElement.childNodes,function(layer) {
      var layerType = layer.tagName;
      if(Q._tmxProcessors[layerType]) {
        Q._tmxProcessors[layerType](stage, gidMap, tileProperties, layer);
      }
    });
  };

};

<|endoftext|>


<commit_before>module.exports = function (sails) {

	/**
	 * Module dependencies.
	 */

	var util	= require( '../util' );

	/**
	 * Expose `bind` method.
	 */

	return bind;

	/**
	 * Bind new route(s)
	 *
	 * @param {String|RegExp} path
	 * @param {String|Object|Array|Function} target
	 * @param {String} verb (optional)
	 * @param {Object} options (optional)
	 * @api private
	 */

	function bind ( path, target, verb, options ) {
		options = options || {};

		// If trying to bind '*', that's probably not what was intended, so fix it up
		path = path === '*' ? '/*' : path;

		// If path has an HTTP verb, parse it out
		// and remove the verb (`get`, `post`, or whatever) from the path
		var detectedVerb = util.detectVerb(path);
		options.detectedVerb = detectedVerb;
		path = detectedVerb.original;

		// Preserve the explicit verb argument if it was specified
		if (!verb) {
			verb = detectedVerb.verb;
		}

		// Handle target chain syntax
		if (util.isArray(target)) {
			return bindArray(path, target, verb, options);
		}

		// Inline target function
		if (util.isFunction(target)) {

			// Route to middleware function
			return bindFunction(path, target, verb, options);
		}

		// Handle strings beginning with http: or / as redirects
		if (util.isString(target) && target.match(/^(http:|\/)/)) {
			return bindRedirect(path, target, verb);
		}

		// If we make it here, the specified target property is invalid
		// No reason to crash the app in this case, so emit an event informing any listeners
		// that an unknown route was encountered (then hooks can listen to this event and add functionality to sails)
		sails.emit('route:typeUnknown', {
			path	: path,
			target	: target,
			verb	: verb,
			options	: options
		});

	}

	function bindRedirect ( path, redirectTo, verb, options ) {
		// Requests will be redirected to the specified string
		// (which hopefully is a URL or redirectable path!)
		sails.router.bind (path, function (req, res) {
			sails.log.verbose('Redirecting request (`' + path + '`) to `' + redirectTo + '`...');
			res.redirect(redirectTo);
		}, verb, options);
	}

	function bindArray ( path, target, verb, options ) {
		util.each(target, function (fn) {
			bind(path, fn, verb, options);
		});
	}

	/**
	 * simple middleware function syntax
	 */

	function bindFunction ( path, fn, verb, options ) {
		_bind ( path, fn, verb, options );
	}

	/**
	 * Attach middleware function to route
	 *
	 * @api prvate
	 */

	function _bind ( path, fn, verb, options ) {

		// Make sure (optional) options is a valid {}
		options = util.isPlainObject(options) ? options : {};

		sails.log.verbose('Binding route :: ', verb || '', path);

		// Ensure that fn is a function
		if ( !util.isFunction(fn) ) {
			sails.log.error(
				(verb ? verb + ' ' : ''),
				path,
				':: Ignoring invalid attempt to bind route to a non-function:',
				fn
			);
			return;
		}

		// Middleware event
		// Parameter augmentations must be made here, since they occur
		// on a per-middleware, not per-route, basis
		var enhancedFn = function (req,res,next) {

			// This event can be tapped into to take control of logic
			// that should be run before each middleware
			// (does not block-- so only use synchronous logic!)
			sails.emit('router:route', {
				req: req,
				res: res,
				next: next,
				options: options
			});

			// Trigger original middleware function
			fn(req, res, next);
		};

		// If verb is not specified, the route should be cloned to all verbs
		sails.router._app[verb || 'all'](path, enhancedFn);

		// Emit an event to make hooks aware that a route was bound
		// (this allows hooks to handle routes directly if they want to)
		sails.emit('router:bind', {
			path	: path,
			target	: util.clone(enhancedFn),
			verb	: verb
		});

		// e.g. with Express, the handler for this event looks like:
		// sails.express.app[verb || 'all'](path, target);
	}

	/**
	 * Right now we just return the "middleware" property of the route config, expecting it to be a function.
	 * In the future parseMiddleware will handle string values for "middleware", as well as config options
	 * such as inputs and outputs.
	 *
	 * @api private
	 */

	function parseMiddleware(target) {
		return target.middleware;
	}

	function logInvalidRouteError (path, target, verb) {

		sails.log.error(
			(verb ? verb + ' ' : ''),
			path,
			':: Ignoring invalid attempt to bind route to non-function:',
			util.inspect(target)
		);
	}

};
<commit_msg>Added https support to redirect syntax in router.<commit_after>module.exports = function (sails) {

	/**
	 * Module dependencies.
	 */

	var util	= require( '../util' );

	/**
	 * Expose `bind` method.
	 */

	return bind;

	/**
	 * Bind new route(s)
	 *
	 * @param {String|RegExp} path
	 * @param {String|Object|Array|Function} target
	 * @param {String} verb (optional)
	 * @param {Object} options (optional)
	 * @api private
	 */

	function bind ( path, target, verb, options ) {
		options = options || {};

		// If trying to bind '*', that's probably not what was intended, so fix it up
		path = path === '*' ? '/*' : path;

		// If path has an HTTP verb, parse it out
		// and remove the verb (`get`, `post`, or whatever) from the path
		var detectedVerb = util.detectVerb(path);
		options.detectedVerb = detectedVerb;
		path = detectedVerb.original;

		// Preserve the explicit verb argument if it was specified
		if (!verb) {
			verb = detectedVerb.verb;
		}

		// Handle target chain syntax
		if (util.isArray(target)) {
			return bindArray(path, target, verb, options);
		}

		// Inline target function
		if (util.isFunction(target)) {

			// Route to middleware function
			return bindFunction(path, target, verb, options);
		}

		// Handle strings beginning with http: or / as redirects
		if (util.isString(target) && target.match(/^(https?:|\/)/)) {
			return bindRedirect(path, target, verb);
		}

		// If we make it here, the specified target property is invalid
		// No reason to crash the app in this case, so emit an event informing any listeners
		// that an unknown route was encountered (then hooks can listen to this event and add functionality to sails)
		sails.emit('route:typeUnknown', {
			path	: path,
			target	: target,
			verb	: verb,
			options	: options
		});

	}

	function bindRedirect ( path, redirectTo, verb, options ) {
		// Requests will be redirected to the specified string
		// (which hopefully is a URL or redirectable path!)
		sails.router.bind (path, function (req, res) {
			sails.log.verbose('Redirecting request (`' + path + '`) to `' + redirectTo + '`...');
			res.redirect(redirectTo);
		}, verb, options);
	}

	function bindArray ( path, target, verb, options ) {
		util.each(target, function (fn) {
			bind(path, fn, verb, options);
		});
	}

	/**
	 * simple middleware function syntax
	 */

	function bindFunction ( path, fn, verb, options ) {
		_bind ( path, fn, verb, options );
	}

	/**
	 * Attach middleware function to route
	 *
	 * @api prvate
	 */

	function _bind ( path, fn, verb, options ) {

		// Make sure (optional) options is a valid {}
		options = util.isPlainObject(options) ? options : {};

		sails.log.verbose('Binding route :: ', verb || '', path);

		// Ensure that fn is a function
		if ( !util.isFunction(fn) ) {
			sails.log.error(
				(verb ? verb + ' ' : ''),
				path,
				':: Ignoring invalid attempt to bind route to a non-function:',
				fn
			);
			return;
		}

		// Middleware event
		// Parameter augmentations must be made here, since they occur
		// on a per-middleware, not per-route, basis
		var enhancedFn = function (req,res,next) {

			// This event can be tapped into to take control of logic
			// that should be run before each middleware
			// (does not block-- so only use synchronous logic!)
			sails.emit('router:route', {
				req: req,
				res: res,
				next: next,
				options: options
			});

			// Trigger original middleware function
			fn(req, res, next);
		};

		// If verb is not specified, the route should be cloned to all verbs
		sails.router._app[verb || 'all'](path, enhancedFn);

		// Emit an event to make hooks aware that a route was bound
		// (this allows hooks to handle routes directly if they want to)
		sails.emit('router:bind', {
			path	: path,
			target	: util.clone(enhancedFn),
			verb	: verb
		});

		// e.g. with Express, the handler for this event looks like:
		// sails.express.app[verb || 'all'](path, target);
	}

	/**
	 * Right now we just return the "middleware" property of the route config, expecting it to be a function.
	 * In the future parseMiddleware will handle string values for "middleware", as well as config options
	 * such as inputs and outputs.
	 *
	 * @api private
	 */

	function parseMiddleware(target) {
		return target.middleware;
	}

	function logInvalidRouteError (path, target, verb) {

		sails.log.error(
			(verb ? verb + ' ' : ''),
			path,
			':: Ignoring invalid attempt to bind route to non-function:',
			util.inspect(target)
		);
	}

};
<|endoftext|>


<commit_before>/**
 * Created by ksinger on 04.08.2017.
 */

import { buildCreateMessage } from "../won-message-utils.js";

import { actionCreators, actionTypes } from "./actions.js";

import { ensureLoggedIn } from "./account-actions.js";

import {
  getIn,
  reverseSearchNominatim,
  nominatim2draftLocation,
} from "../utils.js";

export function needCreate(draft, persona, nodeUri) {
  return (dispatch, getState) => {
    const state = getState();

    if (!nodeUri) {
      nodeUri = getIn(state, ["config", "defaultNodeUri"]);
    }

    let prevParams = getIn(state, ["router", "prevParams"]);

    if (!state.getIn(["user", "loggedIn"]) && prevParams.privateId) {
      /*
             * `ensureLoggedIn` will generate a new privateId. should
             * there be a previous privateId, we don't want to change
             * back to that later.
             */
      prevParams = Object.assign({}, prevParams);
      delete prevParams.privateId;
    }

    return ensureLoggedIn(dispatch, getState)
      .then(() => {
        return dispatch(actionCreators.router__stateGoDefault());
      })
      .then(async () => {
        const { message, eventUri, needUri } = await buildCreateMessage(
          draft,
          nodeUri
        );
        if (persona) {
          const response = await fetch("rest/action/connect", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify([
              {
                pending: true,
                facet: `${needUri}#holdableFacet`,
              },
              {
                pending: false,
                facet: `${persona}#holderFacet`,
              },
            ]),
            credentials: "include",
          });
          if (!response.ok) {
            const errorMsg = await response.text();
            throw new Error(`Could not connect identity: ${errorMsg}`);
          }
        }
        dispatch({
          type: actionTypes.needs.create,
          payload: { eventUri, message, needUri, need: draft },
        });

        dispatch(
          actionCreators.router__stateGoAbs("connections", {
            postUri: undefined,
            connectionUri: undefined,
          })
        );
      });
  };
}

export function createWhatsNew() {
  return (dispatch, getState) => {
    console.log("Create Whats New");
    const state = getState();
    const nodeUri = getIn(state, ["config", "defaultNodeUri"]);
    const defaultContext = getIn(state, ["config", "theme", "defaultContext"]);

    dispatch({ type: actionTypes.needs.whatsNew });

    const whatsNew = {
      whatsNew: true,
    };

    //TODO: Point to same DataSet instead of double it
    const whatsNewObject = {
      seeks: whatsNew,
      matchingContext: defaultContext,
    };

    getIn(state, ["needs"])
      .filter(
        need =>
          need.get("state") === "won:Active" &&
          (need.get("isWhatsAround") || need.get("isWhatsNew"))
      )
      .map(need => {
        dispatch(actionCreators.needs__close(need.get("uri")));
      });

    dispatch(actionCreators.needs__create(whatsNewObject, nodeUri));
  };
}

export function createWhatsAround() {
  return (dispatch, getState) => {
    console.log("Create Whats Around");
    const state = getState();
    const nodeUri = getIn(state, ["config", "defaultNodeUri"]);
    const defaultContext = getIn(state, ["config", "theme", "defaultContext"]);

    dispatch({ type: actionTypes.needs.whatsAround });

    if ("geolocation" in navigator) {
      navigator.geolocation.getCurrentPosition(
        currentLocation => {
          const lat = currentLocation.coords.latitude;
          const lng = currentLocation.coords.longitude;
          const zoom = 13; // TODO use `currentLocation.coords.accuracy` to control coarseness of query / zoom-level

          reverseSearchNominatim(lat, lng, zoom).then(searchResult => {
            const location = nominatim2draftLocation(searchResult);
            let whatsAround = {
              location: location,
              whatsAround: true,
            };

            getIn(state, ["needs"])
              .filter(
                need =>
                  need.get("state") === "won:Active" &&
                  (need.get("isWhatsAround") || need.get("isWhatsNew"))
              )
              .map(need => {
                dispatch(actionCreators.needs__close(need.get("uri"))); //TODO action creators should not call other action creators, according to Moru
              });
            const whatsAroundObject = {
              seeks: whatsAround,
              matchingContext: defaultContext,
            };

            dispatch(actionCreators.needs__create(whatsAroundObject, nodeUri));
          });
        },
        error => {
          //error handler
          dispatch({ type: actionTypes.failedToGetLocation });
          console.error(
            "Could not retrieve geolocation due to error: ",
            error.code,
            "fullerror:",
            error
          );
        },
        {
          //options
          enableHighAccuracy: true,
          maximumAge: 0,
        }
      );
    }
  };
}
<commit_msg>Fixed whatsX creation.<commit_after>/**
 * Created by ksinger on 04.08.2017.
 */

import { buildCreateMessage } from "../won-message-utils.js";

import { actionCreators, actionTypes } from "./actions.js";

import { ensureLoggedIn } from "./account-actions.js";

import {
  getIn,
  reverseSearchNominatim,
  nominatim2draftLocation,
} from "../utils.js";

export function needCreate(draft, persona, nodeUri) {
  return (dispatch, getState) => {
    const state = getState();

    if (!nodeUri) {
      nodeUri = getIn(state, ["config", "defaultNodeUri"]);
    }

    let prevParams = getIn(state, ["router", "prevParams"]);

    if (!state.getIn(["user", "loggedIn"]) && prevParams.privateId) {
      /*
             * `ensureLoggedIn` will generate a new privateId. should
             * there be a previous privateId, we don't want to change
             * back to that later.
             */
      prevParams = Object.assign({}, prevParams);
      delete prevParams.privateId;
    }

    return ensureLoggedIn(dispatch, getState)
      .then(() => {
        return dispatch(actionCreators.router__stateGoDefault());
      })
      .then(async () => {
        const { message, eventUri, needUri } = await buildCreateMessage(
          draft,
          nodeUri
        );
        if (persona) {
          const response = await fetch("rest/action/connect", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify([
              {
                pending: true,
                facet: `${needUri}#holdableFacet`,
              },
              {
                pending: false,
                facet: `${persona}#holderFacet`,
              },
            ]),
            credentials: "include",
          });
          if (!response.ok) {
            const errorMsg = await response.text();
            throw new Error(`Could not connect identity: ${errorMsg}`);
          }
        }
        dispatch({
          type: actionTypes.needs.create,
          payload: { eventUri, message, needUri, need: draft },
        });

        dispatch(
          actionCreators.router__stateGoAbs("connections", {
            postUri: undefined,
            connectionUri: undefined,
          })
        );
      });
  };
}

export function createWhatsNew() {
  return (dispatch, getState) => {
    console.log("Create Whats New");
    const state = getState();
    const nodeUri = getIn(state, ["config", "defaultNodeUri"]);
    const defaultContext = getIn(state, ["config", "theme", "defaultContext"]);

    dispatch({ type: actionTypes.needs.whatsNew });

    const whatsNew = {
      whatsNew: true,
    };

    //TODO: Point to same DataSet instead of double it
    const whatsNewObject = {
      seeks: whatsNew,
      matchingContext: defaultContext,
    };

    getIn(state, ["needs"])
      .filter(
        need =>
          need.get("state") === "won:Active" &&
          (need.get("isWhatsAround") || need.get("isWhatsNew"))
      )
      .map(need => {
        dispatch(actionCreators.needs__close(need.get("uri")));
      });

    dispatch(actionCreators.needs__create(whatsNewObject, null, nodeUri));
  };
}

export function createWhatsAround() {
  return (dispatch, getState) => {
    console.log("Create Whats Around");
    const state = getState();
    const nodeUri = getIn(state, ["config", "defaultNodeUri"]);
    const defaultContext = getIn(state, ["config", "theme", "defaultContext"]);

    dispatch({ type: actionTypes.needs.whatsAround });

    if ("geolocation" in navigator) {
      navigator.geolocation.getCurrentPosition(
        currentLocation => {
          const lat = currentLocation.coords.latitude;
          const lng = currentLocation.coords.longitude;
          const zoom = 13; // TODO use `currentLocation.coords.accuracy` to control coarseness of query / zoom-level

          reverseSearchNominatim(lat, lng, zoom).then(searchResult => {
            const location = nominatim2draftLocation(searchResult);
            let whatsAround = {
              location: location,
              whatsAround: true,
            };

            getIn(state, ["needs"])
              .filter(
                need =>
                  need.get("state") === "won:Active" &&
                  (need.get("isWhatsAround") || need.get("isWhatsNew"))
              )
              .map(need => {
                dispatch(actionCreators.needs__close(need.get("uri"))); //TODO action creators should not call other action creators, according to Moru
              });
            const whatsAroundObject = {
              seeks: whatsAround,
              matchingContext: defaultContext,
            };

            dispatch(
              actionCreators.needs__create(whatsAroundObject, null, nodeUri)
            );
          });
        },
        error => {
          //error handler
          dispatch({ type: actionTypes.failedToGetLocation });
          console.error(
            "Could not retrieve geolocation due to error: ",
            error.code,
            "fullerror:",
            error
          );
        },
        {
          //options
          enableHighAccuracy: true,
          maximumAge: 0,
        }
      );
    }
  };
}
<|endoftext|>


<commit_before>odoo.define('website_forum.website_forum', function (require) {
'use strict';

var ajax = require('web.ajax');
var core = require('web.core');
var website = require('website.website');

var _t = core._t;

var lastsearch;

if(!$('.website_forum').length) {
    return $.Deferred().reject("DOM doesn't contain '.website_forum'");
}

    $("[data-toggle='popover']").popover();
    $('.karma_required').on('click', function (ev) {
        var karma = $(ev.currentTarget).data('karma');
        if (karma) {
            ev.preventDefault();
            var msg = karma + ' ' + _t(' karma is required to perform this action. You can earn karma by having your answers upvoted by the community.')
            if ($('a[href*="/login"]').length) {
                msg = _t('Sorry you must be logged in to perform this action');
            };
            var $warning = $('
'+
                '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                msg + '
');
            var vote_alert = $(ev.currentTarget).parent().find("#vote_alert");
            if (vote_alert.length == 0) {
                $(ev.currentTarget).parent().append($warning);
            }
        }
    });

    $('.o_js_forum_tag_follow').hover(
        function(event) {
            $(this).find('.o_forum_tag_follow_box').stop().fadeIn().css('display','block');
        },
        function(event) {
            $(this).find('.o_forum_tag_follow_box').stop().fadeOut().css('display','none');
    });

    // Extended user biography toogle
    $('.o_forum_user_info').hover(
        function () {
           $(this).parent().find('.o_forum_user_bio_expand').delay(500).toggle('fast');
        },
        function () {
            $(this).parent().find('.o_forum_user_bio_expand').clearQueue();
        }
    );

    $('.o_forum_user_bio_expand').hover(
        function () {},
        function () {
            $(this).fadeOut('fast');
        }
    );

    $('.flag').not('.karma_required').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {})
            .then(function (data) {
                if(data.error) {
                    var $warning;
                    if(data.error == 'anonymous_user') {
                        $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('Sorry you must be logged to flag a post') +
                            '
');
                    } else if(data.error == 'post_already_flagged') {
                        $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('This post is already flagged') +
                            '
');
                    } else if(data.error == 'post_non_flaggable') {
                        $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('This post can not be flagged') +
                            '
');
                    }
                    var flag_alert = $link.parent().find("#flag_alert");
                    if (flag_alert.length === 0) {
                        $link.parent().append($warning);
                    }
                } else if(data.success) {
                    var elem = $link;
                    if(data.success == 'post_flagged_moderator') {
                        elem.html(' Flagged');
                        var c = parseInt($('#count_flagged_posts').html(), 10);
                        c++;
                        $('#count_flagged_posts').html(c);
                    } else if(data.success == 'post_flagged_non_moderator') {
                        elem.html(' Flagged');
                        var forum_answer = elem.closest('.forum_answer');
                        forum_answer.fadeIn(1000);
                        forum_answer.slideUp(1000);
                    }
                }
            });
    });

    $('.vote_up,.vote_down').not('.karma_required').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {})
            .then(function (data) {
                if (data.error){
                    if (data.error == 'own_post'){
                        var $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('Sorry, you cannot vote for your own posts') +
                            '
');
                    } else if (data.error == 'anonymous_user'){
                        var $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('Sorry you must be logged to vote') +
                            '
');
                    }
                    var vote_alert = $link.parent().find("#vote_alert");
                    if (vote_alert.length === 0) {
                        $link.parent().append($warning);
                    }
                } else {
                    $link.parent().find(".vote_count").html(data['vote_count']);
                    if (data.user_vote === 0) {
                        $link.parent().find(".text-success").removeClass("text-success");
                        $link.parent().find(".text-warning").removeClass("text-warning");
                    } else {
                        if (data.user_vote === 1) {
                            $link.addClass("text-success");
                        } else {
                            $link.addClass("text-warning");
                        }
                    }
                }
            });
    });

    $('.o_js_validation_queue a').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        var self = $(this);
        $(this).parents('.post_to_validate').hide();
        $.get($link.attr('href'))
            .fail(function() {
                self.parents('.o_js_validation_queue > div').addClass('panel-danger').css('background-color', '#FAA');
                self.parents('.post_to_validate').show();
            })
            .done(function() {
                var left = $('.o_js_validation_queue:visible').length;
                var type = $('h2.page-header li.active a').data('type');
                $('#count_post').text(left);
                $('#moderation_tools a[href*="/'+type+'_"]').find('strong').text(left);
            });

    });

    $('.accept_answer').not('.karma_required').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {}).then(function (data) {
            if (data.error) {
                if (data.error == 'anonymous_user') {
                    var $warning = $('
'+
                        '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                        _t('Sorry, anonymous users cannot choose correct answer.') +
                        '
');
                }
                var correct_answer_alert = $link.parent().find("#correct_answer_alert");
                if (correct_answer_alert.length == 0) {
                    $link.parent().append($warning);
                }
            } else {
                if (data) {
                    $(".oe_answer_true").addClass('oe_answer_false').removeClass("oe_answer_true");
                    $link.addClass("oe_answer_true").removeClass('oe_answer_false');
                } else {
                    $link.removeClass("oe_answer_true").addClass('oe_answer_false');
                }
            }
        });
    });

    $('.favourite_question').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {}).then(function (data) {
            if (data) {
                $link.addClass("forum_favourite_question")
            } else {
                $link.removeClass("forum_favourite_question")
            }
        });
    });

    $('.comment_delete').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.closest('form').attr('action'), 'call', {}).then(function () {
            $link.parents('.comment').first().remove();
        });
    });

    $('.notification_close').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc("/forum/notification_read", 'call', {
            'notification_id': $link.attr("id")});
    });

    $('.send_validation_email').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc("/forum/send_validation_email", 'call', {
            'forum_id': $link.attr('forum-id'),
        }).then(function (data) {
            if (data) {
                $('button.validation_email_close').click();
            }
        });
    });

    $('.validated_email_close').on('click', function (ev) {
        ajax.jsonRpc("/forum/validate_email/close", 'call', {});
    });

    // welcome message action button
    var forum_login = _.string.sprintf('%s/web?redirect=%s',
        window.location.origin, escape(window.location.href));
    $('.forum_register_url').attr('href',forum_login);

    $('.js_close_intro').on('click', function (ev) {
        ev.preventDefault();
        document.cookie = "forum_welcome_message = false";
        $('.forum_intro').slideUp();
        return true;
    });

    $('.link_url, .o_forum_post_link').on('change', function (ev) {  // keep .link_url for compat
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        var display_error = function(){
            var $warning = $('
'+
                '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                'Please enter valid URL. Example: http://www.odoo.com'+
                '
');
            $link.parent().append($warning);
            $link.parents('form').find('button')[0].disabled = true;

        };
        var url = $link.val();
        if (url.search("^http(s?)://.*")) {
            url = 'http://'+url;
        }

        // https://gist.github.com/dperini/729294
        var regex = new RegExp(
          "^" +
            // protocol identifier
            "(?:(?:https?|ftp)://)" +
            // user:pass authentication
            "(?:\\S+(?::\\S*)?@)?" +
            "(?:" +
              // IP address exclusion
              // private & local networks
              "(?!(?:10|127)(?:\\.\\d{1,3}){3})" +
              "(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" +
              "(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})" +
              // IP address dotted notation octets
              // excludes loopback network 0.0.0.0
              // excludes reserved space >= 224.0.0.0
              // excludes network & broacast addresses
              // (first & last IP address of each class)
              "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" +
              "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" +
              "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" +
            "|" +
              // host name
              "(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)" +
              // domain name
              "(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*" +
              // TLD identifier
              "(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))" +
              // TLD may end with dot
              "\\.?" +
            ")" +
            // port number
            "(?::\\d{2,5})?" +
            // resource path
            "(?:[/?#]\\S*)?" +
          "$", "i"
        );

        if(regex.test(url)){
            ajax.jsonRpc("/forum/get_url_title", 'call', {'url': url}).then(function (data) {
                if (data) {
                    $("input[name='post_name']")[0].value = data;
                    $link.parents('form').find('button')[0].disabled = false;
                } else {
                    display_error();
                }

            });
        } else {
            display_error();
        }
    });

    $('input.js_select2').select2({
        tags: true,
        tokenSeparators: [",", " ", "_"],
        maximumInputLength: 35,
        minimumInputLength: 2,
        maximumSelectionSize: 5,
        lastsearch: [],
        createSearchChoice: function (term) {
            if ($(lastsearch).filter(function () { return this.text.localeCompare(term) === 0;}).length === 0) {
                //check Karma
                if (parseInt($("#karma").val()) >= parseInt($("#karma_edit_retag").val())) {
                    return {
                        id: "_" + $.trim(term),
                        text: $.trim(term) + ' *',
                        isNew: true,
                    };
                }
            }
        },
        formatResult: function(term) {
            if (term.isNew) {
                return 'New ' + _.escape(term.text);
            }
            else {
                return _.escape(term.text);
            }
        },
        ajax: {
            url: '/forum/get_tags',
            dataType: 'json',
            data: function(term) {
                return {
                    q: term,
                    l: 50
                };
            },
            results: function(data) {
                var ret = [];
                _.each(data, function(x) {
                    ret.push({ id: x.id, text: x.name, isNew: false });
                });
                lastsearch = ret;
                return { results: ret };
            }
        },
        // Take default tags from the input value
        initSelection: function (element, callback) {
            var data = [];
            _.each(element.data('init-value'), function(x) {
                data.push({ id: x.id, text: x.name, isNew: false });
            });
            element.val('');
            callback(data);
        },
    });

    $('textarea.load_editor').each(function () {
        var $textarea = $(this);
        var editor_karma = $textarea.data('karma') || 30;  // default value for backward compatibility
        if (!$textarea.val().match(/\S/)) {
            $textarea.val("

");
        }
        var $form = $textarea.closest('form');
        var toolbar = [
                ['style', ['style']],
                ['font', ['bold', 'italic', 'underline', 'clear']],
                ['para', ['ul', 'ol', 'paragraph']],
                ['table', ['table']],
                ['history', ['undo', 'redo']],
            ];
        if (parseInt($("#karma").val()) >= editor_karma) {
            toolbar.push(['insert', ['link', 'picture']]);
        }
        $textarea.summernote({
                height: 150,
                toolbar: toolbar,
                styleWithSpan: false
            });
        $form.on('click', 'button, .a-submit', function () {
            $textarea.html($form.find('.note-editable').code());
        });
    });

});<commit_msg>[FIX] website_forum: ask reason when refuse a post.<commit_after>odoo.define('website_forum.website_forum', function (require) {
'use strict';

var ajax = require('web.ajax');
var core = require('web.core');
var website = require('website.website');

var _t = core._t;

var lastsearch;

if(!$('.website_forum').length) {
    return $.Deferred().reject("DOM doesn't contain '.website_forum'");
}

    $("[data-toggle='popover']").popover();
    $('.karma_required').on('click', function (ev) {
        var karma = $(ev.currentTarget).data('karma');
        if (karma) {
            ev.preventDefault();
            var msg = karma + ' ' + _t(' karma is required to perform this action. You can earn karma by having your answers upvoted by the community.')
            if ($('a[href*="/login"]').length) {
                msg = _t('Sorry you must be logged in to perform this action');
            };
            var $warning = $('
'+
                '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                msg + '
');
            var vote_alert = $(ev.currentTarget).parent().find("#vote_alert");
            if (vote_alert.length == 0) {
                $(ev.currentTarget).parent().append($warning);
            }
        }
    });

    $('.o_js_forum_tag_follow').hover(
        function(event) {
            $(this).find('.o_forum_tag_follow_box').stop().fadeIn().css('display','block');
        },
        function(event) {
            $(this).find('.o_forum_tag_follow_box').stop().fadeOut().css('display','none');
    });

    // Extended user biography toogle
    $('.o_forum_user_info').hover(
        function () {
           $(this).parent().find('.o_forum_user_bio_expand').delay(500).toggle('fast');
        },
        function () {
            $(this).parent().find('.o_forum_user_bio_expand').clearQueue();
        }
    );

    $('.o_forum_user_bio_expand').hover(
        function () {},
        function () {
            $(this).fadeOut('fast');
        }
    );

    $('.flag').not('.karma_required').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {})
            .then(function (data) {
                if(data.error) {
                    var $warning;
                    if(data.error == 'anonymous_user') {
                        $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('Sorry you must be logged to flag a post') +
                            '
');
                    } else if(data.error == 'post_already_flagged') {
                        $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('This post is already flagged') +
                            '
');
                    } else if(data.error == 'post_non_flaggable') {
                        $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('This post can not be flagged') +
                            '
');
                    }
                    var flag_alert = $link.parent().find("#flag_alert");
                    if (flag_alert.length === 0) {
                        $link.parent().append($warning);
                    }
                } else if(data.success) {
                    var elem = $link;
                    if(data.success == 'post_flagged_moderator') {
                        elem.html(' Flagged');
                        var c = parseInt($('#count_flagged_posts').html(), 10);
                        c++;
                        $('#count_flagged_posts').html(c);
                    } else if(data.success == 'post_flagged_non_moderator') {
                        elem.html(' Flagged');
                        var forum_answer = elem.closest('.forum_answer');
                        forum_answer.fadeIn(1000);
                        forum_answer.slideUp(1000);
                    }
                }
            });
    });

    $('.vote_up,.vote_down').not('.karma_required').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {})
            .then(function (data) {
                if (data.error){
                    if (data.error == 'own_post'){
                        var $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('Sorry, you cannot vote for your own posts') +
                            '
');
                    } else if (data.error == 'anonymous_user'){
                        var $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('Sorry you must be logged to vote') +
                            '
');
                    }
                    var vote_alert = $link.parent().find("#vote_alert");
                    if (vote_alert.length === 0) {
                        $link.parent().append($warning);
                    }
                } else {
                    $link.parent().find(".vote_count").html(data['vote_count']);
                    if (data.user_vote === 0) {
                        $link.parent().find(".text-success").removeClass("text-success");
                        $link.parent().find(".text-warning").removeClass("text-warning");
                    } else {
                        if (data.user_vote === 1) {
                            $link.addClass("text-success");
                        } else {
                            $link.addClass("text-warning");
                        }
                    }
                }
            });
    });

    $(".o_js_validation_queue a[href*='/validate']").on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        var self = $(this);
        $(this).parents('.post_to_validate').hide();
        $.get($link.attr('href'))
            .fail(function() {
                self.parents('.o_js_validation_queue > div').addClass('panel-danger').css('background-color', '#FAA');
                self.parents('.post_to_validate').show();
            })
            .done(function() {
                var left = $('.o_js_validation_queue:visible').length;
                var type = $('h2.page-header li.active a').data('type');
                $('#count_post').text(left);
                $('#moderation_tools a[href*="/'+type+'_"]').find('strong').text(left);
            });

    });

    $('.accept_answer').not('.karma_required').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {}).then(function (data) {
            if (data.error) {
                if (data.error == 'anonymous_user') {
                    var $warning = $('
'+
                        '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                        _t('Sorry, anonymous users cannot choose correct answer.') +
                        '
');
                }
                var correct_answer_alert = $link.parent().find("#correct_answer_alert");
                if (correct_answer_alert.length == 0) {
                    $link.parent().append($warning);
                }
            } else {
                if (data) {
                    $(".oe_answer_true").addClass('oe_answer_false').removeClass("oe_answer_true");
                    $link.addClass("oe_answer_true").removeClass('oe_answer_false');
                } else {
                    $link.removeClass("oe_answer_true").addClass('oe_answer_false');
                }
            }
        });
    });

    $('.favourite_question').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {}).then(function (data) {
            if (data) {
                $link.addClass("forum_favourite_question")
            } else {
                $link.removeClass("forum_favourite_question")
            }
        });
    });

    $('.comment_delete').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.closest('form').attr('action'), 'call', {}).then(function () {
            $link.parents('.comment').first().remove();
        });
    });

    $('.notification_close').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc("/forum/notification_read", 'call', {
            'notification_id': $link.attr("id")});
    });

    $('.send_validation_email').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc("/forum/send_validation_email", 'call', {
            'forum_id': $link.attr('forum-id'),
        }).then(function (data) {
            if (data) {
                $('button.validation_email_close').click();
            }
        });
    });

    $('.validated_email_close').on('click', function (ev) {
        ajax.jsonRpc("/forum/validate_email/close", 'call', {});
    });

    // welcome message action button
    var forum_login = _.string.sprintf('%s/web?redirect=%s',
        window.location.origin, escape(window.location.href));
    $('.forum_register_url').attr('href',forum_login);

    $('.js_close_intro').on('click', function (ev) {
        ev.preventDefault();
        document.cookie = "forum_welcome_message = false";
        $('.forum_intro').slideUp();
        return true;
    });

    $('.link_url, .o_forum_post_link').on('change', function (ev) {  // keep .link_url for compat
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        var display_error = function(){
            var $warning = $('
'+
                '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                'Please enter valid URL. Example: http://www.odoo.com'+
                '
');
            $link.parent().append($warning);
            $link.parents('form').find('button')[0].disabled = true;

        };
        var url = $link.val();
        if (url.search("^http(s?)://.*")) {
            url = 'http://'+url;
        }

        // https://gist.github.com/dperini/729294
        var regex = new RegExp(
          "^" +
            // protocol identifier
            "(?:(?:https?|ftp)://)" +
            // user:pass authentication
            "(?:\\S+(?::\\S*)?@)?" +
            "(?:" +
              // IP address exclusion
              // private & local networks
              "(?!(?:10|127)(?:\\.\\d{1,3}){3})" +
              "(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" +
              "(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})" +
              // IP address dotted notation octets
              // excludes loopback network 0.0.0.0
              // excludes reserved space >= 224.0.0.0
              // excludes network & broacast addresses
              // (first & last IP address of each class)
              "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" +
              "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" +
              "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" +
            "|" +
              // host name
              "(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)" +
              // domain name
              "(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*" +
              // TLD identifier
              "(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))" +
              // TLD may end with dot
              "\\.?" +
            ")" +
            // port number
            "(?::\\d{2,5})?" +
            // resource path
            "(?:[/?#]\\S*)?" +
          "$", "i"
        );

        if(regex.test(url)){
            ajax.jsonRpc("/forum/get_url_title", 'call', {'url': url}).then(function (data) {
                if (data) {
                    $("input[name='post_name']")[0].value = data;
                    $link.parents('form').find('button')[0].disabled = false;
                } else {
                    display_error();
                }

            });
        } else {
            display_error();
        }
    });

    $('input.js_select2').select2({
        tags: true,
        tokenSeparators: [",", " ", "_"],
        maximumInputLength: 35,
        minimumInputLength: 2,
        maximumSelectionSize: 5,
        lastsearch: [],
        createSearchChoice: function (term) {
            if ($(lastsearch).filter(function () { return this.text.localeCompare(term) === 0;}).length === 0) {
                //check Karma
                if (parseInt($("#karma").val()) >= parseInt($("#karma_edit_retag").val())) {
                    return {
                        id: "_" + $.trim(term),
                        text: $.trim(term) + ' *',
                        isNew: true,
                    };
                }
            }
        },
        formatResult: function(term) {
            if (term.isNew) {
                return 'New ' + _.escape(term.text);
            }
            else {
                return _.escape(term.text);
            }
        },
        ajax: {
            url: '/forum/get_tags',
            dataType: 'json',
            data: function(term) {
                return {
                    q: term,
                    l: 50
                };
            },
            results: function(data) {
                var ret = [];
                _.each(data, function(x) {
                    ret.push({ id: x.id, text: x.name, isNew: false });
                });
                lastsearch = ret;
                return { results: ret };
            }
        },
        // Take default tags from the input value
        initSelection: function (element, callback) {
            var data = [];
            _.each(element.data('init-value'), function(x) {
                data.push({ id: x.id, text: x.name, isNew: false });
            });
            element.val('');
            callback(data);
        },
    });

    $('textarea.load_editor').each(function () {
        var $textarea = $(this);
        var editor_karma = $textarea.data('karma') || 30;  // default value for backward compatibility
        if (!$textarea.val().match(/\S/)) {
            $textarea.val("

");
        }
        var $form = $textarea.closest('form');
        var toolbar = [
                ['style', ['style']],
                ['font', ['bold', 'italic', 'underline', 'clear']],
                ['para', ['ul', 'ol', 'paragraph']],
                ['table', ['table']],
                ['history', ['undo', 'redo']],
            ];
        if (parseInt($("#karma").val()) >= editor_karma) {
            toolbar.push(['insert', ['link', 'picture']]);
        }
        $textarea.summernote({
                height: 150,
                toolbar: toolbar,
                styleWithSpan: false
            });
        $form.on('click', 'button, .a-submit', function () {
            $textarea.html($form.find('.note-editable').code());
        });
    });

});<|endoftext|>