From ef63d816fe123ab6d5afce504d06adb9d3bc4316 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Fri, 21 Feb 2014 23:52:23 -0500
Subject: [PATCH 001/193] NodeBB will now listen to SIGINT signal

---
 src/webserver.js | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/src/webserver.js b/src/webserver.js
index 01a779e972..0d65b7c99e 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -44,6 +44,16 @@ if(nconf.get('ssl')) {
 
 module.exports.server = server;
 
+// Signals
+process.on('SIGINT', function() {
+	winston.info('[app] Shutdown Initialised.');
+	db.close();
+	winston.info('[app] Database connection closed.');
+
+	winston.info('[app] Goodbye!');
+	process.exit();
+});
+
 (function (app) {
 	"use strict";
 

From 64c4dd7e630557af2a011491ff1684cc492eb79e Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sat, 22 Feb 2014 02:27:14 -0500
Subject: [PATCH 002/193] communication between loader and child

---
 loader.js        | 19 +++++++++++++++++++
 src/webserver.js | 31 ++++++++++++++++++++++++-------
 2 files changed, 43 insertions(+), 7 deletions(-)
 create mode 100644 loader.js

diff --git a/loader.js b/loader.js
new file mode 100644
index 0000000000..eb46269d5a
--- /dev/null
+++ b/loader.js
@@ -0,0 +1,19 @@
+var	fork = require('child_process').fork,
+	start = function() {
+		var	nbb = fork('./app', [], {
+				env: {
+					'NODE_ENV': 'development'
+				}
+			});
+
+		nbb.on('message', function(cmd) {
+			if (cmd === 'nodebb:restart') {
+				nbb.kill();
+				setTimeout(function() {
+					start();
+				}, 1000);
+			}
+		});
+	};
+
+start();
\ No newline at end of file
diff --git a/src/webserver.js b/src/webserver.js
index 0d65b7c99e..ff0f7bd67e 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -45,13 +45,30 @@ if(nconf.get('ssl')) {
 module.exports.server = server;
 
 // Signals
-process.on('SIGINT', function() {
-	winston.info('[app] Shutdown Initialised.');
-	db.close();
-	winston.info('[app] Database connection closed.');
-
-	winston.info('[app] Goodbye!');
-	process.exit();
+var	shutdown = function(code) {
+		winston.info('[app] Shutdown (SIGTERM/SIGINT) Initialised.');
+		db.close();
+		winston.info('[app] Database connection closed.');
+
+		winston.info('[app] Goodbye!');
+		process.exit();
+	},
+	restart = function() {
+		if (process.send) {
+			winston.info('[app] Restarting...');
+			process.send('nodebb:restart');
+		} else {
+			winston.error('[app] Could not restart server. Shutting down.');
+			shutdown();
+		}
+	};
+process.on('SIGTERM', shutdown);
+process.on('SIGINT', shutdown);
+process.on('SIGHUP', restart);
+process.on('uncaughtException', function(err) {
+	winston.error('[app] Encountered Uncaught Exception: ' + err.message);
+	console.log(err.stack);
+	restart();
 });
 
 (function (app) {

From d6a1fad5274fff768e87cd3be105549694a479ce Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sat, 22 Feb 2014 02:36:56 -0500
Subject: [PATCH 003/193] removing timeout before restarting nodebb, fixing
 nodebb executable

---
 loader.js |  8 ++++----
 nodebb    | 33 ++++++++-------------------------
 2 files changed, 12 insertions(+), 29 deletions(-)

diff --git a/loader.js b/loader.js
index eb46269d5a..21fb5d0c8c 100644
--- a/loader.js
+++ b/loader.js
@@ -2,16 +2,16 @@ var	fork = require('child_process').fork,
 	start = function() {
 		var	nbb = fork('./app', [], {
 				env: {
-					'NODE_ENV': 'development'
+					'NODE_ENV': process.env.NODE_ENV
 				}
 			});
 
 		nbb.on('message', function(cmd) {
 			if (cmd === 'nodebb:restart') {
-				nbb.kill();
-				setTimeout(function() {
+				nbb.on('exit', function() {
 					start();
-				}, 1000);
+				});
+				nbb.kill();
 			}
 		});
 	};
diff --git a/nodebb b/nodebb
index e514db0051..0ea67b0a69 100755
--- a/nodebb
+++ b/nodebb
@@ -6,57 +6,40 @@
 
 case "$1" in
 	start)
-		node app "$@"
+		node loader "$@"
 		;;
 
 	upgrade)
 		npm install
 		ls -d node_modules/nodebb* | xargs -n1 basename | xargs npm install
 		ls -d node_modules/nodebb* | xargs -n1 basename | xargs npm update
-		node app --upgrade
+		node loader --upgrade
 		touch package.json
 		echo -e "\n\e[00;32mNodeBB Dependencies up-to-date!\e[00;00m";
 		;;
 
 	setup)
-		node app --setup
+		node loader --setup
 		;;
 
-        reset)
-                node app --reset
-                ;;
+	reset)
+		node loader --reset
+		;;
 
 	dev)
 		echo "Launching NodeBB in \"development\" mode."
 		echo "To run the production build of NodeBB, please use \"forever\"."
 		echo "More Information: https://github.com/designcreateplay/NodeBB/wiki/How-to-run-NodeBB"
-		NODE_ENV=development node app "$@"
+		NODE_ENV=development node loader "$@"
 		;;
 
 	watch)
 		echo "Launching NodeBB in \"development\" mode."
 		echo "To run the production build of NodeBB, please use \"forever\"."
 		echo "More Information: https://github.com/designcreateplay/NodeBB/wiki/How-to-run-NodeBB"
-		NODE_ENV=development supervisor -q --extensions 'node|js|tpl' -- app "$@"
+		NODE_ENV=development supervisor -q --extensions 'node|js|tpl' -- loader "$@"
 		;;
 
-	# language)
-	# 	case "$2" in
-	# 		check)
-	# 			node app --language="check"
-	# 			;;
-
-	# 		*)
-	# 			echo "Language Settings"
-	# 			echo $"Usage: $0 language {check}"
-	# 			echo ''
-	# 			column -s '	' -t <<< '
-	# 			check	Compare language files against the /en directory
-	# 			'
-	# 			;;
-	# 	esac
-	# 	;;
-
 	*)
 		echo "Welcome to NodeBB"
 		echo $"Usage: $0 {start|dev|watch|upgrade}"

From b64e5870b701c467cd2971d29663a14abb3ba4f8 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sat, 22 Feb 2014 03:01:54 -0500
Subject: [PATCH 004/193] loader now handles arguments and ./nodebb watch
 command updated to not use loader.

---
 loader.js           | 16 ++++++++++------
 nodebb              |  2 +-
 src/routes/debug.js |  4 +---
 3 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/loader.js b/loader.js
index 21fb5d0c8c..d8393c55f7 100644
--- a/loader.js
+++ b/loader.js
@@ -1,6 +1,6 @@
 var	fork = require('child_process').fork,
 	start = function() {
-		var	nbb = fork('./app', [], {
+		var	nbb = fork('./app', process.argv.slice(2), {
 				env: {
 					'NODE_ENV': process.env.NODE_ENV
 				}
@@ -8,12 +8,16 @@ var	fork = require('child_process').fork,
 
 		nbb.on('message', function(cmd) {
 			if (cmd === 'nodebb:restart') {
-				nbb.on('exit', function() {
-					start();
-				});
-				nbb.kill();
+				if (process.env.NODE_ENV !== 'development') {
+					nbb.on('exit', function() {
+						start();
+					});
+					nbb.kill();
+				} else {
+					console.log('[app] Development Mode is on, restart aborted.');
+				}
 			}
 		});
-	};
+	}
 
 start();
\ No newline at end of file
diff --git a/nodebb b/nodebb
index 0ea67b0a69..cdefacd524 100755
--- a/nodebb
+++ b/nodebb
@@ -37,7 +37,7 @@ case "$1" in
 		echo "Launching NodeBB in \"development\" mode."
 		echo "To run the production build of NodeBB, please use \"forever\"."
 		echo "More Information: https://github.com/designcreateplay/NodeBB/wiki/How-to-run-NodeBB"
-		NODE_ENV=development supervisor -q --extensions 'node|js|tpl' -- loader "$@"
+		NODE_ENV=development supervisor -q --extensions 'node|js|tpl' -- app "$@"
 		;;
 
 	*)
diff --git a/src/routes/debug.js b/src/routes/debug.js
index 7d877c8724..cca3f359de 100644
--- a/src/routes/debug.js
+++ b/src/routes/debug.js
@@ -55,9 +55,7 @@ var	DebugRoute = function(app) {
 		});
 
 		app.get('/test', function(req, res) {
-			// categories.getModerators(1, function(err, mods) {
-			// 	res.json(mods);
-			// })
+			res.send(200);
 		});
 
 	});

From 7760a6b2074babb262dddb5dbd074b5042e85b22 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sat, 22 Feb 2014 03:11:13 -0500
Subject: [PATCH 005/193] added restart button to ACP (!!)

---
 public/src/forum/admin/index.js  | 4 ++++
 public/templates/admin/index.tpl | 3 +++
 src/meta.js                      | 8 ++++++++
 src/socket.io/admin.js           | 4 ++++
 4 files changed, 19 insertions(+)

diff --git a/public/src/forum/admin/index.js b/public/src/forum/admin/index.js
index 914676115d..1870884328 100644
--- a/public/src/forum/admin/index.js
+++ b/public/src/forum/admin/index.js
@@ -31,6 +31,10 @@ define(function() {
 				checkEl.append('<p>A new version (v' + latestVersion + ') has been released. Consider upgrading your NodeBB.</p>');
 			}
 		});
+
+		$('.restart').on('click', function() {
+			socket.emit('admin.restart');
+		});
 	};
 
 	Admin.updateRoomUsage = function(err, data) {
diff --git a/public/templates/admin/index.tpl b/public/templates/admin/index.tpl
index a6b8db55be..2d548c459c 100644
--- a/public/templates/admin/index.tpl
+++ b/public/templates/admin/index.tpl
@@ -22,6 +22,9 @@
 				<p>
 					Always make sure that your <strong>NodeBB</strong> is up to date for the latest security patches and bug fixes.
 				</p>
+				<p class="pull-right">
+					<button class="btn btn-warning restart">Restart NodeBB</button>
+				</p>
 			</div>
 		</div>
 	</div>
diff --git a/src/meta.js b/src/meta.js
index c1eb612744..419f293648 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -336,4 +336,12 @@ var fs = require('fs'),
 			db.getFileName(callback);
 		}
 	};
+
+	Meta.restart = function() {
+		if (process.send) {
+			process.send('nodebb:restart');
+		} else {
+			winston.error('[meta.restart] Could not restart, are you sure NodeBB was started with `./nodebb start`?');
+		}
+	};
 }(exports));
diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index 21f3d8bf11..3f674f2d65 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -31,6 +31,10 @@ SocketAdmin.before = function(socket, next) {
 	});
 };
 
+SocketAdmin.restart = function(socket, data, callback) {
+	meta.restart();
+};
+
 /* Topics */
 
 SocketAdmin.topics = {};

From d6d9776cde44cd9851f2c2973ea800cf23764b94 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 22 Feb 2014 17:56:13 -0500
Subject: [PATCH 006/193] added toPid to posts

---
 public/src/forum/topic.js      |  7 ++++---
 public/src/modules/composer.js | 13 +++++++------
 src/posts.js                   | 11 ++++++++++-
 src/socket.io/posts.js         |  5 ++++-
 src/topics.js                  | 14 +++++++++-----
 5 files changed, 34 insertions(+), 16 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index dbb1aa3431..e9fa69e934 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -405,13 +405,14 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			}
 
 			var username = '',
-				post = $(this).parents('li[data-pid]');
+				post = $(this).parents('li[data-pid]'),
+				pid = $(this).parents('.post-row').attr('data-pid');
 			if (post.length) {
 				username = '@' + post.attr('data-username').replace(/\s/g, '-') + ' ';
 			}
 
 			if (thread_state.locked !== '1') {
-				composer.newReply(tid, topic_name, selectionText.length > 0 ? selectionText + '\n\n' + username : '' + username);
+				composer.newReply(tid, pid, topic_name, selectionText.length > 0 ? selectionText + '\n\n' + username : '' + username);
 			}
 		});
 
@@ -436,7 +437,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					if($('.composer').length) {
 						composer.addQuote(tid, pid, topic_name, username, quoted);
 					}else {
-						composer.newReply(tid, topic_name, username + ' said:\n' + quoted);
+						composer.newReply(tid, pid, topic_name, username + ' said:\n' + quoted);
 					}
 				});
 			}
diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js
index 3e352401a4..9c1bbf351c 100644
--- a/public/src/modules/composer.js
+++ b/public/src/modules/composer.js
@@ -294,22 +294,22 @@ define(['taskbar'], function(taskbar) {
 				var prevText = bodyEl.val();
 				if(tid !== composer.posts[uuid].tid) {
 					text = username + ' said in ['+title+'](/topic/'+tid+'#'+pid+'):\n'+text;
-				}else {
+				} else {
 					text = username + ' said:\n' + text;
 				}
 				composer.posts[uuid].body = (prevText.length ? prevText + '\n\n' : '') + text;
 				bodyEl.val(composer.posts[uuid].body);
-			}else{
-				composer.newReply(tid,title,username + ' said:\n' + text);
+			} else {
+				composer.newReply(tid, pid, title, username + ' said:\n' + text);
 			}
-
 		}
 	};
 
-	composer.newReply = function(tid, title, text) {
+	composer.newReply = function(tid, pid, title, text) {
 		if(allowed()) {
 			push({
 				tid: tid,
+				toPid: pid,
 				title: title,
 				body: text,
 				modified: false,
@@ -737,7 +737,8 @@ define(['taskbar'], function(taskbar) {
 		} else if (parseInt(postData.tid, 10) > 0) {
 			socket.emit('posts.reply', {
 				topic_id: postData.tid,
-				content: bodyEl.val()
+				content: bodyEl.val(),
+				toPid: postData.toPid
 			}, done);
 		} else if (parseInt(postData.pid, 10) > 0) {
 			socket.emit('posts.edit', {
diff --git a/src/posts.js b/src/posts.js
index b010ef19b5..fa3703a585 100644
--- a/src/posts.js
+++ b/src/posts.js
@@ -20,7 +20,12 @@ var db = require('./database'),
 (function(Posts) {
 	var customUserInfo = {};
 
-	Posts.create = function(uid, tid, content, callback) {
+	Posts.create = function(data, callback) {
+		var uid = data.uid,
+			tid = data.tid,
+			content = data.content,
+			toPid = data.toPid;
+
 		if (uid === null) {
 			return callback(new Error('invalid-user'), null);
 		}
@@ -56,6 +61,10 @@ var db = require('./database'),
 						'deleted': 0
 					};
 
+				if (toPid) {
+					postData['toPid'] = toPid;
+				}
+
 				db.setObject('post:' + pid, postData, function(err) {
 					if(err) {
 						return next(err);
diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js
index 2d10ec91b1..2b54551554 100644
--- a/src/socket.io/posts.js
+++ b/src/socket.io/posts.js
@@ -14,6 +14,7 @@ var	async = require('async'),
 	SocketPosts = {};
 
 SocketPosts.reply = function(socket, data, callback) {
+
 	if (!socket.uid && !parseInt(meta.config.allowGuestPosting, 10)) {
 		socket.emit('event:alert', {
 			title: 'Reply Unsuccessful',
@@ -28,7 +29,9 @@ SocketPosts.reply = function(socket, data, callback) {
 		return callback(new Error('invalid data'));
 	}
 
-	topics.reply(data.topic_id, socket.uid, data.content, function(err, postData) {
+	data.uid = socket.uid;
+
+	topics.reply(data, function(err, postData) {
 		if(err) {
 			if (err.message === 'content-too-short') {
 				module.parent.exports.emitContentTooShortAlert(socket);
diff --git a/src/topics.js b/src/topics.js
index 7e0362e2cd..b7e22871a5 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -121,7 +121,7 @@ var async = require('async'),
 				Topics.create({uid: uid, title: title, cid: cid, thumb: thumb}, next);
 			},
 			function(tid, next) {
-				Topics.reply(tid, uid, content, next);
+				Topics.reply({uid:uid, tid:tid, content:content}, next);
 			},
 			function(postData, next) {
 				threadTools.toggleFollow(postData.tid, uid);
@@ -143,9 +143,13 @@ var async = require('async'),
 		], callback);
 	};
 
-	Topics.reply = function(tid, uid, content, callback) {
-		var privileges;
-		var postData;
+	Topics.reply = function(data, callback) {
+		var tid = data.topic_id,
+			uid = data.uid,
+			toPid = data.toPid,
+			content = data.content,
+			privileges,
+			postData;
 
 		async.waterfall([
 			function(next) {
@@ -170,7 +174,7 @@ var async = require('async'),
 					return next(new Error('content-too-short'));
 				}
 
-				posts.create(uid, tid, content, next);
+				posts.create({uid:uid, tid:tid, content:content, toPid:toPid}, next);
 			},
 			function(data, next) {
 				postData = data;

From 6e5a6b8784658f834b948e94c6825b35637bee20 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 22 Feb 2014 18:56:37 -0500
Subject: [PATCH 007/193] upgraded categories to sorted set, score is the order
 set from acp, check if category topic exists before posting

---
 public/src/modules/composer.js |  2 +-
 src/admin/categories.js        |  4 ++-
 src/categories.js              | 30 +++++++++-------
 src/categoryTools.js           |  5 +++
 src/posts.js                   | 32 ++++++++---------
 src/socket.io/posts.js         |  2 +-
 src/threadTools.js             | 10 +-----
 src/topics.js                  | 30 +++++++++++-----
 src/upgrade.js                 | 66 ++++++++++++++++++++++++++++++----
 9 files changed, 127 insertions(+), 54 deletions(-)

diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js
index 9c1bbf351c..45b8c2b974 100644
--- a/public/src/modules/composer.js
+++ b/public/src/modules/composer.js
@@ -736,7 +736,7 @@ define(['taskbar'], function(taskbar) {
 			}, done);
 		} else if (parseInt(postData.tid, 10) > 0) {
 			socket.emit('posts.reply', {
-				topic_id: postData.tid,
+				tid: postData.tid,
 				content: bodyEl.val(),
 				toPid: postData.toPid
 			}, done);
diff --git a/src/admin/categories.js b/src/admin/categories.js
index 333a22cf70..0522837a05 100644
--- a/src/admin/categories.js
+++ b/src/admin/categories.js
@@ -13,10 +13,12 @@ var db = require('./../database'),
 			for (var key in category) {
 				db.setObjectField('category:' + cid, key, category[key]);
 
-				if (key == 'name') {
+				if (key === 'name') {
 					// reset slugs if name is updated
 					var slug = cid + '/' + utils.slugify(category[key]);
 					db.setObjectField('category:' + cid, 'slug', slug);
+				} else if (key === 'order') {
+					db.sortedSetAdd('categories:cid', category[key], cid);
 				}
 			}
 
diff --git a/src/categories.js b/src/categories.js
index 85315e103a..da9f84a8af 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -18,11 +18,10 @@ var db = require('./database'),
 	Categories.create = function(data, callback) {
 		db.incrObjectField('global', 'nextCid', function(err, cid) {
 			if (err) {
-				return callback(err, null);
+				return callback(err);
 			}
 
 			var slug = cid + '/' + utils.slugify(data.name);
-			db.listAppend('categories:cid', cid);
 
 			var category = {
 				cid: cid,
@@ -36,14 +35,20 @@ var db = require('./database'),
 				topic_count: 0,
 				disabled: 0,
 				order: data.order,
-				link: "",
+				link: '',
 				numRecentReplies: 2,
 				class: 'col-md-3 col-xs-6',
 				imageClass: 'default'
 			};
 
-			db.setObject('category:' + cid, category, function(err, data) {
-				callback(err, category);
+			db.setObject('category:' + cid, category, function(err) {
+				if(err) {
+					return callback(err);
+				}
+
+				db.sortedSetAdd('categories:cid', data.order, cid);
+
+				callback(null, category);
 			});
 		});
 	};
@@ -132,6 +137,10 @@ var db = require('./database'),
 				return callback(err);
 			}
 
+			if (parseInt(topicCount, 10) === 0) {
+				return callback(null, 1);
+			}
+
 			user.getSettings(uid, function(err, settings) {
 				if(err) {
 					return callback(err);
@@ -142,8 +151,8 @@ var db = require('./database'),
 		});
 	};
 
-	Categories.getAllCategories = function(current_user, callback) {
-		db.getListRange('categories:cid', 0, -1, function(err, cids) {
+	Categories.getAllCategories = function(uid, callback) {
+		db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) {
 			if(err) {
 				return callback(err);
 			}
@@ -152,7 +161,7 @@ var db = require('./database'),
 				return callback(null, {categories : []});
 			}
 
-			Categories.getCategories(cids, current_user, callback);
+			Categories.getCategories(cids, uid, callback);
 		});
 	};
 
@@ -311,14 +320,11 @@ var db = require('./database'),
 
 		async.map(cids, getCategory, function(err, categories) {
 			if (err) {
-				winston.err(err);
-				return callback(err, null);
+				return callback(err);
 			}
 
 			categories = categories.filter(function(category) {
 				return !!category;
-			}).sort(function(a, b) {
-				return parseInt(a.order, 10) - parseInt(b.order, 10);
 			});
 
 			callback(null, {
diff --git a/src/categoryTools.js b/src/categoryTools.js
index 8dd2f6664e..454d87128b 100644
--- a/src/categoryTools.js
+++ b/src/categoryTools.js
@@ -2,9 +2,14 @@ var	Groups = require('./groups'),
 	User = require('./user'),
 
 	async = require('async'),
+	db = require('./database'),
 
 	CategoryTools = {};
 
+CategoryTools.exists = function(cid, callback) {
+	db.isSortedSetMember('categories:cid', cid, callback);
+};
+
 CategoryTools.privileges = function(cid, uid, callback) {
 	async.parallel({
 		"+r": function(next) {
diff --git a/src/posts.js b/src/posts.js
index fa3703a585..1ad0750d16 100644
--- a/src/posts.js
+++ b/src/posts.js
@@ -87,7 +87,7 @@ var db = require('./database'),
 			function(postData, next) {
 				postTools.parse(postData.content, function(err, content) {
 					if(err) {
-						return next(err, null);
+						return next(err);
 					}
 
 					postData.content = content;
@@ -303,24 +303,24 @@ var db = require('./database'),
 					});
 				},
 				function(postData, next) {
-					if (postData.content) {
-						postTools.parse(postData.content, function(err, content) {
-							if(err) {
-								return next(err);
-							}
+					if (!postData.content) {
+						return next(null, postData);
+					}
 
-							if(stripTags) {
-								var s = S(content);
-								postData.content = s.stripTags.apply(s, utils.getTagsExcept(['img', 'i'])).s;
-							} else {
-								postData.content = content;
-							}
+					postTools.parse(postData.content, function(err, content) {
+						if(err) {
+							return next(err);
+						}
+
+						if(stripTags) {
+							var s = S(content);
+							postData.content = s.stripTags.apply(s, utils.getTagsExcept(['img', 'i'])).s;
+						} else {
+							postData.content = content;
+						}
 
-							next(null, postData);
-						});
-					} else {
 						next(null, postData);
-					}
+					});
 				}
 			], callback);
 		}
diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js
index 2b54551554..c1b4dc9aaf 100644
--- a/src/socket.io/posts.js
+++ b/src/socket.io/posts.js
@@ -25,7 +25,7 @@ SocketPosts.reply = function(socket, data, callback) {
 		return callback(new Error('not-logged-in'));
 	}
 
-	if(!data || !data.topic_id || !data.content) {
+	if(!data || !data.tid || !data.content) {
 		return callback(new Error('invalid data'));
 	}
 
diff --git a/src/threadTools.js b/src/threadTools.js
index f99f5f7ef6..b9fde04bc5 100644
--- a/src/threadTools.js
+++ b/src/threadTools.js
@@ -18,15 +18,7 @@ var winston = require('winston'),
 (function(ThreadTools) {
 
 	ThreadTools.exists = function(tid, callback) {
-
-		db.isSortedSetMember('topics:tid', tid, function(err, ismember) {
-
-			if (err) {
-				callback(false);
-			}
-
-			callback(ismember);
-		});
+		db.isSortedSetMember('topics:tid', tid, callback);
 	}
 
 	ThreadTools.privileges = function(tid, uid, callback) {
diff --git a/src/topics.js b/src/topics.js
index b7e22871a5..51a7008ef9 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -106,6 +106,12 @@ var async = require('async'),
 
 		async.waterfall([
 			function(next) {
+				categoryTools.exists(cid, next);
+			},
+			function(categoryExists, next) {
+				if(!categoryExists) {
+					return next(new Error('category doesn\'t exist'))
+				}
 				categoryTools.privileges(cid, uid, next);
 			},
 			function(privileges, next) {
@@ -144,7 +150,7 @@ var async = require('async'),
 	};
 
 	Topics.reply = function(data, callback) {
-		var tid = data.topic_id,
+		var tid = data.tid,
 			uid = data.uid,
 			toPid = data.toPid,
 			content = data.content,
@@ -153,6 +159,12 @@ var async = require('async'),
 
 		async.waterfall([
 			function(next) {
+				threadTools.exists(tid, next);
+			},
+			function(topicExists, next) {
+				if (!topicExists) {
+					return next(new Error('topic doesn\'t exist'));
+				}
 				threadTools.privileges(tid, uid, next);
 			},
 			function(privilegesData, next) {
@@ -265,9 +277,9 @@ var async = require('async'),
 	};
 
 	Topics.movePostToTopic = function(pid, tid, callback) {
-		threadTools.exists(tid, function(exists) {
-			if(!exists) {
-				return callback(new Error('Topic doesn\'t exist'));
+		threadTools.exists(tid, function(err, exists) {
+			if(err || !exists) {
+				return callback(err || new Error('Topic doesn\'t exist'));
 			}
 
 			posts.getPostFields(pid, ['deleted', 'tid', 'timestamp'], function(err, postData) {
@@ -426,7 +438,9 @@ var async = require('async'),
 			if(err) {
 				return callback(err);
 			}
-
+			if(!parseInt(postCount, 10)) {
+				return callback(null, 1);
+			}
 			user.getSettings(uid, function(err, settings) {
 				if(err) {
 					return callback(err);
@@ -762,9 +776,9 @@ var async = require('async'),
 	};
 
 	Topics.getTopicWithPosts = function(tid, current_user, start, end, quiet, callback) {
-		threadTools.exists(tid, function(exists) {
-			if (!exists) {
-				return callback(new Error('Topic tid \'' + tid + '\' not found'));
+		threadTools.exists(tid, function(err, exists) {
+			if (err || !exists) {
+				return callback(err || new Error('Topic tid \'' + tid + '\' not found'));
 			}
 
 			// "quiet" is used for things like RSS feed updating, HTML parsing for non-js users, etc
diff --git a/src/upgrade.js b/src/upgrade.js
index 41076ef259..26a3900644 100644
--- a/src/upgrade.js
+++ b/src/upgrade.js
@@ -7,6 +7,7 @@ var db = require('./database'),
 	User = require('./user'),
 	Topics = require('./topics'),
 	Posts = require('./posts'),
+	Categories = require('./categories'),
 	Groups = require('./groups'),
 	Meta = require('./meta'),
 	Plugins = require('./plugins'),
@@ -19,7 +20,7 @@ var db = require('./database'),
 
 Upgrade.check = function(callback) {
 	// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema
-	var	latestSchema = new Date(2014, 1, 20, 20, 25).getTime();
+	var	latestSchema = new Date(2014, 1, 22).getTime();
 
 	db.get('schemaDate', function(err, value) {
 		if (parseInt(value, 10) >= latestSchema) {
@@ -691,7 +692,7 @@ Upgrade.upgrade = function(callback) {
 
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
-				
+
 				db.setObjectField('widgets:home.tpl', 'motd', JSON.stringify([
 					{
 						"widget": "html",
@@ -717,9 +718,9 @@ Upgrade.upgrade = function(callback) {
 
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
-				
+
 				var container = '<div class="panel panel-default"><div class="panel-heading">{title}</div><div class="panel-body">{body}</div></div>';
-				
+
 				db.setObjectField('widgets:category.tpl', 'sidebar', JSON.stringify([
 					{
 						"widget": "recentreplies",
@@ -756,7 +757,7 @@ Upgrade.upgrade = function(callback) {
 
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
-				
+
 				db.setObjectField('widgets:home.tpl', 'footer', JSON.stringify([
 					{
 						"widget": "forumstats",
@@ -778,7 +779,7 @@ Upgrade.upgrade = function(callback) {
 				updatesMade = true;
 
 				var container = '<div class="panel panel-default"><div class="panel-heading">{title}</div><div class="panel-body">{body}</div></div>';
-				
+
 				db.setObjectField('widgets:home.tpl', 'sidebar', JSON.stringify([
 					{
 						"widget": "html",
@@ -813,6 +814,59 @@ Upgrade.upgrade = function(callback) {
 				winston.info('[2014/2/20] Activating NodeBB Essential Widgets - skipped');
 				next();
 			}
+		},
+		function(next) {
+			thisSchemaDate = new Date(2014, 1, 22).getTime();
+
+			if (schemaDate < thisSchemaDate) {
+				updatesMade = true;
+
+				db.exists('categories:cid', function(err, exists) {
+					if(err) {
+						return next(err);
+					}
+					if(!exists) {
+						winston.info('[2014/2/22] Added categories to sorted set - skipped');
+						return next();
+					}
+
+					db.getListRange('categories:cid', 0, -1, function(err, cids) {
+						if(err) {
+							return next(err);
+						}
+
+						if(!Array.isArray(cids)) {
+							winston.info('[2014/2/22] Add categories to sorted set - skipped (cant find any cids)');
+							return next();
+						}
+
+						db.rename('categories:cid', 'categories:cid:old', function(err) {
+							if(err) {
+								return next(err);
+							}
+
+							async.each(cids, function(cid, next) {
+								Categories.getCategoryField(cid, 'order', function(err, order) {
+									if(err) {
+										return next(err);
+									}
+									db.sortedSetAdd('categories:cid', order, cid, next);
+								});
+							}, function(err) {
+								if(err) {
+									return next(err);
+								}
+								winston.info('[2014/2/22] Added categories to sorted set');
+								db.delete('categories:cid:old', next);
+							});
+						});
+					});
+				});
+
+			} else {
+				winston.info('[2014/2/22] Added categories to sorted set - skipped');
+				next();
+			}
 		}
 		// Add new schema updates here
 		// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 17!!!

From a29ea2759637f577d296890b2f2e345c7df09b30 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 22 Feb 2014 22:46:58 -0500
Subject: [PATCH 008/193] closes #1106

---
 src/socket.io/user.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/socket.io/user.js b/src/socket.io/user.js
index 2e9cf32d73..05fa97fcb0 100644
--- a/src/socket.io/user.js
+++ b/src/socket.io/user.js
@@ -1,6 +1,7 @@
 var	async = require('async'),
 	user = require('../user'),
 	topics = require('../topics'),
+	utils = require('./../../public/src/utils'),
 	SocketUser = {};
 
 SocketUser.exists = function(socket, data, callback) {

From 8ef59adb4a2334569426415eadeab226f7cb178f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Del=20Rinc=C3=B3n=20L=C3=B3pez?=
 <alexsp92@gmail.com>
Date: Sun, 23 Feb 2014 14:38:50 +0100
Subject: [PATCH 009/193] =?UTF-8?q?Actualizaci=C3=B3n=20lenguaje=20espa?=
 =?UTF-8?q?=C3=B1ol?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The translation was very faulty with a lack of accents and some words untranslated
---
 public/language/es/global.json | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/public/language/es/global.json b/public/language/es/global.json
index c8c4addfd6..8fa7474ad2 100644
--- a/public/language/es/global.json
+++ b/public/language/es/global.json
@@ -4,23 +4,23 @@
     "buttons.close": "Cerrar",
     "403.title": "Acceso denegado",
     "403.message": "Al parecer no tienes premisos necesarios para estar en este lugar. Tal vez puedes <a href='/login'>intentar conectarte</a>?",
-    "404.title": "Ups... 404, no se encontra che!",
+    "404.title": "Ups... 404, no se encontró lo que buscabas!",
     "404.message": "Al parecer lo que estas buscando no existe. Te recomendamos que vuelvas al <a href='/''>inicio</a>.",
     "500.title": "Error Interno.",
     "500.message": "Ooops! Algo salio mal!, No te alarmes. Nuestros simios hiperinteligentes lo solucionarán",
     "register": "Registrarse",
     "login": "Conectarse",
-    "welcome_back": "Bienvenido de nuevo !",
+    "welcome_back": "Bienvenido de nuevo!",
     "you_have_successfully_logged_in": "Te has conectado!",
     "logout": "Salir",
     "logout.title": "Te has desconectado.",
-    "logout.message": "Haz sido desconectado correctamente",
+    "logout.message": "Has sido desconectado correctamente",
     "save_changes": "Guardar Cambios",
     "close": "Cerrar",
     "pagination": "Paginación",
-    "header.admin": "Admin",
+    "header.admin": "Administración",
     "header.recent": "Recientes",
-    "header.unread": "No Leeidos",
+    "header.unread": "No Leídos",
     "header.popular": "Popular",
     "header.users": "Miembros",
     "header.chats": "Chats",
@@ -29,28 +29,28 @@
     "header.profile": "Perfil",
     "notifications.loading": "Cargando Notificaciones",
     "chats.loading": "Cargando Chats",
-    "motd.welcome": "Bienvenido a NodeBB, la plataforma de debate sobre el futuro.",
+    "motd.welcome": "Bienvenido a NodeBB, la plataforma de debate del el futuro.",
     "motd.get": "Obtener NodeBB",
-    "motd.fork": "Fork",
+    "motd.fork": "Bifurcación",
     "motd.like": "Me gusta",
     "motd.follow": "Seguir",
-    "previouspage": "Pagina Anterior",
-    "nextpage": "Siguente Pagina",
-    "alert.success": "Exito!",
+    "previouspage": "Página Anterior",
+    "nextpage": "Siguente Página",
+    "alert.success": "Éxito!",
     "alert.error": "Error",
-    "alert.banned": "Banneado",
-    "alert.banned.message": "Estas banneado, seras desconectado!",
-    "alert.unfollow": "Ya no estas siguiendo a %1!",
-    "alert.follow": "Estas siguiendo a %1!",
-    "posts": "Posts",
+    "alert.banned": "Baneado",
+    "alert.banned.message": "Estás baneado, serás desconectado!",
+    "alert.unfollow": "Ya no estás siguiendo a %1!",
+    "alert.follow": "Estás siguiendo a %1!",
+    "posts": "Publicaciones",
     "views": "Visitas",
     "posted": "publicado",
     "in": "en",
-    "recentposts": "Posteos Recientes",
+    "recentposts": "Publicaciones Recientes",
     "online": "Conectado",
     "away": "No disponible",
     "dnd": "No molestar",
     "invisible": "Invisible",
     "offline": "Desconectado",
     "privacy": "Privacidad"
-}
\ No newline at end of file
+}

From 8111b9e91f53e3451e3e51a874bc64cbafcd1599 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Del=20Rinc=C3=B3n?= <alexsp92@gmail.com>
Date: Sun, 23 Feb 2014 15:00:52 +0100
Subject: [PATCH 010/193] Finally reviewed spanish transalation

I revised the spanish translation to something more serious and complete. There was a lot of wrong spelled words and some untranslated/bad translated words
---
 public/language/es/category.json       |  2 +-
 public/language/es/footer.json         |  4 +-
 public/language/es/login.json          |  4 +-
 public/language/es/modules.json        |  2 +-
 public/language/es/notifications.json  |  2 +-
 public/language/es/pages.json          | 22 +++++------
 public/language/es/recent.json         |  2 +-
 public/language/es/register.json       | 14 +++----
 public/language/es/reset_password.json | 14 +++----
 public/language/es/topic.json          | 52 +++++++++++++-------------
 public/language/es/unread.json         |  4 +-
 public/language/es/user.json           | 32 ++++++++--------
 public/language/es/users.json          |  6 +--
 13 files changed, 80 insertions(+), 80 deletions(-)

diff --git a/public/language/es/category.json b/public/language/es/category.json
index 4829fb6f60..d75bf5e916 100644
--- a/public/language/es/category.json
+++ b/public/language/es/category.json
@@ -9,6 +9,6 @@
     "posted": "posted",
     "browsing": "viendo ahora",
     "no_replies": "Nadie ha respondido aún",
-    "replied": "respondio",
+    "replied": "respondió",
     "last_edited_by": "ultima edición por"
 }
\ No newline at end of file
diff --git a/public/language/es/footer.json b/public/language/es/footer.json
index 10d3638430..48f2274776 100644
--- a/public/language/es/footer.json
+++ b/public/language/es/footer.json
@@ -2,6 +2,6 @@
     "stats.online": "Online",
     "stats.users": "Gente",
     "stats.topics": "Temas",
-    "stats.posts": "Posts",
-    "success": "exito!"
+    "stats.posts": "Publicaciones",
+    "success": "éxito!"
 }
\ No newline at end of file
diff --git a/public/language/es/login.json b/public/language/es/login.json
index 9093fb3c8a..2a94c0b96c 100644
--- a/public/language/es/login.json
+++ b/public/language/es/login.json
@@ -5,6 +5,6 @@
     "remember_me": "Recordarme?",
     "forgot_password": "Olvidaste tu contraseña?",
     "alternative_logins": "Conexiones Alternativas",
-    "failed_login_attempt": "Error al loguearte, intenta de nuevo.",
-    "login_successful": "Te has conectado con exito!"
+    "failed_login_attempt": "Error al iniciar sesión, intenta otra vez.",
+    "login_successful": "Te has conectado con éxito!"
 }
\ No newline at end of file
diff --git a/public/language/es/modules.json b/public/language/es/modules.json
index dd945e8ed4..e3f2652032 100644
--- a/public/language/es/modules.json
+++ b/public/language/es/modules.json
@@ -1,6 +1,6 @@
 {
     "chat.chatting_with": "Chatear con <span id='chat-with-name'></span>",
-    "chat.placeholder": "ingresa tu mensaje aqui, y presiona enter para enviar",
+    "chat.placeholder": "ingresa tu mensaje aquí, y presiona Intro para enviar",
     "chat.send": "Enviar",
     "chat.no_active": "No tiene conversaciones activas."
 }
\ No newline at end of file
diff --git a/public/language/es/notifications.json b/public/language/es/notifications.json
index 9fb44b2aef..e7b072975d 100644
--- a/public/language/es/notifications.json
+++ b/public/language/es/notifications.json
@@ -3,7 +3,7 @@
     "no_notifs": "No tiene nuevas notificaciones",
     "see_all": "Ver todas las notificaciones",
     "back_to_home": "Volver al Inicio",
-    "outgoing_link": "Link Externo",
+    "outgoing_link": "Enlace Externo",
     "outgoing_link_message": "Estas saliendo del sitio",
     "continue_to": "Continuar",
     "return_to": "Volver a "
diff --git a/public/language/es/pages.json b/public/language/es/pages.json
index d60e0a0a9b..6d91d53482 100644
--- a/public/language/es/pages.json
+++ b/public/language/es/pages.json
@@ -1,13 +1,13 @@
 {
-    "home": "Home",
-    "unread": "Unread Topics",
-    "popular": "Popular Topics",
-    "recent": "Recent Topics",
-    "users": "Registered Users",
-    "notifications": "Notifications",
-    "user.edit": "Editing \"%1\"",
-    "user.following": "People %1 Follows",
-    "user.followers": "People who Follow %1",
-    "user.favourites": "%1's Favourite Posts",
-    "user.settings": "User Settings"
+    "home": "Inicio",
+    "unread": "Temas No Leídos",
+    "popular": "Temas Populares",
+    "recent": "Temas Recientes",
+    "users": "Usuarios Registrado",
+    "notifications": "Notificaciones",
+    "user.edit": "Editando \"%1\"",
+    "user.following": "Gente que sigue %1 ",
+    "user.followers": "Seguidores de %1",
+    "user.favourites": "Publicaciones favoritas de %1 ",
+    "user.settings": "Preferencias del Usuario"
 }
\ No newline at end of file
diff --git a/public/language/es/recent.json b/public/language/es/recent.json
index 63a9d990f6..99fb134ad2 100644
--- a/public/language/es/recent.json
+++ b/public/language/es/recent.json
@@ -3,5 +3,5 @@
     "day": "Día",
     "week": "Semana",
     "month": "Mes",
-    "no_recent_topics": "No hay posts recientes"
+    "no_recent_topics": "No hay publicaciones recientes"
 }
\ No newline at end of file
diff --git a/public/language/es/register.json b/public/language/es/register.json
index 0e276d70c7..542d04310f 100644
--- a/public/language/es/register.json
+++ b/public/language/es/register.json
@@ -1,10 +1,10 @@
 {
     "register": "Registrase",
-    "help.email": "Por defecto, tu email será oculto al publico.",
-    "help.username_restrictions": "El nombre de usuario debe tener entre %1 y %2 caracteres. Los miembros pueden responderte escribiendo @<span id='yourUsername'>usuario</span>.",
-    "help.minimum_password_length": "Tu contraseña debe tener al menos %1 caracteres.",
-    "email_address": "Email",
-    "email_address_placeholder": "Escribe tu email",
+    "help.email": "Por defecto, tu cuenta de correo electrónico será oculto al publico.",
+    "help.username_restrictions": "El nombre de usuario debe tener entre %1 y %2 carácteres. Los miembros pueden responderte escribiendo @<span id='yourUsername'>usuario</span>.",
+    "help.minimum_password_length": "Tu contraseña debe tener al menos %1 carácteres.",
+    "email_address": "Correo electrónico",
+    "email_address_placeholder": "Escribe tu correo electrónico",
     "username": "Usuario",
     "username_placeholder": "Escribe tu usuario",
     "password": "Contraseña",
@@ -12,7 +12,7 @@
     "confirm_password": "Confirmar Contraseña",
     "confirm_password_placeholder": "Confirmar Contraseña",
     "register_now_button": "Registrarme ahora",
-    "alternative_registration": "Otros metodos interesantes para registrarse",
+    "alternative_registration": "Otros métodos interesantes para registrarse",
     "terms_of_use": "Términos y Condiciones de uso",
-    "agree_to_terms_of_use": "Acepto los Terminos y condiciones de uso"
+    "agree_to_terms_of_use": "Acepto los Términos y Condiciones de uso"
 }
\ No newline at end of file
diff --git a/public/language/es/reset_password.json b/public/language/es/reset_password.json
index 967680359e..99ca4a0f0c 100644
--- a/public/language/es/reset_password.json
+++ b/public/language/es/reset_password.json
@@ -1,13 +1,13 @@
 {
-    "reset_password": "Resetear Contraseña",
+    "reset_password": "Reiniciar Contraseña",
     "update_password": "Actualizar contraseña",
     "password_changed.title": "Contraseña editada",
-    "password_changed.message": "<p>La contraseña fue modificada con exito, por favor <a href=\"/login\">conectate de nuevo</a>.",
-    "wrong_reset_code.title": "Código de Reseteo Incorrecto",
-    "wrong_reset_code.message": "El código de reseteo ingresado no es correcto. Por favor intentalo de nuevo o <a href=\"/reset\">pide un nuevo código</a>.",
+    "password_changed.message": "<p>La contraseña fue modificada con éxito, por favor <a href=\"/login\">inicia sesión de nuevo</a>.",
+    "wrong_reset_code.title": "Código de reinicio Incorrecto",
+    "wrong_reset_code.message": "El código de reinicio ingresado no es correcto. Por favor inténtalo de nuevo o <a href=\"/reset\">pide un nuevo código</a>.",
     "new_password": "Nueva Contraseña",
     "repeat_password": "Confirmar Contraseña",
-    "enter_email": "Por favor ingresa tu <strong>email</strong> y te enviaremos un email de como resetear tu cuenta.",
-    "password_reset_sent": "Resteo de contraseña enviado",
-    "invalid_email": "Email Invalido o no existe!"
+    "enter_email": "Por favor ingresa tu <strong>correo electrónico</strong> y te enviaremos un correo con indicaciones para inicializar tu cuenta.",
+    "password_reset_sent": "Reinicio de contraseña enviado",
+    "invalid_email": "Correo Electrónico no válido o inexistente!"
 }
\ No newline at end of file
diff --git a/public/language/es/topic.json b/public/language/es/topic.json
index f631139043..5997346b23 100644
--- a/public/language/es/topic.json
+++ b/public/language/es/topic.json
@@ -2,62 +2,62 @@
     "topic": "Tema",
     "topics": "Temas",
     "no_topics_found": "No se encontraron temas!",
-    "no_posts_found": "No se encontraron posts!",
+    "no_posts_found": "No se encontraron publicaciones!",
     "profile": "Perfil",
     "posted_by": "Publicado por",
     "chat": "Chat",
-    "notify_me": "Seras notificado cuando haya nuevas respuestas en este tema",
+    "notify_me": "Serás notificado cuando haya nuevas respuestas en este tema",
     "quote": "Citar",
     "reply": "Responder",
     "edit": "Editar",
     "delete": "Borrar",
     "move": "Mover",
-    "fork": "Forkear",
-    "banned": "banneado",
+    "fork": "Bifurcar",
+    "banned": "baneado",
     "link": "Link",
     "share": "Compartir",
     "tools": "Herramientas",
     "flag": "Reportar",
-    "flag_title": "Reportar este post a los moderadores",
+    "flag_title": "Reportar esta publicación a los moderadores",
     "deleted_message": "Este tema ha sido borrado. Solo los miembros con privilegios pueden verlo.",
     "watch": "Seguir",
     "share_this_post": "Compartir este post",
     "thread_tools.title": "Herramientas del Tema",
-    "thread_tools.markAsUnreadForAll": "Marcar como no leido",
+    "thread_tools.markAsUnreadForAll": "Marcar como no leído",
     "thread_tools.pin": "Tema Importante",
     "thread_tools.unpin": "Quitar Importante",
     "thread_tools.lock": "Cerrar Tema",
     "thread_tools.unlock": "Abrir Tema",
     "thread_tools.move": "Mover Tema",
-    "thread_tools.fork": "Fork Topic",
+    "thread_tools.fork": "Bifurcar Tema",
     "thread_tools.delete": "Borrar Tema",
     "thread_tools.restore": "Restaurar Tema",
-    "load_categories": "Cargando Categorias",
-    "disabled_categories_note": "Las categorías deshabilidas estan en gris",
+    "load_categories": "Cargando Categoríaas",
+    "disabled_categories_note": "Las categorías deshabilitadas estan en gris",
     "confirm_move": "Mover",
-    "confirm_fork": "Forkear",
+    "confirm_fork": "Bifurcar",
     "favourite": "Favorito",
     "favourites": "Favoritos",
-    "favourites.not_logged_in.title": "No estas conectado :(",
-    "favourites.not_logged_in.message": "Por favor, conectate para agregar a favorito este post.",
-    "favourites.has_no_favourites": "No tienes favoritos, puedes agregar alguno y volver a verlos aqui!",
-    "vote.not_logged_in.title": "No estas conectado",
-    "vote.not_logged_in.message": "Por favor conectate para votar...",
-    "vote.cant_vote_self.title": "Voto Invalido",
-    "vote.cant_vote_self.message": "No puedes votar tus propios posts, palurdo!",
-    "loading_more_posts": "Cargando más posts",
+    "favourites.not_logged_in.title": "No estás conectado :(",
+    "favourites.not_logged_in.message": "Por favor, conáctate para agregar a favoritos esta publicación.",
+    "favourites.has_no_favourites": "No tienes favoritos, puedes agregar alguno y volver a verlos aquí!",
+    "vote.not_logged_in.title": "No estás conectado",
+    "vote.not_logged_in.message": "Por favor conéctate para votar...",
+    "vote.cant_vote_self.title": "Voto Inválido",
+    "vote.cant_vote_self.message": "No puedes votar tus propias publicaciones!",
+    "loading_more_posts": "Cargando más publicaciones",
     "move_topic": "Mover Tema",
-    "move_post": "Mover post",
-    "fork_topic": "Forkear Tema",
-    "topic_will_be_moved_to": "Este tema sera movido a la categoría",
-    "fork_topic_instruction": "Click en los posts que quieres forkear",
-    "fork_no_pids": "No seleccionaste posts!",
-    "fork_success": "Forkeado con exito!",
+    "move_post": "Mover Publicación",
+    "fork_topic": "Bifurcar Tema",
+    "topic_will_be_moved_to": "Este tema será movido a la categoría",
+    "fork_topic_instruction": "Click en las publicaciones que quieres bifurcar",
+    "fork_no_pids": "No seleccionaste publicaciones!",
+    "fork_success": "Bifurcado con exito!",
     "reputation": "Reputación",
-    "posts": "Posts",
+    "posts": "Publicaciones",
     "composer.title_placeholder": "Ingresa el titulo de tu tema",
     "composer.write": "Escribe",
-    "composer.preview": "Preview",
+    "composer.preview": "Previsualización",
     "composer.discard": "Descartar",
     "composer.submit": "Enviar",
     "composer.replying_to": "Respondiendo a",
diff --git a/public/language/es/unread.json b/public/language/es/unread.json
index c7fb8c5aa8..b06f14f687 100644
--- a/public/language/es/unread.json
+++ b/public/language/es/unread.json
@@ -1,6 +1,6 @@
 {
-    "title": "No leeido",
+    "title": "No leído",
     "no_unread_topics": "No hay temas nuevos para leer.",
-    "mark_all_read": "Marcar todo como leeido",
+    "mark_all_read": "Marcar todo como leído",
     "load_more": "Cargar más"
 }
\ No newline at end of file
diff --git a/public/language/es/user.json b/public/language/es/user.json
index 2aaa6da119..c7ef7ae029 100644
--- a/public/language/es/user.json
+++ b/public/language/es/user.json
@@ -1,18 +1,18 @@
 {
-    "banned": "Banneado",
+    "banned": "Baneado",
     "offline": "Desconectado",
     "username": "Usuario",
-    "email": "Email",
+    "email": "Correo Electrónico",
     "fullname": "Nombre",
-    "website": "Website",
+    "website": "Sitio Web",
     "location": "Ubicación",
     "age": "Edad",
     "joined": "Registro",
-    "lastonline": "Última vez online",
+    "lastonline": "Última vez conectado",
     "profile": "Perfil",
     "profile_views": "Visitas",
     "reputation": "Reputación",
-    "posts": "Posts",
+    "posts": "Publicaciones",
     "favourites": "Favoritos",
     "followers": "Seguidores",
     "following": "Sigue",
@@ -26,22 +26,22 @@
     "edit": "Editar",
     "uploaded_picture": "Fotos Cargadas",
     "upload_new_picture": "Cargar Nueva Foto",
-    "current_password": "Password actual",
+    "current_password": "Contraseña actual",
     "change_password": "Cambiar Contraseña",
     "confirm_password": "Confirmar Contraseña",
     "password": "Contraseña",
     "upload_picture": "Cargar foto",
     "upload_a_picture": "Cargar una foto",
-    "image_spec": "Solo puedes subir, PNG, JPG o Archivos GIF.",
-    "max": "max.",
+    "image_spec": "Sólo puedes subir imágenes en formato PNG, JPG o GIF.",
+    "max": "máx.",
     "settings": "Opciones",
-    "show_email": "Mostrar mi Email",
-    "has_no_follower": "Este miembro no tiene seguidores :(",
-    "follows_no_one": "Este miembro no sigue a nadie, que pena :(",
-    "has_no_posts": "Este usuario aun no ha publicado nada.",
-    "email_hidden": "Email Oculto",
+    "show_email": "Mostrar mi Correo electrónico",
+    "has_no_follower": "Este miembro no tiene seguidores.",
+    "follows_no_one": "Este miembro no sigue a nadie.",
+    "has_no_posts": "Este usuario aún no ha publicado nada.",
+    "email_hidden": "Correo electrónico Oculto",
     "hidden": "oculto",
-    "paginate_description": "La paginación de los temas no es por pagina, ya que tiene scroll infinito.",
-    "topics_per_page": "Temas por pagina",
-    "posts_per_page": "Post por pagina"
+    "paginate_description": "La paginación de los temas no es por página, ya que tiene scroll infinito.",
+    "topics_per_page": "Temas por página",
+    "posts_per_page": "Post por página"
 }
\ No newline at end of file
diff --git a/public/language/es/users.json b/public/language/es/users.json
index f1c3007347..5e39c9cb55 100644
--- a/public/language/es/users.json
+++ b/public/language/es/users.json
@@ -1,9 +1,9 @@
 {
-    "latest_users": "Ultimos Miembros",
-    "top_posters": "Top Posteadores",
+    "latest_users": "Últimos Miembros",
+    "top_posters": "Top Publicadores",
     "most_reputation": "Mayor Reputación",
     "online": "Conectados",
     "search": "Buscar",
-    "enter_username": "Ingresa el nombre de usuario para buscar",
+    "enter_username": "Ingresa el nombre de usuario que quieres buscar",
     "load_more": "Cargar más"
 }
\ No newline at end of file

From a9f20a77912d271cbda76ee9a76a8346706a4584 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Del=20Rinc=C3=B3n?= <alexsp92@gmail.com>
Date: Sun, 23 Feb 2014 15:11:10 +0100
Subject: [PATCH 011/193] More translation

---
 public/language/es/topic.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/language/es/topic.json b/public/language/es/topic.json
index 5997346b23..d826369a84 100644
--- a/public/language/es/topic.json
+++ b/public/language/es/topic.json
@@ -32,7 +32,7 @@
     "thread_tools.fork": "Bifurcar Tema",
     "thread_tools.delete": "Borrar Tema",
     "thread_tools.restore": "Restaurar Tema",
-    "load_categories": "Cargando Categoríaas",
+    "load_categories": "Cargando Categorías",
     "disabled_categories_note": "Las categorías deshabilitadas estan en gris",
     "confirm_move": "Mover",
     "confirm_fork": "Bifurcar",

From 6c6c57f45f4a343c54c5bcd58c023796729c40ae Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sun, 23 Feb 2014 15:07:47 -0500
Subject: [PATCH 012/193] closes #1108

---
 public/language/en_GB/topic.json |  1 +
 public/src/forum/topic.js        | 28 ++++++++++------------------
 2 files changed, 11 insertions(+), 18 deletions(-)

diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json
index f51b3f4d33..e03bada393 100644
--- a/public/language/en_GB/topic.json
+++ b/public/language/en_GB/topic.json
@@ -13,6 +13,7 @@
 	"reply": "Reply",
 	"edit": "Edit",
 	"delete": "Delete",
+	"restore": "Restore",
 	"move": "Move",
 	"fork": "Fork",
 	"banned": "banned",
diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index e9fa69e934..6536905897 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -978,7 +978,6 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		function set_delete_state(deleted) {
 			var deleteThreadEl = $('.delete_thread'),
 				deleteTextEl = $('.delete_thread span'),
-				//deleteThreadEl.getElementsByTagName('span')[0],
 				threadEl = $('#post-container'),
 				deleteNotice = document.getElementById('thread-deleted') || document.createElement('div');
 
@@ -1053,23 +1052,16 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		}
 
 		function toggle_post_tools(pid, isDeleted) {
-			var postEl = $('#post-container li[data-pid="' + pid + '"]'),
-				quoteEl = $(postEl[0].querySelector('.quote')),
-				favEl = $(postEl[0].querySelector('.favourite')),
-				replyEl = $(postEl[0].querySelector('.post_reply')),
-				chatEl = $(postEl[0].querySelector('.chat'));
-
-			if (isDeleted) {
-				quoteEl.addClass('none');
-				favEl.addClass('none');
-				replyEl.addClass('none');
-				chatEl.addClass('none');
-			} else {
-				quoteEl.removeClass('none');
-				favEl.removeClass('none');
-				replyEl.removeClass('none');
-				chatEl.removeClass('none');
-			}
+			var postEl = $('#post-container li[data-pid="' + pid + '"]');
+
+			postEl.find('.quote').toggleClass('none', isDeleted);
+			postEl.find('.favourite').toggleClass('none', isDeleted);
+			postEl.find('.post_reply').toggleClass('none', isDeleted);
+			postEl.find('.chat').toggleClass('none', isDeleted);
+
+			translator.translate(isDeleted ? ' [[topic:restore]]' : ' [[topic:delete]]', function(translated) {
+				postEl.find('.delete').find('span').html(translated);
+			});
 		}
 
 		$(window).on('scroll', updateHeader);

From c1e3d95a84326dc45eb1a1daea52c4376b3d1658 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sun, 23 Feb 2014 16:19:30 -0500
Subject: [PATCH 013/193] upgrade fix for new installs

---
 src/upgrade.js | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/upgrade.js b/src/upgrade.js
index 26a3900644..45d146a116 100644
--- a/src/upgrade.js
+++ b/src/upgrade.js
@@ -23,6 +23,13 @@ Upgrade.check = function(callback) {
 	var	latestSchema = new Date(2014, 1, 22).getTime();
 
 	db.get('schemaDate', function(err, value) {
+		if(!value) {
+			db.set('schemaDate', latestSchema, function(err) {
+				callback(true);
+			});
+			return;
+		}
+
 		if (parseInt(value, 10) >= latestSchema) {
 			callback(true);
 		} else {
@@ -40,7 +47,7 @@ Upgrade.upgrade = function(callback) {
 		function(next) {
 			// Prepare for upgrade & check to make sure the upgrade is possible
 			db.get('schemaDate', function(err, value) {
-				schemaDate = value;
+				schemaDate = parseInt(value, 10);
 
 				if (schemaDate >= minSchemaDate || schemaDate === null) {
 					next();

From 2088903358fe9d574247424ff17aa9e8bfed1d5e Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 23 Feb 2014 16:35:28 -0500
Subject: [PATCH 014/193] added check to upgrade.upgrade

---
 app.js         |  2 +-
 src/upgrade.js | 20 ++++++++++++++------
 2 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/app.js b/app.js
index 57cb4dcc02..8ad89c8ade 100644
--- a/app.js
+++ b/app.js
@@ -232,7 +232,7 @@ function reset() {
 					winston.info("Successfully reset theme to Vanilla and disabled all plugins.");
 				}
 
-				process.exit();				
+				process.exit();
 			});
 		});
 	});
diff --git a/src/upgrade.js b/src/upgrade.js
index 45d146a116..6aad843f05 100644
--- a/src/upgrade.js
+++ b/src/upgrade.js
@@ -16,12 +16,12 @@ var db = require('./database'),
 	Upgrade = {},
 
 	minSchemaDate = new Date(2014, 0, 4).getTime(),		// This value gets updated every new MINOR version
-	schemaDate, thisSchemaDate;
+	schemaDate, thisSchemaDate,
 
-Upgrade.check = function(callback) {
 	// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema
-	var	latestSchema = new Date(2014, 1, 22).getTime();
+	latestSchema = new Date(2014, 1, 22).getTime();
 
+Upgrade.check = function(callback) {
 	db.get('schemaDate', function(err, value) {
 		if(!value) {
 			db.set('schemaDate', latestSchema, function(err) {
@@ -47,9 +47,16 @@ Upgrade.upgrade = function(callback) {
 		function(next) {
 			// Prepare for upgrade & check to make sure the upgrade is possible
 			db.get('schemaDate', function(err, value) {
-				schemaDate = parseInt(value, 10);
+				if(!value) {
+					db.set('schemaDate', latestSchema, function(err) {
+						next();
+					});
+					schemaDate = latestSchema;
+				} else {
+					schemaDate = parseInt(value, 10);
+				}
 
-				if (schemaDate >= minSchemaDate || schemaDate === null) {
+				if (schemaDate >= minSchemaDate) {
 					next();
 				} else {
 					next(new Error('upgrade-not-possible'));
@@ -876,7 +883,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		}
 		// Add new schema updates here
-		// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 17!!!
+		// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 22!!!
 	], function(err) {
 		if (!err) {
 			db.set('schemaDate', thisSchemaDate, function(err) {
@@ -886,6 +893,7 @@ Upgrade.upgrade = function(callback) {
 					} else {
 						winston.info('[upgrade] Schema already up to date!');
 					}
+
 					if (callback) {
 						callback(err);
 					} else {

From 122d1ad82a63d22274d7990553d08a999d54fb2e Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sun, 23 Feb 2014 17:42:31 -0500
Subject: [PATCH 015/193] less is more

---
 public/src/forum/topic.js | 37 ++++++++++---------------------------
 1 file changed, 10 insertions(+), 27 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 6536905897..9313333c90 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -1006,36 +1006,19 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		}
 
 		function set_pinned_state(pinned, alert) {
-			var pinEl = $('.pin_thread');
-
 			translator.translate('<i class="fa fa-fw fa-thumb-tack"></i> [[topic:thread_tools.' + (pinned ? 'unpin' : 'pin') + ']]', function(translated) {
-				if (pinned) {
-					pinEl.html(translated);
-					if (alert) {
-						app.alert({
-							'alert_id': 'thread_pin',
-							type: 'success',
-							title: 'Thread Pinned',
-							message: 'Thread has been successfully pinned',
-							timeout: 5000
-						});
-					}
-
-					thread_state.pinned = '1';
-				} else {
-					pinEl.html(translated);
-					if (alert) {
-						app.alert({
-							'alert_id': 'thread_pin',
-							type: 'success',
-							title: 'Thread Unpinned',
-							message: 'Thread has been successfully unpinned',
-							timeout: 5000
-						});
-					}
+				$('.pin_thread').html(translated);
 
-					thread_state.pinned = '0';
+				if (alert) {
+					app.alert({
+						'alert_id': 'thread_pin',
+						type: 'success',
+						title: 'Thread ' + (pinned ? 'Pinned' : 'Unpinned'),
+						message: 'Thread has been successfully ' + (pinned ? 'pinned' : 'unpinned'),
+						timeout: 5000
+					});
 				}
+				thread_state.pinned = pinned ? '1' : '0';
 			});
 		}
 

From 403de08d602b7e0fbd8d77e95f16402c570efeee Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sun, 23 Feb 2014 18:05:50 -0500
Subject: [PATCH 016/193] cleaned more

---
 public/src/forum/topic.js | 70 +++++++++------------------------------
 1 file changed, 16 insertions(+), 54 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 9313333c90..cf2ec408d4 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -915,64 +915,26 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		}
 
 		function set_locked_state(locked, alert) {
-			var threadReplyBtn = $('.topic-main-buttons .post_reply'),
-				postReplyBtns = document.querySelectorAll('#post-container .post_reply'),
-				quoteBtns = document.querySelectorAll('#post-container .quote'),
-				editBtns = document.querySelectorAll('#post-container .edit'),
-				deleteBtns = document.querySelectorAll('#post-container .delete'),
-				numPosts = document.querySelectorAll('#post_container li[data-pid]').length,
-				lockThreadEl = $('.lock_thread'),
-				x;
-
-			if (locked === true) {
-				translator.translate('<i class="fa fa-fw fa-unlock"></i> [[topic:thread_tools.unlock]]', function(translated) {
-					lockThreadEl.html(translated);
-				});
-				threadReplyBtn.attr('disabled', true);
-				threadReplyBtn.html('Locked <i class="fa fa-lock"></i>');
-				for (x = 0; x < numPosts; x++) {
-					postReplyBtns[x].innerHTML = 'Locked <i class="fa fa-lock"></i>';
-					quoteBtns[x].style.display = 'none';
-					editBtns[x].style.display = 'none';
-					deleteBtns[x].style.display = 'none';
-				}
+			translator.translate('<i class="fa fa-fw fa-' + (locked ? 'un': '') + 'lock"></i> [[topic:thread_tools.' + (locked ? 'un': '') + 'lock]]', function(translated) {
+				$('.lock_thread').html(translated);
+			});
 
-				if (alert) {
-					app.alert({
-						'alert_id': 'thread_lock',
-						type: 'success',
-						title: 'Thread Locked',
-						message: 'Thread has been successfully locked',
-						timeout: 5000
-					});
-				}
+			$('.topic-main-buttons .post_reply').attr('disabled', locked).html(locked ? 'Locked <i class="fa fa-lock"></i>' : 'Reply');
 
-				thread_state.locked = '1';
-			} else {
-				translator.translate('<i class="fa fa-fw fa-lock"></i> [[topic:thread_tools.lock]]', function(translated) {
-					lockThreadEl.html(translated);
-				});
-				threadReplyBtn.attr('disabled', false);
-				threadReplyBtn.html('Reply');
-				for (x = 0; x < numPosts; x++) {
-					postReplyBtns[x].innerHTML = 'Reply <i class="fa fa-reply"></i>';
-					quoteBtns[x].style.display = 'inline-block';
-					editBtns[x].style.display = 'inline-block';
-					deleteBtns[x].style.display = 'inline-block';
-				}
-
-				if (alert) {
-					app.alert({
-						'alert_id': 'thread_lock',
-						type: 'success',
-						title: 'Thread Unlocked',
-						message: 'Thread has been successfully unlocked',
-						timeout: 5000
-					});
-				}
+			$('#post-container .post_reply').html(locked ? 'Locked <i class="fa fa-lock"></i>' : 'Reply <i class="fa fa-reply"></i>');
+			$('#post-container').find('.quote, .edit, .delete').toggleClass('none', locked);
 
-				thread_state.locked = '0';
+			if (alert) {
+				app.alert({
+					'alert_id': 'thread_lock',
+					type: 'success',
+					title: 'Thread ' + (locked ? 'Locked' : 'Unlocked'),
+					message: 'Thread has been successfully ' + (locked ? 'locked' : 'unlocked'),
+					timeout: 5000
+				});
 			}
+
+			thread_state.locked = locked ? '1' : '0';
 		}
 
 		function set_delete_state(deleted) {

From b5c8158ad5a513d715e773c3c36cf2fc61b5d93a Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sun, 23 Feb 2014 18:19:10 -0500
Subject: [PATCH 017/193] more cleanup

---
 public/src/forum/topic.js | 38 +++++++++++---------------------------
 1 file changed, 11 insertions(+), 27 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index cf2ec408d4..6a6b465eb4 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -938,32 +938,19 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		}
 
 		function set_delete_state(deleted) {
-			var deleteThreadEl = $('.delete_thread'),
-				deleteTextEl = $('.delete_thread span'),
-				threadEl = $('#post-container'),
-				deleteNotice = document.getElementById('thread-deleted') || document.createElement('div');
-
-			if (deleted) {
-				translator.translate('<i class="fa fa-fw fa-comment"></i> [[topic:thread_tools.restore]]', function(translated) {
-					deleteTextEl.html(translated);
-				});
-				threadEl.addClass('deleted');
+			var threadEl = $('#post-container');
 
-				// Spawn a 'deleted' notice at the top of the page
-				deleteNotice.setAttribute('id', 'thread-deleted');
-				deleteNotice.className = 'alert alert-warning';
-				deleteNotice.innerHTML = 'This thread has been deleted. Only users with thread management privileges can see it.';
-				threadEl.before(deleteNotice);
+			translator.translate('<i class="fa fa-fw ' + (deleted ? 'fa-comment' : 'fa-trash-o') + '"></i> [[topic:thread_tools.' + (deleted ? 'restore' : 'delete') + ']]', function(translated) {
+				$('.delete_thread span').html(translated);
+			});
 
-				thread_state.deleted = '1';
-			} else {
-				translator.translate('<i class="fa fa-fw fa-trash-o"></i> [[topic:thread_tools.delete]]', function(translated) {
-					deleteTextEl.html(translated);
-				});
-				threadEl.removeClass('deleted');
-				deleteNotice.parentNode.removeChild(deleteNotice);
+			threadEl.toggleClass('deleted', deleted);
+			thread_state.deleted = deleted ? '1' : '0';
 
-				thread_state.deleted = '0';
+			if(deleted) {
+				$('<div id="thread-deleted">This thread has been deleted. Only users with thread management privileges can see it.</div>').insertBefore(threadEl);
+			} else {
+				$('#thread-deleted').remove();
 			}
 		}
 
@@ -999,10 +986,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		function toggle_post_tools(pid, isDeleted) {
 			var postEl = $('#post-container li[data-pid="' + pid + '"]');
 
-			postEl.find('.quote').toggleClass('none', isDeleted);
-			postEl.find('.favourite').toggleClass('none', isDeleted);
-			postEl.find('.post_reply').toggleClass('none', isDeleted);
-			postEl.find('.chat').toggleClass('none', isDeleted);
+			postEl.find('.quote, .favourite, .post_reply, .chat').toggleClass('none', isDeleted);
 
 			translator.translate(isDeleted ? ' [[topic:restore]]' : ' [[topic:delete]]', function(translated) {
 				postEl.find('.delete').find('span').html(translated);

From 649bcf49b4bbfe9145e99d57f43fd686f3ef2789 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sun, 23 Feb 2014 18:25:24 -0500
Subject: [PATCH 018/193] one liner

---
 public/src/forum/topic.js | 14 ++------------
 1 file changed, 2 insertions(+), 12 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 6a6b465eb4..eae49ab483 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -1237,18 +1237,8 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 	}
 
 
-	function toggle_mod_tools(pid, state) {
-		var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')),
-			editEl = postEl.find('.edit'),
-			deleteEl = postEl.find('.delete');
-
-		if (state) {
-			editEl.removeClass('none');
-			deleteEl.removeClass('none');
-		} else {
-			editEl.addClass('none');
-			deleteEl.addClass('none');
-		}
+	function toggle_mod_tools(pid, editable) {
+		$('#post-container li[data-pid="' + pid + '"]').find('.edit, .delete').toggleClass('none', !editable);
 	}
 
 	function updatePostCount() {

From f967407805850d3fedf01795d9296b6d351b4bf1 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sun, 23 Feb 2014 18:38:46 -0500
Subject: [PATCH 019/193] follow state clean up

---
 public/src/forum/topic.js | 54 ++++++++++++++-------------------------
 1 file changed, 19 insertions(+), 35 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index eae49ab483..33b6fbc4a2 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -280,41 +280,11 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			fixDeleteStateForPosts();
 
 
-			// Follow Thread State
-			var followEl = $('.posts .follow'),
-				set_follow_state = function(state, quiet) {
-					if (state && !followEl.hasClass('btn-success')) {
-						followEl.addClass('btn-success');
-						followEl.attr('title', 'You are currently receiving updates to this topic');
-						if (!quiet) {
-							app.alert({
-								alert_id: 'topic_follow',
-								timeout: 2500,
-								title: '[[topic:following_topic.title]]',
-								message: '[[topic:following_topic.message]]',
-								type: 'success'
-							});
-						}
-					} else if (!state && followEl.hasClass('btn-success')) {
-						followEl.removeClass('btn-success');
-						followEl.attr('title', 'Be notified of new replies in this topic');
-						if (!quiet) {
-							app.alert({
-								alert_id: 'topic_follow',
-								timeout: 2500,
-								title: '[[topic:not_following_topic.title]]',
-								message: '[[topic:not_following_topic.message]]',
-								type: 'success'
-							});
-						}
-					}
-				};
-
 			socket.emit('topics.followCheck', tid, function(err, state) {
-				set_follow_state(state, true);
+				set_follow_state(state, false);
 			});
 
-			followEl.on('click', function() {
+			$('.posts .follow').on('click', function() {
 				socket.emit('topics.follow', tid, function(err, state) {
 					if(err) {
 						return app.alert({
@@ -326,7 +296,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 						});
 					}
 
-					set_follow_state(state);
+					set_follow_state(state, true);
 				});
 
 				return false;
@@ -914,6 +884,21 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			favourites.html(currentFavourites).attr('data-favourites', currentFavourites);
 		}
 
+		function set_follow_state(state, alert) {
+
+			$('.posts .follow').toggleClass('btn-success', state).attr('title', state ? 'You are currently receiving updates to this topic' : 'Be notified of new replies in this topic');
+
+			if(alert) {
+				app.alert({
+					alert_id: 'topic_follow',
+					timeout: 2500,
+					title: state ? '[[topic:following_topic.title]]' : '[[topic:not_following_topic.title]]',
+					message: state ? '[[topic:following_topic.message]]' : '[[topic:not_following_topic.message]]',
+					type: 'success'
+				});
+			}
+		}
+
 		function set_locked_state(locked, alert) {
 			translator.translate('<i class="fa fa-fw fa-' + (locked ? 'un': '') + 'lock"></i> [[topic:thread_tools.' + (locked ? 'un': '') + 'lock]]', function(translated) {
 				$('.lock_thread').html(translated);
@@ -1057,8 +1042,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		var scrollBottom = scrollTop + $(window).height();
 
 		var elTop = el.offset().top;
-		var height = Math.floor(el.height());
-		var elBottom = elTop + height;
+		var elBottom = elTop + Math.floor(el.height());
 		return !(elTop > scrollBottom || elBottom < scrollTop);
 	}
 

From b8b83c2ec2c28e3c334979ad60c7a15e1ad69c72 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sun, 23 Feb 2014 19:38:26 -0500
Subject: [PATCH 020/193] share buttons

---
 public/src/forum/category.js  | 2 +-
 public/templates/category.tpl | 2 +-
 public/templates/topic.tpl    | 6 +++---
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/public/src/forum/category.js b/public/src/forum/category.js
index e9777904ab..09eacd8c55 100644
--- a/public/src/forum/category.js
+++ b/public/src/forum/category.js
@@ -12,7 +12,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 
 		app.enterRoom('category_' + cid);
 
-		$('#twitter-intent').on('click', function () {
+		$('#twitter-share').on('click', function () {
 			window.open(twitterUrl, '_blank', 'width=550,height=420,scrollbars=no,status=no');
 			return false;
 		});
diff --git a/public/templates/category.tpl b/public/templates/category.tpl
index 418c8ca40b..c437ceac2d 100644
--- a/public/templates/category.tpl
+++ b/public/templates/category.tpl
@@ -14,7 +14,7 @@
 	<!-- IF !disableSocialButtons -->
 	<div class="inline-block pull-right">
 		<a href="#" id="facebook-share"><i class="fa fa-facebook-square fa-2x"></i></a>&nbsp;
-		<a href="#" id="twitter-intent"><i class="fa fa-twitter-square fa-2x"></i></a>&nbsp;
+		<a href="#" id="twitter-share"><i class="fa fa-twitter-square fa-2x"></i></a>&nbsp;
 		<a href="#" id="google-share"><i class="fa fa-google-plus-square fa-2x"></i></a>&nbsp;
 	</div>
 	<!-- ENDIF !disableSocialButtons -->
diff --git a/public/templates/topic.tpl b/public/templates/topic.tpl
index 20c8cb4b29..2bb43ff789 100644
--- a/public/templates/topic.tpl
+++ b/public/templates/topic.tpl
@@ -101,9 +101,9 @@
 									<button title="[[topic:share]]"class="btn btn-sm btn-default share" data-toggle="dropdown" href="#"><i class="fa fa-share-square-o"></i></button>
 									<ul class="dropdown-menu text-center pull-right" role="menu" aria-labelledby="dLabel">
 										<!-- IF !disableSocialButtons -->
-										<li class="btn btn-sm btn-default facebook-share" type="button" title=""><i class="fa fa-facebook"></i></li>
-										<li class="btn btn-sm btn-default twitter-share" type="button" title=""><i class="fa fa-twitter"></i></li>
-										<li class="btn btn-sm btn-default google-share" type="button" title=""><i class="fa fa-google-plus"></i></li>
+										<li class="btn btn-sm btn-default facebook-share" type="button" title=""><i class="fa fa-facebook fa-fw"></i></li>
+										<li class="btn btn-sm btn-default twitter-share" type="button" title=""><i class="fa fa-twitter fa-fw"></i></li>
+										<li class="btn btn-sm btn-default google-share" type="button" title=""><i class="fa fa-google-plus fa-fw"></i></li>
 										<!-- ENDIF !disableSocialButtons -->
 										<li>
 											<input type="text" id="post_{posts.pid}_link" value="" class="form-control pull-right post-link" style=""></input>

From 590fb2e68af757f2574b90e1dcd66e90871affdf Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 23 Feb 2014 21:05:50 -0500
Subject: [PATCH 021/193] added david dep badge to readme

---
 README.md | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 7f9acaf4e2..b0f317fc47 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
 # <img alt="NodeBB" src="http://i.imgur.com/3yj1n6N.png" />
+![Dependency Management powered by David.](https://david-dm.org/designcreateplay/NodeBB.png)
+
 **NodeBB Forum Software** is powered by Node.js and built on a Redis database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB is compatible down to IE8 and has many modern features out of the box such as social network integration and streaming discussions.
 
 * [Get NodeBB](http://www.nodebb.org/ "NodeBB")
@@ -11,7 +13,7 @@
 * [Get Plugins](http://community.nodebb.org/category/7/nodebb-plugins "NodeBB Plugins")
 * [Get Themes](http://community.nodebb.org/category/10/nodebb-themes "NodeBB Themes")
 
-## Screenshots 
+## Screenshots
 
 [<img src="http://i.imgur.com/FLOUuIqb.png" />](http://i.imgur.com/FLOUuIq.png)&nbsp;[<img src="http://i.imgur.com/Ud1LrfIb.png" />](http://i.imgur.com/Ud1LrfI.png)&nbsp;[<img src="http://i.imgur.com/ZC8W39ab.png" />](http://i.imgur.com/ZC8W39a.png)&nbsp;[<img src="http://i.imgur.com/o90kVPib.png" />](http://i.imgur.com/o90kVPi.png)&nbsp;[<img src="http://i.imgur.com/AaRRrU2b.png" />](http://i.imgur.com/AaRRrU2.png)&nbsp;[<img src="http://i.imgur.com/LmHtPhob.png" />](http://i.imgur.com/LmHtPho.png)&nbsp;[<img src="http://i.imgur.com/paiJPJkb.jpg" />](http://i.imgur.com/paiJPJk.jpg)&nbsp;[<img src="http://i.imgur.com/ZfavPHDb.png" />](http://i.imgur.com/ZfavPHD.png)
 

From e3d01df6a2503de71ee5fa65ed8f547653b2dcf4 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sun, 23 Feb 2014 21:50:02 -0500
Subject: [PATCH 022/193] closes #980

---
 public/src/app.js            | 20 +++++++++++++++++---
 public/src/forum/account.js  |  7 ++++++-
 public/src/forum/users.js    | 12 +++++++-----
 public/src/modules/chat.js   |  6 +++++-
 public/templates/account.tpl |  2 +-
 public/templates/chat.tpl    |  2 +-
 public/templates/users.tpl   |  2 +-
 7 files changed, 38 insertions(+), 13 deletions(-)

diff --git a/public/src/app.js b/public/src/app.js
index f7734ab588..9006a23df8 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -290,9 +290,14 @@ var socket,
 				var el = jQuery(this),
 					uid = el.parents('li').attr('data-uid');
 
-				if (uid && users[uid]) {
-					el.siblings('i').attr('class', 'fa fa-circle status ' + users[uid].status)
-				}
+				translator.translate('[[global:' + users[uid].status + ']]', function(translated) {
+					if (uid && users[uid]) {
+						el.siblings('i')
+							.attr('class', 'fa fa-circle status ' + users[uid].status)
+							.attr('title', translated)
+							.attr('data-original-title', translated);
+					}
+				});
 			});
 		});
 	};
@@ -325,6 +330,13 @@ var socket,
 		});
 	};
 
+	app.createStatusTooltips = function() {
+		$('body').tooltip({
+			selector:'.fa-circle.status',
+			placement: 'top'
+		});
+	}
+
 	app.makeNumbersHumanReadable = function(elements) {
 		elements.each(function() {
 			$(this).html(utils.makeNumberHumanReadable($(this).attr('title')));
@@ -343,6 +355,8 @@ var socket,
 
 		app.createUserTooltips();
 
+		app.createStatusTooltips();
+
 		setTimeout(function () {
 			window.scrollTo(0, 1); // rehide address bar on mobile after page load completes.
 		}, 100);
diff --git a/public/src/forum/account.js b/public/src/forum/account.js
index 145e21bf3e..41f1310b62 100644
--- a/public/src/forum/account.js
+++ b/public/src/forum/account.js
@@ -89,7 +89,12 @@ define(['forum/accountheader'], function(header) {
 			return;
 		}
 
-		onlineStatus.attr('class', 'account-online-status fa fa-circle status ' + data.status);
+		translator.translate('[[global:' + data.status + ']]', function(translated) {
+			onlineStatus.attr('class', 'account-online-status fa fa-circle status ' + data.status)
+				.attr('title', translated)
+				.attr('data-original-title', translated);
+		});
+
 	};
 
 	return Account;
diff --git a/public/src/forum/users.js b/public/src/forum/users.js
index 5e780b4670..e0af14faf7 100644
--- a/public/src/forum/users.js
+++ b/public/src/forum/users.js
@@ -111,12 +111,14 @@ define(function() {
 				users: users
 			});
 
-			if(emptyContainer) {
-				$('#users-container .registered-user').remove();
-			}
+			translator.translate(html, function(translated) {
+				if(emptyContainer) {
+					$('#users-container .registered-user').remove();
+				}
 
-			$('#users-container').append(html);
-			$('#users-container .anon-user').appendTo($('#users-container'));
+				$('#users-container').append(translated);
+				$('#users-container .anon-user').appendTo($('#users-container'));
+			});
 		}
 
 		function loadMoreUsers() {
diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js
index c874b330b1..cf99aa1d5c 100644
--- a/public/src/modules/chat.js
+++ b/public/src/modules/chat.js
@@ -91,7 +91,11 @@ define(['taskbar', 'string'], function(taskbar, S) {
 
 	function checkStatus(chatModal) {
 		socket.emit('user.isOnline', chatModal.touid, function(err, data) {
-			$('#chat-user-status').attr('class', 'fa fa-circle status ' + data.status);
+			translator.translate('[[global:' + data.status + ']]', function(translated) {
+				$('#chat-user-status').attr('class', 'fa fa-circle status ' + data.status)
+					.attr('title', translated)
+					.attr('data-original-title', translated);
+			});
 		});
 	}
 
diff --git a/public/templates/account.tpl b/public/templates/account.tpl
index 683b1c42f6..dac12502d9 100644
--- a/public/templates/account.tpl
+++ b/public/templates/account.tpl
@@ -17,7 +17,7 @@
 					<div>
 						<div>
 							<span>
-								<i class="account-online-status fa fa-circle status offline"></i>
+								<i class="account-online-status fa fa-circle status offline" title="[[global:{status}]]"></i>
 								<span class="account-username"> {username}</span>
 							</span>
 						</div>
diff --git a/public/templates/chat.tpl b/public/templates/chat.tpl
index 8cc01343d8..8a7653651c 100644
--- a/public/templates/chat.tpl
+++ b/public/templates/chat.tpl
@@ -4,7 +4,7 @@
 		<div class="modal-content">
 			<div class="modal-header">
 
-				<h4>[[modules:chat.chatting_with]] <i id="chat-user-status" class="fa fa-circle status offline"></i></h4>
+				<h4>[[modules:chat.chatting_with]] <i id="chat-user-status" class="fa fa-circle status offline" title="[[global:offline]]"></i></h4>
 
 			</div>
 			<div class="modal-body">
diff --git a/public/templates/users.tpl b/public/templates/users.tpl
index 5f02ada4cf..e2a8214bc5 100644
--- a/public/templates/users.tpl
+++ b/public/templates/users.tpl
@@ -25,7 +25,7 @@
 			<br/>
 			<div class="user-info">
 				<span>
-					<i class="fa fa-circle status {users.status}"></i>
+					<i class="fa fa-circle status {users.status}" title="[[global:{users.status}]]"></i>
 					<a href="{relative_path}/user/{users.userslug}">{users.username}</a>
 				</span>
 				<br/>

From fd41d93bdb1e01f9d513f93be200d07205a3c0f4 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sun, 23 Feb 2014 22:09:06 -0500
Subject: [PATCH 023/193] took out if from the src

---
 public/templates/category.tpl | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/public/templates/category.tpl b/public/templates/category.tpl
index c437ceac2d..0f4f97748a 100644
--- a/public/templates/category.tpl
+++ b/public/templates/category.tpl
@@ -43,7 +43,11 @@
 						todo: add a check for config.allowTopicsThumbnail if issue#1066 is a win
 					-->
 					<a href="../../user/{topics.userslug}" class="pull-left">
-						<img src="<!-- IF topics.thumb -->{topics.thumb}<!-- ELSE -->{topics.picture}<!-- ENDIF topics.thumb -->" class="img-rounded user-img" title="{topics.username}"/>
+						<!-- IF topics.thumb -->
+						<img src="{topics.thumb}" class="img-rounded user-img" title="{topics.username}"/>
+						<!-- ELSE -->
+						<img src="{topics.picture}" class="img-rounded user-img" title="{topics.username}"/>
+						<!-- ENDIF topics.thumb -->
 					</a>
 
 					<h3>

From 8fe9e5ab46c90b344d78e043153f5b0c6c156b85 Mon Sep 17 00:00:00 2001
From: psychobunny <psycho.bunny@hotmail.com>
Date: Sun, 23 Feb 2014 22:22:17 -0500
Subject: [PATCH 024/193] auto installing nodebb-widget-essentials on new
 setups

---
 src/install.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/install.js b/src/install.js
index 889d5e2639..10ca8c39e6 100644
--- a/src/install.js
+++ b/src/install.js
@@ -339,7 +339,7 @@ var async = require('async'),
 					winston.info('Enabling default plugins');
 
 					var defaultEnabled = [
-						'nodebb-plugin-markdown', 'nodebb-plugin-mentions'
+						'nodebb-plugin-markdown', 'nodebb-plugin-mentions', 'nodebb-widget-essentials'
 					];
 
 					async.each(defaultEnabled, function (pluginId, next) {

From 216ff0f3f9b871bf06fcc5c0ad22396d54b78cc2 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 23 Feb 2014 22:44:49 -0500
Subject: [PATCH 025/193] some checks for handling plugin deletion

---
 src/plugins.js | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/plugins.js b/src/plugins.js
index 93806a8a9a..8da34ebfe2 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -375,11 +375,15 @@ var fs = require('fs'),
 					dirs = dirs.map(function(file) {
 						return path.join(npmPluginPath, file);
 					}).filter(function(file) {
-						var stats = fs.statSync(file),
-							isPlugin =  file.substr(npmPluginPath.length + 1, 14) === 'nodebb-plugin-' || file.substr(npmPluginPath.length + 1, 14) === 'nodebb-widget-';
+						if (fs.existsSync(file)) {
+							var stats = fs.statSync(file),
+								isPlugin =  file.substr(npmPluginPath.length + 1, 14) === 'nodebb-plugin-' || file.substr(npmPluginPath.length + 1, 14) === 'nodebb-widget-';
 
-						if (stats.isDirectory() && isPlugin) return true;
-						else return false;
+							if (stats.isDirectory() && isPlugin) return true;
+							else return false;
+						} else {
+							return false;
+						}
 					});
 
 					next(err, dirs);

From 059c5452ea96a813875910773865c5b2df624f6a Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 23 Feb 2014 22:51:53 -0500
Subject: [PATCH 026/193] emitting alert when restart is attempted in
 development mode

---
 src/socket.io/admin.js | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index 3f674f2d65..5b59557534 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -33,6 +33,15 @@ SocketAdmin.before = function(socket, next) {
 
 SocketAdmin.restart = function(socket, data, callback) {
 	meta.restart();
+
+	if (process.env.NODE_ENV === 'development') {
+		socket.emit('event:alert', {
+			title: 'Restart Not Available',
+			message: 'NodeBB restarting is disabled in development mode.',
+			type: 'warning',
+			timeout: 2500
+		});
+	}
 };
 
 /* Topics */

From c797d6251d4c572acb05a63d2c7768b9ba21c080 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 23 Feb 2014 22:52:59 -0500
Subject: [PATCH 027/193] Revert "emitting alert when restart is attempted in
 development mode"

This reverts commit 059c5452ea96a813875910773865c5b2df624f6a.
---
 src/socket.io/admin.js | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index 5b59557534..3f674f2d65 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -33,15 +33,6 @@ SocketAdmin.before = function(socket, next) {
 
 SocketAdmin.restart = function(socket, data, callback) {
 	meta.restart();
-
-	if (process.env.NODE_ENV === 'development') {
-		socket.emit('event:alert', {
-			title: 'Restart Not Available',
-			message: 'NodeBB restarting is disabled in development mode.',
-			type: 'warning',
-			timeout: 2500
-		});
-	}
 };
 
 /* Topics */

From 2baa381917842d883c26a154324aaf4ca2038df8 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 23 Feb 2014 23:08:54 -0500
Subject: [PATCH 028/193] plugins and themes now trigger a restart

---
 loader.js                         | 12 ++++--------
 public/src/forum/admin/plugins.js |  8 --------
 public/src/forum/admin/themes.js  |  8 --------
 src/socket.io/admin.js            |  6 +++++-
 4 files changed, 9 insertions(+), 25 deletions(-)

diff --git a/loader.js b/loader.js
index d8393c55f7..63027b1603 100644
--- a/loader.js
+++ b/loader.js
@@ -8,14 +8,10 @@ var	fork = require('child_process').fork,
 
 		nbb.on('message', function(cmd) {
 			if (cmd === 'nodebb:restart') {
-				if (process.env.NODE_ENV !== 'development') {
-					nbb.on('exit', function() {
-						start();
-					});
-					nbb.kill();
-				} else {
-					console.log('[app] Development Mode is on, restart aborted.');
-				}
+				nbb.on('exit', function() {
+					start();
+				});
+				nbb.kill();
 			}
 		});
 	}
diff --git a/public/src/forum/admin/plugins.js b/public/src/forum/admin/plugins.js
index 9386f8390f..2cda33fb4e 100644
--- a/public/src/forum/admin/plugins.js
+++ b/public/src/forum/admin/plugins.js
@@ -15,14 +15,6 @@ define(function() {
 					pluginTgl = $('.plugins li[data-plugin-id="' + status.id + '"] button');
 					pluginTgl.html('<i class="fa fa-power-off"></i> ' + (status.active ? 'Dea' : 'A') + 'ctivate');
 					pluginTgl.toggleClass('btn-warning', status.active).toggleClass('btn-success', !status.active);
-
-					app.alert({
-						alert_id: 'plugin_toggled_' + status.id,
-						title: 'Plugin ' + (status.active ? 'Enabled' : 'Disabled'),
-						message: 'You may need to restart NodeBB in order for these changes to be reflected.',
-						type: 'warning',
-						timeout: 5000
-					})
 				});
 			} else {
 				pluginsList.append('<li><p><i>No plugins found.</i></p></li>');
diff --git a/public/src/forum/admin/themes.js b/public/src/forum/admin/themes.js
index 6207b98fff..d8cc89bbd8 100644
--- a/public/src/forum/admin/themes.js
+++ b/public/src/forum/admin/themes.js
@@ -21,14 +21,6 @@ define(['forum/admin/settings'], function(Settings) {
 								type: themeType,
 								id: themeId,
 								src: cssSrc
-							}, function(err) {
-								app.alert({
-									alert_id: 'admin:theme',
-									type: 'success',
-									title: 'Theme Changed',
-									message: 'You have successfully changed your NodeBB\'s theme. Please restart to see the changes.',
-									timeout: 2500
-								});
 							});
 						break;
 					}
diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index 3f674f2d65..f4d11f6d4b 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -274,12 +274,16 @@ SocketAdmin.themes.set = function(socket, data, callback) {
 	if(!data) {
 		return callback(new Error('invalid data'));
 	}
-	meta.themes.set(data, callback);
+	meta.themes.set(data, function() {
+		callback();
+		meta.restart()
+	});
 };
 
 SocketAdmin.plugins.toggle = function(socket, plugin_id) {
 	plugins.toggleActive(plugin_id, function(status) {
 		socket.emit('admin.plugins.toggle', status);
+		meta.restart();
 	});
 };
 

From 105216537305ed0036eb2ee804ef6965c61bc778 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 23 Feb 2014 23:13:32 -0500
Subject: [PATCH 029/193] updated messages when toggling themes or plugins

---
 public/src/forum/admin/plugins.js |  8 ++++++++
 public/src/forum/admin/themes.js  | 10 +++++++++-
 2 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/public/src/forum/admin/plugins.js b/public/src/forum/admin/plugins.js
index 2cda33fb4e..1de7c4637b 100644
--- a/public/src/forum/admin/plugins.js
+++ b/public/src/forum/admin/plugins.js
@@ -15,6 +15,14 @@ define(function() {
 					pluginTgl = $('.plugins li[data-plugin-id="' + status.id + '"] button');
 					pluginTgl.html('<i class="fa fa-power-off"></i> ' + (status.active ? 'Dea' : 'A') + 'ctivate');
 					pluginTgl.toggleClass('btn-warning', status.active).toggleClass('btn-success', !status.active);
+
+					app.alert({
+						alert_id: 'plugin_toggled',
+						title: 'Plugin ' + (status.active ? 'Enabled' : 'Disabled'),
+						message: 'Restarting your NodeBB <i class="fa fa-refresh fa-spin"></i>',
+						type: 'warning',
+						timeout: 5000
+					})
 				});
 			} else {
 				pluginsList.append('<li><p><i>No plugins found.</i></p></li>');
diff --git a/public/src/forum/admin/themes.js b/public/src/forum/admin/themes.js
index d8cc89bbd8..ec474eadb7 100644
--- a/public/src/forum/admin/themes.js
+++ b/public/src/forum/admin/themes.js
@@ -21,6 +21,14 @@ define(['forum/admin/settings'], function(Settings) {
 								type: themeType,
 								id: themeId,
 								src: cssSrc
+							}, function(err) {
+								app.alert({
+									alert_id: 'admin:theme',
+									type: 'success',
+									title: 'Theme Changed',
+									message: 'Restarting your NodeBB <i class="fa fa-refresh fa-spin"></i>',
+									timeout: 3500
+								});
 							});
 						break;
 					}
@@ -42,7 +50,7 @@ define(['forum/admin/settings'], function(Settings) {
 							alert_id: 'admin:theme',
 							type: 'success',
 							title: 'Theme Changed',
-							message: 'You have successfully reverted your NodeBB back to it\'s default theme. Please restart to see the changes.',
+							message: 'You have successfully reverted your NodeBB back to it\'s default theme. Restarting your NodeBB <i class="fa fa-refresh fa-spin"></i>',
 							timeout: 3500
 						});
 					});

From a79ca2b13522cf9bc83cd85306471be59dee72a7 Mon Sep 17 00:00:00 2001
From: psychobunny <psycho.bunny@hotmail.com>
Date: Mon, 24 Feb 2014 13:49:15 -0500
Subject: [PATCH 030/193] clean up ENDIF conditional if object.value is
 undefined and is used in an ELSE block

---
 public/src/templates.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/public/src/templates.js b/public/src/templates.js
index 866e076302..086eca12c1 100644
--- a/public/src/templates.js
+++ b/public/src/templates.js
@@ -410,7 +410,8 @@
 			} else {
 				// clean up all undefined conditionals
 				template = template.replace(/<!-- ELSE -->/gi, 'ENDIF -->')
-									.replace(/<!-- IF([^@]*?)ENDIF([^@]*?)-->/gi, '');
+									.replace(/<!-- IF([^@]*?)ENDIF([^@]*?)-->/gi, '')
+									.replace(/<!-- ENDIF ([^@]*?)-->/gi, '');
 			}
 
 			return template;

From 71ea01edb7d6f1581a69ea0ff8da8997d950cf26 Mon Sep 17 00:00:00 2001
From: psychobunny <psycho.bunny@hotmail.com>
Date: Mon, 24 Feb 2014 13:49:47 -0500
Subject: [PATCH 031/193] switching topics.thumb conditional back

---
 public/templates/category.tpl | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/public/templates/category.tpl b/public/templates/category.tpl
index 0f4f97748a..c437ceac2d 100644
--- a/public/templates/category.tpl
+++ b/public/templates/category.tpl
@@ -43,11 +43,7 @@
 						todo: add a check for config.allowTopicsThumbnail if issue#1066 is a win
 					-->
 					<a href="../../user/{topics.userslug}" class="pull-left">
-						<!-- IF topics.thumb -->
-						<img src="{topics.thumb}" class="img-rounded user-img" title="{topics.username}"/>
-						<!-- ELSE -->
-						<img src="{topics.picture}" class="img-rounded user-img" title="{topics.username}"/>
-						<!-- ENDIF topics.thumb -->
+						<img src="<!-- IF topics.thumb -->{topics.thumb}<!-- ELSE -->{topics.picture}<!-- ENDIF topics.thumb -->" class="img-rounded user-img" title="{topics.username}"/>
 					</a>
 
 					<h3>

From acafa9095f6e49302238b83425493c209e554922 Mon Sep 17 00:00:00 2001
From: psychobunny <psycho.bunny@hotmail.com>
Date: Mon, 24 Feb 2014 14:28:09 -0500
Subject: [PATCH 032/193] added footer widget area to topic.tpl

---
 public/templates/topic.tpl | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/public/templates/topic.tpl b/public/templates/topic.tpl
index 2bb43ff789..2a60224fe6 100644
--- a/public/templates/topic.tpl
+++ b/public/templates/topic.tpl
@@ -8,7 +8,6 @@
 <input type="hidden" template-variable="topic_name" value="{topic_name}" />
 <input type="hidden" template-variable="postcount" value="{postcount}" />
 
-
 <div class="topic">
 	<ol class="breadcrumb">
 		<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
@@ -307,3 +306,9 @@
 	</div>
 
 </div>
+
+<div widget-area="footer" class="col-xs-12">
+	<!-- BEGIN widgets -->
+	{widgets.html}
+	<!-- END widgets -->
+</div>
\ No newline at end of file

From cd9bd91ab2a453b1c92e1b5fc2a63f91d4660404 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 24 Feb 2014 15:10:27 -0500
Subject: [PATCH 033/193] fixes double hashes in share links

---
 public/src/forum/topic.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 33b6fbc4a2..8ebd8342d8 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -484,7 +484,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 
 		$('#post-container').on('shown.bs.dropdown', '.share-dropdown', function() {
 			var pid = $(this).parents('.post-row').attr('data-pid');
-			$('#post_' + pid + '_link').val(window.location.href + "#" + pid);
+			$('#post_' + pid + '_link').val(window.location.protocol + '//' + window.location.host + window.location.pathname + '#' + pid);
 			// without the setTimeout can't select the text in the input
 			setTimeout(function() {
 				$('#post_' + pid + '_link').putCursorAtEnd().select();

From 7c49c32ad311b812747068c02ef93658d15d4290 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 24 Feb 2014 15:23:26 -0500
Subject: [PATCH 034/193] scroll fix

---
 public/src/forum/topic.js | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 8ebd8342d8..b42a970001 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -1055,7 +1055,6 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			offset = 0;
 		}
 
-
 		if($('#post_anchor_' + pid).length) {
 			return scrollToPid(pid);
 		}
@@ -1093,8 +1092,8 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				tid = $('#post-container').attr('data-tid');
 
 			function animateScroll() {
-				$('window,html').animate({
-					scrollTop: scrollTo.offset().top - $('#header-menu').height() - offset
+				$("html, body").animate({
+					scrollTop: (scrollTo.offset().top - $('#header-menu').height() - offset) + "px"
 				}, duration !== undefined ? duration : 400, function() {
 					updateHeader();
 				});

From 0f8ee3a671cc7eb92b720ac90db6ada66853cc7f Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 24 Feb 2014 15:45:30 -0500
Subject: [PATCH 035/193] added instructions class

---
 public/templates/composer.tpl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/templates/composer.tpl b/public/templates/composer.tpl
index 795ce28517..584ba48153 100644
--- a/public/templates/composer.tpl
+++ b/public/templates/composer.tpl
@@ -75,7 +75,7 @@
 
 		<div class="imagedrop"><div>[[topic:composer.drag_and_drop_images]]</div></div>
 
-		<div class="text-center">
+		<div class="text-center instructions">
 			<span>
 				<small>[[topic:composer.content_is_parsed_with]] <a href="http://daringfireball.net/projects/markdown/syntax">Markdown</a>. </small>
 				<span class="upload-instructions hide"><small>[[topic:composer.upload_instructions]]</small></span>

From 9216b29b69dca2f5db9adc3be0444a270b027615 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?=
 <barisusakli@gmail.com>
Date: Mon, 24 Feb 2014 15:54:43 -0500
Subject: [PATCH 036/193] Update README.md

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index b0f317fc47..36c8253255 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
 # <img alt="NodeBB" src="http://i.imgur.com/3yj1n6N.png" />
-![Dependency Management powered by David.](https://david-dm.org/designcreateplay/NodeBB.png)
+[![Dependency Status](https://david-dm.org/designcreateplay/nodebb.png)](https://david-dm.org/designcreateplay/nodebb)
 
 **NodeBB Forum Software** is powered by Node.js and built on a Redis database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB is compatible down to IE8 and has many modern features out of the box such as social network integration and streaming discussions.
 

From ed8e76ebced46d06b1691ec955ac0d90b2be914b Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 24 Feb 2014 16:01:01 -0500
Subject: [PATCH 037/193] added error check to getTopicDataWithUser

---
 src/topics.js | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/src/topics.js b/src/topics.js
index 51a7008ef9..ca7c134831 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -325,13 +325,13 @@ var async = require('async'),
 
 	Topics.getTopicDataWithUser = function(tid, callback) {
 		Topics.getTopicData(tid, function(err, topic) {
-			if(err) {
-				return callback(err, null);
+			if(err || !topic) {
+				return callback(err || new Error('topic doesn\'t exist'));
 			}
 
 			user.getUserFields(topic.uid, ['username', 'userslug', 'picture'] , function(err, userData) {
 				if(err) {
-					return callback(err, null);
+					return callback(err);
 				}
 
 				topic.username = userData.username;
@@ -859,11 +859,7 @@ var async = require('async'),
 		}
 
 		function getReadStatus(next) {
-			if (uid && parseInt(uid, 10) > 0) {
-				Topics.hasReadTopic(tid, uid, next);
-			} else {
-				next(null, null);
-			}
+			Topics.hasReadTopic(tid, uid, next);
 		}
 
 		function getTeaser(next) {

From 8feac114c37b29d6fc224d3db95e090dc73bd08f Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 24 Feb 2014 16:23:11 -0500
Subject: [PATCH 038/193] closes #1022

---
 src/threadTools.js | 10 +++-------
 src/topics.js      | 26 ++++++++++++++++++++++++--
 2 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/src/threadTools.js b/src/threadTools.js
index b9fde04bc5..66269a9133 100644
--- a/src/threadTools.js
+++ b/src/threadTools.js
@@ -65,8 +65,6 @@ var winston = require('winston'),
 					return callback(err);
 				}
 
-				db.decrObjectField('global', 'topicCount');
-
 				ThreadTools.lock(tid);
 
 				Plugins.fireHook('action:topic.delete', tid);
@@ -101,10 +99,10 @@ var winston = require('winston'),
 					return callback(err);
 				}
 
-				db.incrObjectField('global', 'topicCount');
-
 				ThreadTools.unlock(tid);
 
+				Plugins.fireHook('action:topic.restore', tid);
+
 				events.logTopicRestore(uid, tid);
 
 				websockets.emitTopicPostStats();
@@ -113,8 +111,6 @@ var winston = require('winston'),
 					tid: tid
 				});
 
-				Plugins.fireHook('action:topic.restore', tid);
-
 				callback(null, {
 					tid:tid
 				});
@@ -134,7 +130,7 @@ var winston = require('winston'),
 				tid: tid
 			});
 		}
-	}
+	};
 
 	ThreadTools.unlock = function(tid, uid, callback) {
 		topics.setTopicField(tid, 'locked', 0);
diff --git a/src/topics.js b/src/topics.js
index ca7c134831..5c2690c10a 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -1154,6 +1154,16 @@ var async = require('async'),
 		});
 	}
 
+	Topics.updateTopicCount = function(callback) {
+		db.sortedSetCard('topics:recent', function(err, count) {
+			if(err) {
+				return callback(err);
+			}
+
+			db.setObjectField('global', count, callback);
+		});
+	};
+
 	Topics.delete = function(tid, callback) {
 		async.parallel([
 			function(next) {
@@ -1176,7 +1186,13 @@ var async = require('async'),
 					db.incrObjectFieldBy('category:' + cid, 'topic_count', -1, next);
 				});
 			}
-		], callback);
+		], function(err) {
+			if (err) {
+				return callback(err);
+			}
+
+			Topics.updateTopicCount(callback);
+		});
 	};
 
 	Topics.restore = function(tid, callback) {
@@ -1206,7 +1222,13 @@ var async = require('async'),
 						db.incrObjectFieldBy('category:' + cid, 'topic_count', 1, next);
 					});
 				}
-			], callback);
+			], function(err) {
+				if (err) {
+					return callback(err);
+				}
+
+				Topics.updateTopicCount(callback);
+			});
 		});
 	};
 }(exports));

From bbc2df11e1fe69d2adbdd5027811ff545c07f752 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 24 Feb 2014 16:24:21 -0500
Subject: [PATCH 039/193] fixed updateTopicCount

---
 src/topics.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/topics.js b/src/topics.js
index 5c2690c10a..906dac90b9 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -1160,7 +1160,7 @@ var async = require('async'),
 				return callback(err);
 			}
 
-			db.setObjectField('global', count, callback);
+			db.setObjectField('global', 'topicCount', count, callback);
 		});
 	};
 

From 8557c56c45b827db8f867231d1e914d127728997 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Mon, 24 Feb 2014 17:06:41 -0500
Subject: [PATCH 040/193] resolved #1117

---
 src/routes/authentication.js | 15 ++++++++++-----
 src/user.js                  |  6 ++++++
 2 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/src/routes/authentication.js b/src/routes/authentication.js
index 5f40c1133b..f72836caf2 100644
--- a/src/routes/authentication.js
+++ b/src/routes/authentication.js
@@ -83,12 +83,12 @@
 			});
 
 			app.post('/login', function(req, res, next) {
-				passport.authenticate('local', function(err, user, info) {
+				passport.authenticate('local', function(err, userData, info) {
 					if (err) {
 						return next(err);
 					}
 
-					if (!user) {
+					if (!userData) {
 						return res.json(403, info);
 					}
 
@@ -103,9 +103,13 @@
 					}
 
 					req.login({
-						uid: user.uid
+						uid: userData.uid
 					}, function() {
-						res.json(info);
+						if (userData.uid) {
+							user.logIP(userData.uid, req.ip);
+						}
+
+						res.json(200, info);
 					});
 				})(req, res, next);
 			});
@@ -149,6 +153,7 @@
 			}
 
 			if(!uid) {
+				// Even if a user doesn't exist, compare passwords anyway, so we don't immediately return
 				return next(null, false, 'user doesn\'t exist');
 			}
 
@@ -172,7 +177,7 @@
 					}
 
 					if (!res) {
-						next(null, false, 'invalid-password');
+						return next(null, false, 'invalid-password');
 					}
 
 					next(null, {
diff --git a/src/user.js b/src/user.js
index cbf5790ebb..696b71fd0f 100644
--- a/src/user.js
+++ b/src/user.js
@@ -963,6 +963,12 @@ var bcrypt = require('bcryptjs'),
 		});
 	};
 
+	User.logIP = function(uid, ip) {
+		db.sortedSetAdd('uid:' + uid + ':ip', +new Date(), ip || 'Unknown', function(err) {
+			console.log(ip, 'for uid', uid);
+		});
+	};
+
 	User.email = {
 		verify: function(uid, email) {
 			if (!plugins.hasListeners('action:email.send')) {

From 3ed2d21eb6e3b1f4e616106e6c279e1b4558cb6a Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 24 Feb 2014 17:19:49 -0500
Subject: [PATCH 041/193] fixed flag post, added highlight to scroll post

---
 public/src/app.js         | 12 ++++++------
 public/src/forum/topic.js | 10 ++++++++--
 src/socket.io/posts.js    |  1 -
 3 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/public/src/app.js b/public/src/app.js
index 9006a23df8..39cfc8e7b3 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -290,14 +290,14 @@ var socket,
 				var el = jQuery(this),
 					uid = el.parents('li').attr('data-uid');
 
-				translator.translate('[[global:' + users[uid].status + ']]', function(translated) {
 					if (uid && users[uid]) {
-						el.siblings('i')
-							.attr('class', 'fa fa-circle status ' + users[uid].status)
-							.attr('title', translated)
-							.attr('data-original-title', translated);
+						translator.translate('[[global:' + users[uid].status + ']]', function(translated) {
+							el.siblings('i')
+								.attr('class', 'fa fa-circle status ' + users[uid].status)
+								.attr('title', translated)
+								.attr('data-original-title', translated);
+						});
 					}
-				});
 			});
 		});
 	};
diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index b42a970001..418d7ea4c2 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -467,10 +467,10 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		});
 
 		$('#post-container').on('click', '.flag', function() {
+			var btn = $(this);
 			bootbox.confirm('Are you sure you want to flag this post?', function(confirm) {
 				if (confirm) {
-					var pid = $(this).parents('.post-row').attr('data-pid');
-
+					var pid = btn.parents('.post-row').attr('data-pid');
 					socket.emit('posts.flag', pid, function(err) {
 						if(err) {
 							return app.alertError(err.message);
@@ -1096,6 +1096,12 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					scrollTop: (scrollTo.offset().top - $('#header-menu').height() - offset) + "px"
 				}, duration !== undefined ? duration : 400, function() {
 					updateHeader();
+
+					scrollTo.parent().addClass('highlight');
+					setTimeout(function() {
+						scrollTo.parent().removeClass('highlight');
+					}, 5000);
+
 				});
 			}
 
diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js
index c1b4dc9aaf..7a0475354e 100644
--- a/src/socket.io/posts.js
+++ b/src/socket.io/posts.js
@@ -266,7 +266,6 @@ SocketPosts.flag = function(socket, pid, callback) {
 			groups.getByGroupName('administrators', {}, next);
 		},
 		function(adminGroup, next) {
-
 			notifications.create({
 				text: message,
 				path: path,

From 1710b97df565603976a38ff4f658bbb6bbe25c23 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Mon, 24 Feb 2014 17:20:45 -0500
Subject: [PATCH 042/193] removed console log

---
 src/user.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/user.js b/src/user.js
index 696b71fd0f..b4875ceccd 100644
--- a/src/user.js
+++ b/src/user.js
@@ -965,7 +965,7 @@ var bcrypt = require('bcryptjs'),
 
 	User.logIP = function(uid, ip) {
 		db.sortedSetAdd('uid:' + uid + ':ip', +new Date(), ip || 'Unknown', function(err) {
-			console.log(ip, 'for uid', uid);
+			// Do nothing
 		});
 	};
 

From c48d81379b3ba96806a1a327836938138f3149e1 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Mon, 24 Feb 2014 17:49:22 -0500
Subject: [PATCH 043/193] logging unique IPs as well

---
 src/user.js      | 4 +---
 src/webserver.js | 3 +++
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/user.js b/src/user.js
index b4875ceccd..a1648615d9 100644
--- a/src/user.js
+++ b/src/user.js
@@ -964,9 +964,7 @@ var bcrypt = require('bcryptjs'),
 	};
 
 	User.logIP = function(uid, ip) {
-		db.sortedSetAdd('uid:' + uid + ':ip', +new Date(), ip || 'Unknown', function(err) {
-			// Do nothing
-		});
+		db.sortedSetAdd('uid:' + uid + ':ip', +new Date(), ip || 'Unknown');
 	};
 
 	User.email = {
diff --git a/src/webserver.js b/src/webserver.js
index ff0f7bd67e..5ae87ab622 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -278,6 +278,9 @@ process.on('uncaughtException', function(err) {
 					// Disable framing
 					res.setHeader('X-Frame-Options', 'SAMEORIGIN');
 
+					// Log IP address
+					db.sortedSetAdd('ip:recent', +new Date(), req.ip || 'Unknown');
+
 					next();
 				});
 

From 0da2839943f2a3580c632ecd186b1ea33f4c8dd7 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 24 Feb 2014 19:16:23 -0500
Subject: [PATCH 044/193] added the class for topic delete

---
 public/src/forum/topic.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 418d7ea4c2..9923c04d2a 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -933,7 +933,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			thread_state.deleted = deleted ? '1' : '0';
 
 			if(deleted) {
-				$('<div id="thread-deleted">This thread has been deleted. Only users with thread management privileges can see it.</div>').insertBefore(threadEl);
+				$('<div id="thread-deleted" class="alert alert-warning">This thread has been deleted. Only users with thread management privileges can see it.</div>').insertBefore(threadEl);
 			} else {
 				$('#thread-deleted').remove();
 			}

From 0777c96a5530362d854609ff1391cf526468a2b4 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 24 Feb 2014 19:26:26 -0500
Subject: [PATCH 045/193] dont highlight reverse infinite loaded posts

---
 public/src/forum/topic.js | 23 ++++++++++++-----------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 9923c04d2a..df36549074 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -305,10 +305,10 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			enableInfiniteLoading();
 
 			var bookmark = localStorage.getItem('topic:' + tid + ':bookmark');
-			if(window.location.hash) {
-				Topic.scrollToPost(window.location.hash.substr(1));
-			} else if(bookmark) {
-				Topic.scrollToPost(parseInt(bookmark, 10));
+			if (window.location.hash) {
+				Topic.scrollToPost(window.location.hash.substr(1), true);
+			} else if (bookmark) {
+				Topic.scrollToPost(parseInt(bookmark, 10), true);
 			} else {
 				updateHeader();
 			}
@@ -351,7 +351,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 						loadMorePosts(tid, after, function() {
 							fixDeleteStateForPosts();
 							if(direction < 0 && el) {
-								Topic.scrollToPost(el.attr('data-pid'), 0, offset);
+								Topic.scrollToPost(el.attr('data-pid'), false, 0, offset);
 							}
 						});
 					}
@@ -1046,7 +1046,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		return !(elTop > scrollBottom || elBottom < scrollTop);
 	}
 
-	Topic.scrollToPost = function(pid, duration, offset) {
+	Topic.scrollToPost = function(pid, highlight, duration, offset) {
 		if (!pid) {
 			return;
 		}
@@ -1097,11 +1097,12 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				}, duration !== undefined ? duration : 400, function() {
 					updateHeader();
 
-					scrollTo.parent().addClass('highlight');
-					setTimeout(function() {
-						scrollTo.parent().removeClass('highlight');
-					}, 5000);
-
+					if (highlight) {
+						scrollTo.parent().addClass('highlight');
+						setTimeout(function() {
+							scrollTo.parent().removeClass('highlight');
+						}, 5000);
+					}
 				});
 			}
 

From 037ac9180b7a5b5859affeed4a1d6f443a0311f6 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 24 Feb 2014 20:19:31 -0500
Subject: [PATCH 046/193] closes #1122

---
 src/user.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/user.js b/src/user.js
index a1648615d9..ac48c4e7f0 100644
--- a/src/user.js
+++ b/src/user.js
@@ -25,6 +25,7 @@ var bcrypt = require('bcryptjs'),
 		userData.username = userData.username.trim();
 		if (userData.email !== undefined) {
 			userData.email = userData.email.trim();
+			userData.email = validator.escape(userData.email);
 		}
 
 		async.parallel([

From 156c0302c92ce7bbd7df159b7272644f3bd03bdb Mon Sep 17 00:00:00 2001
From: psychobunny <psycho.bunny@hotmail.com>
Date: Tue, 25 Feb 2014 13:15:23 -0500
Subject: [PATCH 047/193] properly namespacing objects in templates

---
 public/src/templates.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/src/templates.js b/public/src/templates.js
index 086eca12c1..243bc59d68 100644
--- a/public/src/templates.js
+++ b/public/src/templates.js
@@ -383,7 +383,7 @@
 						namespace = namespace.replace(d + '.', '');
 						template = setBlock(regex, result, template);
 					} else if (data[d] instanceof Object) {
-						template = parse(data[d], d + '.', template);
+						template = parse(data[d], namespace + d + '.', template);
 					} else {
 						var key = namespace + d,
 							value = typeof data[d] === 'string' ? data[d].replace(/^\s+|\s+$/g, '') : data[d];

From 7e5a7c53bd9a4a9fc2d91e704c4f3b22db077da1 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 14:03:47 -0500
Subject: [PATCH 048/193] changes to templates

---
 public/templates/category.tpl |  12 +--
 public/templates/popular.tpl  |  16 ++--
 public/templates/recent.tpl   |  16 ++--
 public/templates/unread.tpl   |  16 ++--
 src/postTools.js              |   8 +-
 src/socket.io/topics.js       |   1 +
 src/threadTools.js            |   5 +-
 src/topics.js                 | 149 ++++++++++------------------------
 8 files changed, 80 insertions(+), 143 deletions(-)

diff --git a/public/templates/category.tpl b/public/templates/category.tpl
index c437ceac2d..24afd18f2d 100644
--- a/public/templates/category.tpl
+++ b/public/templates/category.tpl
@@ -33,7 +33,7 @@
 		<ul id="topics-container" itemscope itemtype="http://www.schema.org/ItemList" data-nextstart="{nextStart}">
 			<meta itemprop="itemListOrder" content="descending">
 			<!-- BEGIN topics -->
-			<li class="category-item {topics.deleted-class} {topics.unread-class}" itemprop="itemListElement">
+			<li class="category-item <!-- IF topics.deleted -->deleted<!-- ENDIF topics.deleted --><!-- IF topics.unread -->unread<!-- ENDIF topics.unread -->" itemprop="itemListElement">
 
 				<div class="col-md-12 col-xs-12 panel panel-default topic-row">
 
@@ -50,7 +50,7 @@
 						<a href="../../topic/{topics.slug}" itemprop="url">
 							<meta itemprop="name" content="{topics.title}">
 
-							<strong><i class="fa {topics.pin-icon}"></i> <i class="fa {topics.lock-icon}"></i></strong>
+							<strong><!-- IF topics.pinned --><i class="fa fa-thumb-tack"></i><!-- ENDIF topics.pinned --> <!-- IF topics.locked --><i class="fa fa-lock"></i><!-- ENDIF topics.locked --></strong>
 							<span class="topic-title">{topics.title}</span>
 						</a>
 					</h3>
@@ -74,13 +74,13 @@
 							<!-- IF topics.unreplied -->
 							[[category:no_replies]]
 							<!-- ELSE -->
-							<a href="../../user/{topics.teaser_userslug}">
-								<img class="teaser-pic" src="{topics.teaser_userpicture}" title="{topics.teaser_username}"/>
+							<a href="../../user/{topics.teaser.userslug}">
+								<img class="teaser-pic" src="{topics.teaser.picture}" title="{topics.teaser.username}"/>
 							</a>
-							<a href="../../topic/{topics.slug}#{topics.teaser_pid}">
+							<a href="../../topic/{topics.slug}#{topics.teaser.pid}">
 								[[category:replied]]
 							</a>
-							<span class="timeago" title="{topics.teaser_timestamp}"></span>
+							<span class="timeago" title="{topics.teaser.timestamp}"></span>
 							<!-- ENDIF topics.unreplied -->
 						</span>
 					</small>
diff --git a/public/templates/popular.tpl b/public/templates/popular.tpl
index f2c2f59362..2ead0b6b08 100644
--- a/public/templates/popular.tpl
+++ b/public/templates/popular.tpl
@@ -24,7 +24,7 @@
 	<div class="col-md-12">
 		<ul id="topics-container" data-nextstart="{nextStart}">
 		<!-- BEGIN topics -->
-		<li class="category-item {topics.deleted-class} {topics.unread-class}">
+		<li class="category-item <!-- IF topics.deleted --> deleted<!-- ENDIF topics.deleted --><!-- IF topics.unread --> unread<!-- ENDIF topics.unread -->">
 			<div class="col-md-12 col-xs-12 panel panel-default topic-row">
 				<a href="{relative_path}/user/{topics.userslug}" class="pull-left">
 					<img class="img-rounded user-img" src="{topics.picture}" title="{topics.username}" />
@@ -32,7 +32,7 @@
 
 				<h3>
 					<a href="{relative_path}/topic/{topics.slug}">
-						<strong><i class="fa {topics.pin-icon}"></i><i class="fa {topics.lock-icon}"></i></strong>
+						<strong><!-- IF topics.pinned --><i class="fa fa-thumb-tack"></i><!-- ENDIF topics.pinned --> <!-- IF topics.locked --><i class="fa fa-lock"></i><!-- ENDIF topics.locked --></strong>
 						<span class="topic-title">{topics.title}</span>
 					</a>
 				</h3>
@@ -50,8 +50,8 @@
 					|
 					<span>
 						[[category:posted]] [[global:in]]
-						<a href="{relative_path}/category/{topics.categorySlug}">
-							<i class="fa {topics.categoryIcon}"></i> {topics.categoryName}
+						<a href="{relative_path}/category/{topics.category.slug}">
+							<i class="fa {topics.category.icon}"></i> {topics.category.name}
 						</a>
 						<span class="timeago" title="{topics.relativeTime}"></span>
 						</span>
@@ -61,13 +61,13 @@
 						<!-- IF topics.unreplied -->
 						[[category:no_replies]]
 						<!-- ELSE -->
-						<a href="{relative_path}/user/{topics.teaser_userslug}">
-							<img class="teaser-pic" src="{topics.teaser_userpicture}" title="{topics.teaser_username}"/>
+						<a href="{relative_path}/user/{topics.teaser.userslug}">
+							<img class="teaser-pic" src="{topics.teaser.picture}" title="{topics.teaser.username}"/>
 						</a>
-						<a href="{relative_path}/topic/{topics.slug}#{topics.teaser_pid}">
+						<a href="{relative_path}/topic/{topics.slug}#{topics.teaser.pid}">
 							[[category:replied]]
 						</a>
-						<span class="timeago" title="{topics.teaser_timestamp}"></span>
+						<span class="timeago" title="{topics.teaser.timestamp}"></span>
 						<!-- ENDIF topics.unreplied -->
 					</span>
 				</small>
diff --git a/public/templates/recent.tpl b/public/templates/recent.tpl
index 025cffef17..31765697be 100644
--- a/public/templates/recent.tpl
+++ b/public/templates/recent.tpl
@@ -25,7 +25,7 @@
 	<div class="col-md-12">
 		<ul id="topics-container" data-nextstart="{nextStart}">
 		<!-- BEGIN topics -->
-		<li class="category-item {topics.deleted-class} {topics.unread-class}">
+		<li class="category-item <!-- IF topics.deleted --> deleted<!-- ENDIF topics.deleted --><!-- IF topics.unread --> unread<!-- ENDIF topics.unread -->">
 			<div class="col-md-12 col-xs-12 panel panel-default topic-row">
 				<a href="{relative_path}/user/{topics.userslug}" class="pull-left">
 					<img class="img-rounded user-img" src="{topics.picture}" title="{topics.username}" />
@@ -33,7 +33,7 @@
 
 				<h3>
 					<a href="{relative_path}/topic/{topics.slug}">
-						<strong><i class="fa {topics.pin-icon}"></i><i class="fa {topics.lock-icon}"></i></strong>
+						<strong><!-- IF topics.pinned --><i class="fa fa-thumb-tack"></i><!-- ENDIF topics.pinned --> <!-- IF topics.locked --><i class="fa fa-lock"></i><!-- ENDIF topics.locked --></strong>
 						<span class="topic-title">{topics.title}</span>
 					</a>
 				</h3>
@@ -51,9 +51,7 @@
 					|
 					<span>
 						[[category:posted]] [[global:in]]
-						<a href="{relative_path}/category/{topics.categorySlug}">
-							<i class="fa {topics.categoryIcon}"></i> {topics.categoryName}
-						</a>
+						<a href="{relative_path}/category/{topics.category.slug}"><i class="fa {topics.category.icon}"></i> {topics.category.name}</a>
 						<span class="timeago" title="{topics.relativeTime}"></span>
 						</span>
 					</span>
@@ -62,13 +60,13 @@
 						<!-- IF topics.unreplied -->
 						[[category:no_replies]]
 						<!-- ELSE -->
-						<a href="{relative_path}/user/{topics.teaser_userslug}">
-							<img class="teaser-pic" src="{topics.teaser_userpicture}" title="{topics.teaser_username}"/>
+						<a href="{relative_path}/user/{topics.teaser.userslug}">
+							<img class="teaser-pic" src="{topics.teaser.picture}" title="{topics.teaser.username}"/>
 						</a>
-						<a href="{relative_path}/topic/{topics.slug}#{topics.teaser_pid}">
+						<a href="{relative_path}/topic/{topics.slug}#{topics.teaser.pid}">
 							[[category:replied]]
 						</a>
-						<span class="timeago" title="{topics.teaser_timestamp}"></span>
+						<span class="timeago" title="{topics.teaser.timestamp}"></span>
 						<!-- ENDIF topics.unreplied -->
 					</span>
 				</small>
diff --git a/public/templates/unread.tpl b/public/templates/unread.tpl
index 6ec77b3668..c1aa0f87d1 100644
--- a/public/templates/unread.tpl
+++ b/public/templates/unread.tpl
@@ -19,14 +19,14 @@
 		<div class="col-md-12">
 			<ul id="topics-container" data-nextstart="{nextStart}">
 			<!-- BEGIN topics -->
-			<li class="category-item {topics.deleted-class}">
+			<li class="category-item <!-- IF topics.deleted --> deleted<!-- ENDIF topics.deleted -->">
 				<div class="col-md-12 col-xs-12 panel panel-default topic-row">
 					<a href="{relative_path}/user/{topics.userslug}" class="pull-left">
 						<img class="img-rounded user-img" src="{topics.picture}" title="{topics.username}" />
 					</a>
 					<h3>
 						<a href="{relative_path}/topic/{topics.slug}">
-							<strong><i class="fa {topics.pin-icon}"></i><i class="fa {topics.lock-icon}"></i></strong>
+							<strong><!-- IF topics.pinned --><i class="fa fa-thumb-tack"></i><!-- ENDIF topics.pinned --> <!-- IF topics.locked --><i class="fa fa-lock"></i><!-- ENDIF topics.locked --></strong>
 							<span class="topic-title">{topics.title}</span>
 						</a>
 					</h3>
@@ -44,9 +44,7 @@
 						|
 						<span>
 							[[category:posted]] [[global:in]]
-							<a href="{relative_path}/category/{topics.categorySlug}">
-								<i class="fa {topics.categoryIcon}"></i> {topics.categoryName}
-							</a>
+							<a href="{relative_path}/category/{topics.category.slug}"><i class="fa {topics.category.icon}"></i> {topics.category.name}</a>
 							<span class="timeago" title="{topics.relativeTime}"></span>
 							</span>
 						</span>
@@ -55,13 +53,13 @@
 							<!-- IF topics.unreplied -->
 							[[category:no_replies]]
 							<!-- ELSE -->
-							<a href="{relative_path}/user/{topics.teaser_userslug}">
-								<img class="teaser-pic" src="{topics.teaser_userpicture}" title="{topics.teaser_username}"/>
+							<a href="{relative_path}/user/{topics.teaser.userslug}">
+								<img class="teaser-pic" src="{topics.teaser.picture}" title="{topics.teaser.username}"/>
 							</a>
-							<a href="{relative_path}/topic/{topics.slug}#{topics.teaser_pid}">
+							<a href="{relative_path}/topic/{topics.slug}#{topics.teaser.pid}">
 								[[category:replied]]
 							</a>
-							<span class="timeago" title="{topics.teaser_timestamp}"></span>
+							<span class="timeago" title="{topics.teaser.timestamp}"></span>
 							<!-- ENDIF topics.unreplied -->
 						</span>
 					</small>
diff --git a/src/postTools.js b/src/postTools.js
index 303ab53dcf..a097df2d30 100644
--- a/src/postTools.js
+++ b/src/postTools.js
@@ -153,7 +153,11 @@ var winston = require('winston'),
 
 				// Delete the thread if it is the last undeleted post
 				threadTools.getLatestUndeletedPid(postData.tid, function(err, pid) {
-					if (err && err.message === 'no-undeleted-pids-found') {
+					if(err) {
+						return winston.error(err.message);
+					}
+
+					if (!pid) {
 						threadTools.delete(postData.tid, uid, function(err) {
 							if (err) {
 								winston.error('Could not delete topic (tid: ' + postData.tid + ')', err.stack);
@@ -166,7 +170,6 @@ var winston = require('winston'),
 					}
 				});
 
-
 				callback(null);
 			});
 		};
@@ -182,7 +185,6 @@ var winston = require('winston'),
 				}
 			});
 		});
-
 	}
 
 	PostTools.restore = function(uid, pid, callback) {
diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js
index 0950e9fd6c..e07bc09211 100644
--- a/src/socket.io/topics.js
+++ b/src/socket.io/topics.js
@@ -53,6 +53,7 @@ SocketTopics.post = function(socket, data, callback) {
 		}
 
 		if (result) {
+			console.log(result.topicData);
 			index.server.sockets.in('category_' + data.category_id).emit('event:new_topic', result.topicData);
 			index.server.sockets.in('recent_posts').emit('event:new_topic', result.topicData);
 			index.server.sockets.in('user/' + socket.uid).emit('event:new_post', {
diff --git a/src/threadTools.js b/src/threadTools.js
index 66269a9133..05d5f2d09a 100644
--- a/src/threadTools.js
+++ b/src/threadTools.js
@@ -310,8 +310,9 @@ var winston = require('winston'),
 			if(err) {
 				return callback(err);
 			}
+
 			if (pids.length === 0) {
-				return callback(new Error('no-undeleted-pids-found'));
+				return callback(null, null);
 			}
 
 			async.detectSeries(pids, function(pid, next) {
@@ -322,7 +323,7 @@ var winston = require('winston'),
 				if (pid) {
 					callback(null, pid);
 				} else {
-					callback(new Error('no-undeleted-pids-found'));
+					callback(null, null);
 				}
 			});
 		});
diff --git a/src/topics.js b/src/topics.js
index 906dac90b9..4641a6ee5f 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -134,12 +134,16 @@ var async = require('async'),
 				next(null, postData);
 			},
 			function(postData, next) {
-				Topics.getTopicForCategoryView(postData.tid, uid, function(err, topicData) {
+				Topics.getTopicsByTids([postData.tid], data.cid, uid, function(err, topicData) {
 					if(err) {
 						return next(err);
 					}
-
+					if(!topicData || !topicData.length) {
+						return next(new Error('no-topic'));
+					}
+					topicData = topicData[0];
 					topicData.unreplied = 1;
+					console.log(topicData);
 					next(null, {
 						topicData: topicData,
 						postData: postData
@@ -325,18 +329,22 @@ var async = require('async'),
 
 	Topics.getTopicDataWithUser = function(tid, callback) {
 		Topics.getTopicData(tid, function(err, topic) {
-			if(err || !topic) {
+			if (err || !topic) {
 				return callback(err || new Error('topic doesn\'t exist'));
 			}
 
 			user.getUserFields(topic.uid, ['username', 'userslug', 'picture'] , function(err, userData) {
-				if(err) {
+				if (err) {
 					return callback(err);
 				}
 
-				topic.username = userData.username;
-				topic.userslug = userData.userslug
-				topic.picture = userData.picture;
+				if (!userData) {
+					userData = {};
+				}
+
+				topic.username = userData.username || 'Anonymous';
+				topic.userslug = userData.userslug || '';
+				topic.picture = userData.picture || gravatar.url('', {}, true);
 				callback(null, topic);
 			});
 		});
@@ -669,47 +677,21 @@ var async = require('async'),
 		}
 
 		function getTopicInfo(topicData, callback) {
-
-			function getUserInfo(next) {
-				user.getUserFields(topicData.uid, ['username', 'userslug', 'picture'], next);
-			}
-
-			function hasReadTopic(next) {
-				Topics.hasReadTopic(topicData.tid, current_user, next);
-			}
-
-			function getTeaserInfo(next) {
-				Topics.getTeaser(topicData.tid, function(err, teaser) {
-					next(null, teaser || {});
-				});
-			}
-
-			// temporary. I don't think this call should belong here
-
-			function getPrivileges(next) {
-				categoryTools.privileges(cid, current_user, next);
-			}
-
-			function getCategoryInfo(next) {
-				categories.getCategoryFields(topicData.cid, ['name', 'slug', 'icon'], next);
-			}
-
-			async.parallel([getUserInfo, hasReadTopic, getTeaserInfo, getPrivileges, getCategoryInfo], function(err, results) {
-				if(err) {
-					return callback(err);
+			async.parallel({
+				hasread : function (next) {
+					Topics.hasReadTopic(topicData.tid, current_user, next);
+				},
+				teaser : function (next) {
+					Topics.getTeaser(topicData.tid, next);
+				},
+				privileges : function (next) {
+					categoryTools.privileges(topicData.cid, current_user, next);
+				},
+				categoryData : function (next) {
+					console.log(topicData.cid);
+					categories.getCategoryFields(topicData.cid, ['name', 'slug', 'icon'], next);
 				}
-
-				callback(null, {
-					username: results[0].username,
-					userslug: results[0].userslug,
-					picture: results[0].picture,
-					userbanned: results[0].banned,
-					hasread: results[1],
-					teaserInfo: results[2],
-					privileges: results[3],
-					categoryData: results[4]
-				});
-			});
+			}, callback);
 		}
 
 		function isTopicVisible(topicData, topicInfo) {
@@ -719,7 +701,8 @@ var async = require('async'),
 		}
 
 		function loadTopic(tid, next) {
-			Topics.getTopicData(tid, function(err, topicData) {
+
+			Topics.getTopicDataWithUser(tid, function(err, topicData) {
 				if(err) {
 					return next(err);
 				}
@@ -737,25 +720,14 @@ var async = require('async'),
 						return next();
 					}
 
-					topicData['pin-icon'] = parseInt(topicData.pinned, 10) === 1 ? 'fa-thumb-tack' : 'none';
-					topicData['lock-icon'] = parseInt(topicData.locked, 10) === 1 ? 'fa-lock' : 'none';
-					topicData['deleted-class'] = parseInt(topicData.deleted, 10) === 1 ? 'deleted' : '';
-					topicData['unread-class'] = !(topicInfo.hasread && parseInt(current_user, 10) !== 0) ? 'unread' : '';
-
+					topicData.pinned = parseInt(topicData.pinned, 10) === 1;
+					topicData.locked = parseInt(topicData.locked, 10) === 1;
+					topicData.deleted = parseInt(topicData.deleted, 10) === 1;
 					topicData.unread = !(topicInfo.hasread && parseInt(current_user, 10) !== 0);
 					topicData.unreplied = parseInt(topicData.postcount, 10) === 1;
-					topicData.username = topicInfo.username || 'anonymous';
-					topicData.userslug = topicInfo.userslug || '';
-					topicData.picture = topicInfo.picture || gravatar.url('', {}, true);
-					topicData.categoryIcon = topicInfo.categoryData.icon;
-					topicData.categoryName = topicInfo.categoryData.name;
-					topicData.categorySlug = topicInfo.categoryData.slug;
-
-					topicData.teaser_username = topicInfo.teaserInfo.username || '';
-					topicData.teaser_userslug = topicInfo.teaserInfo.userslug || '';
-					topicData.teaser_userpicture = topicInfo.teaserInfo.picture || gravatar.url('', {}, true);
-					topicData.teaser_pid = topicInfo.teaserInfo.pid;
-					topicData.teaser_timestamp = utils.toISOString(topicInfo.teaserInfo.timestamp);
+
+					topicData.category = topicInfo.categoryData;
+					topicData.teaser = topicInfo.teaser;
 
 					next(null, topicData);
 				});
@@ -851,45 +823,6 @@ var async = require('async'),
 		});
 	};
 
-
-	Topics.getTopicForCategoryView = function(tid, uid, callback) {
-
-		function getTopicData(next) {
-			Topics.getTopicDataWithUser(tid, next);
-		}
-
-		function getReadStatus(next) {
-			Topics.hasReadTopic(tid, uid, next);
-		}
-
-		function getTeaser(next) {
-			Topics.getTeaser(tid, next);
-		}
-
-		async.parallel([getTopicData, getReadStatus, getTeaser], function(err, results) {
-			if (err) {
-				return callback(err);
-			}
-
-			var topicData = results[0],
-				hasRead = results[1],
-				teaser = results[2];
-
-			topicData['pin-icon'] = parseInt(topicData.pinned, 10) === 1 ? 'fa-thumb-tack' : 'none';
-			topicData['lock-icon'] = parseInt(topicData.locked, 10) === 1 ? 'fa-lock' : 'none';
-			topicData['unread-class'] = !(hasRead && parseInt(uid, 10) !== 0) ? 'unread' : '';
-
-			topicData.unread = !(hasRead && parseInt(uid, 10) !== 0);
-			topicData.teaser_username = teaser.username || '';
-			topicData.teaser_userslug = teaser.userslug || '';
-			topicData.userslug = teaser.userslug || '';
-			topicData.teaser_timestamp = utils.toISOString(teaser.timestamp);
-			topicData.teaser_userpicture = teaser.picture;
-
-			callback(null, topicData);
-		});
-	};
-
 	Topics.getAllTopics = function(start, end, callback) {
 		db.getSortedSetRevRange('topics:tid', start, end, function(err, tids) {
 			if(err) {
@@ -1037,6 +970,10 @@ var async = require('async'),
 				return callback(err);
 			}
 
+			if (!pid) {
+				return callback(null, null);
+			}
+
 			posts.getPostFields(pid, ['pid', 'uid', 'timestamp'], function(err, postData) {
 				if (err) {
 					return callback(err);
@@ -1052,9 +989,9 @@ var async = require('async'),
 					callback(null, {
 						pid: postData.pid,
 						username: userData.username || 'anonymous',
-						userslug: userData.userslug,
+						userslug: userData.userslug || '',
 						picture: userData.picture || gravatar.url('', {}, true),
-						timestamp: postData.timestamp
+						timestamp: utils.toISOString(postData.timestamp)
 					});
 				});
 			});

From fd8e7327f0da28a160504ecb0d45880bb96c9c7f Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 14:05:14 -0500
Subject: [PATCH 049/193] removed console.log

---
 src/socket.io/topics.js | 1 -
 src/topics.js           | 3 +--
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js
index e07bc09211..0950e9fd6c 100644
--- a/src/socket.io/topics.js
+++ b/src/socket.io/topics.js
@@ -53,7 +53,6 @@ SocketTopics.post = function(socket, data, callback) {
 		}
 
 		if (result) {
-			console.log(result.topicData);
 			index.server.sockets.in('category_' + data.category_id).emit('event:new_topic', result.topicData);
 			index.server.sockets.in('recent_posts').emit('event:new_topic', result.topicData);
 			index.server.sockets.in('user/' + socket.uid).emit('event:new_post', {
diff --git a/src/topics.js b/src/topics.js
index 4641a6ee5f..dbf370f645 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -143,7 +143,7 @@ var async = require('async'),
 					}
 					topicData = topicData[0];
 					topicData.unreplied = 1;
-					console.log(topicData);
+
 					next(null, {
 						topicData: topicData,
 						postData: postData
@@ -688,7 +688,6 @@ var async = require('async'),
 					categoryTools.privileges(topicData.cid, current_user, next);
 				},
 				categoryData : function (next) {
-					console.log(topicData.cid);
 					categories.getCategoryFields(topicData.cid, ['name', 'slug', 'icon'], next);
 				}
 			}, callback);

From 53f1e4d3d41816fa02e84e7928209869abdbea55 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Tue, 25 Feb 2014 14:13:09 -0500
Subject: [PATCH 050/193] dedicated stylesheet.css route for LESS compilation,
 no longer usin less-middleware for base theme...

---
 package.json                |  3 ++-
 public/templates/header.tpl |  2 +-
 src/plugins.js              | 15 ++++++++++++
 src/routes/theme.js         | 47 +++++++++++++++++++++++++++++++++++++
 src/webserver.js            | 21 +++++++----------
 5 files changed, 73 insertions(+), 15 deletions(-)
 create mode 100644 src/routes/theme.js

diff --git a/package.json b/package.json
index f35c875dc3..7fff110bd6 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,8 @@
     "nodebb-widget-essentials": "~0.0",
     "nodebb-theme-vanilla": "~0.0.14",
     "nodebb-theme-cerulean": "~0.0.13",
-    "nodebb-theme-lavender": "~0.0.21"
+    "nodebb-theme-lavender": "~0.0.21",
+    "less": "^1.6.3"
   },
   "optionalDependencies": {
     "redis": "0.8.3",
diff --git a/public/templates/header.tpl b/public/templates/header.tpl
index 5e4c0e2d4a..4a5aa70794 100644
--- a/public/templates/header.tpl
+++ b/public/templates/header.tpl
@@ -6,7 +6,7 @@
 	<meta<!-- IF metaTags.name --> name="{metaTags.name}"<!-- ENDIF metaTags.name --><!-- IF metaTags.property --> property="{metaTags.property}"<!-- ENDIF metaTags.property --><!-- IF metaTags.content --> content="{metaTags.content}"<!-- ENDIF metaTags.content --> />
 	<!-- END metaTags -->
 	<link rel="stylesheet" href="{relative_path}/vendor/fontawesome/css/font-awesome.min.css">
-	<link rel="stylesheet" type="text/css" href="{relative_path}/css/theme.css?{cache-buster}" />
+	<link rel="stylesheet" type="text/css" href="{relative_path}/stylesheet.css?{cache-buster}" />
 	<!-- IF bootswatchCSS --><link href="{bootswatchCSS}" rel="stylesheet" media="screen"><!-- ENDIF bootswatchCSS -->
 	<!-- BEGIN linkTags -->
 	<link<!-- IF linkTags.link --> link="{linkTags.link}"<!-- ENDIF linkTags.link --><!-- IF linkTags.rel --> rel="{linkTags.rel}"<!-- ENDIF linkTags.rel --><!-- IF linkTags.type --> type="{linkTags.type}"<!-- ENDIF linkTags.type --><!-- IF linkTags.href --> href="{linkTags.href}"<!-- ENDIF linkTags.href --> />
diff --git a/src/plugins.js b/src/plugins.js
index 8da34ebfe2..5dad12632a 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -16,6 +16,7 @@ var fs = require('fs'),
 	Plugins.loadedHooks = {};
 	Plugins.staticDirs = {};
 	Plugins.cssFiles = [];
+	Plugins.lessFiles = [];
 
 	Plugins.initialized = false;
 
@@ -221,6 +222,20 @@ var fs = require('fs'),
 					} else {
 						next();
 					}
+				},
+				function(next) {
+					// LESS files for plugins
+					if (pluginData.less && pluginData.less instanceof Array) {
+						if (global.env === 'development') {
+							winston.info('[plugins] Found ' + pluginData.less.length + ' LESS file(s) for plugin ' + pluginData.id);
+						}
+
+						Plugins.lessFiles = Plugins.lessFiles.concat(pluginData.less.map(function(file) {
+							return path.join(pluginData.id, file);
+						}));
+					}
+
+					next();
 				}
 			], function(err) {
 				if (!err) {
diff --git a/src/routes/theme.js b/src/routes/theme.js
new file mode 100644
index 0000000000..f4e260352f
--- /dev/null
+++ b/src/routes/theme.js
@@ -0,0 +1,47 @@
+var path = require('path'),
+	nconf = require('nconf'),
+	less = require('less'),
+
+	meta = require('../meta'),
+	db = require('../database'),
+	plugins = require('../plugins');
+
+(function (Meta) {
+	Meta.createRoutes = function(app) {
+		app.get('/stylesheet.css', function(req, res) {
+			db.getObjectFields('config', ['theme:type', 'theme:id'], function(err, themeData) {
+				var themeId = (themeData['theme:id'] || 'nodebb-theme-vanilla'),
+					baseThemePath = path.join(nconf.get('themes_path'), themeId),
+					paths = [baseThemePath, path.join(__dirname, '../../node_modules')],
+					source = '@import "./theme";',
+					x, numLESS;
+
+				// Add the imports for each LESS file
+				for(x=0,numLESS=plugins.lessFiles.length;x<numLESS;x++) {
+					source += '\n@import "./' + plugins.lessFiles[x] + '";';
+				}
+
+				// Detect if a theme has been selected, and handle appropriately
+				if (!themeData['theme:type'] || themeData['theme:type'] === 'local') {
+					// Local theme
+					var	parser = new (less.Parser)({
+							paths: paths
+						});
+
+					parser.parse(source, function(err, tree) {
+						if (err) {
+							res.send(500, err.message);
+							console.log(err);
+							return;
+						}
+
+						res.type('text/css').send(200, tree.toCSS());
+					});
+				} else {
+					// Bootswatch theme not supported yet
+					res.send(500, 'Give me time!');
+				}
+			});
+		});
+	};
+})(exports);
\ No newline at end of file
diff --git a/src/webserver.js b/src/webserver.js
index 5ae87ab622..50fcae9035 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -22,16 +22,18 @@ var path = require('path'),
 	topics = require('./topics'),
 	ThreadTools = require('./threadTools'),
 	notifications = require('./notifications'),
-	admin = require('./routes/admin'),
-	userRoute = require('./routes/user'),
-	apiRoute = require('./routes/api'),
-	feedsRoute = require('./routes/feeds'),
 	auth = require('./routes/authentication'),
 	meta = require('./meta'),
 	plugins = require('./plugins'),
 	logger = require('./logger'),
 	templates = require('./../public/src/templates'),
-	translator = require('./../public/src/translator');
+	translator = require('./../public/src/translator'),
+
+	admin = require('./routes/admin'),
+	userRoute = require('./routes/user'),
+	apiRoute = require('./routes/api'),
+	feedsRoute = require('./routes/feeds'),
+	themeRoute = require('./routes/theme');
 
 if(nconf.get('ssl')) {
 	server = require('https').createServer({
@@ -322,13 +324,6 @@ process.on('uncaughtException', function(err) {
 									}
 								}
 
-								app.use(require('less-middleware')({
-									src: path.join(nconf.get('themes_path'), themeId),
-									dest: path.join(__dirname, '../public/css'),
-									prefix: nconf.get('relative_path') + '/css',
-									yuicompress: app.enabled('minification') ? true : false
-								}));
-
 								next();
 							} else {
 								// If not using a local theme (bootswatch, etc), drop back to vanilla
@@ -487,8 +482,8 @@ process.on('uncaughtException', function(err) {
 	};
 
 	app.namespace(nconf.get('relative_path'), function () {
-
 		auth.registerApp(app);
+		themeRoute.createRoutes(app);
 		admin.createRoutes(app);
 		userRoute.createRoutes(app);
 		apiRoute.createRoutes(app);

From f39248c83b8730cdc36d00d44f6b282c3085499e Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 14:17:42 -0500
Subject: [PATCH 051/193] closes #1123

---
 public/src/forum/topic.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index df36549074..78174cd737 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -1004,9 +1004,9 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		var tid = templates.get('topic_id');
 
 		if(scrollTop > 50) {
-			$('#header-topic-title').html(templates.get('topic_name')).show();
+			$('#header-topic-title').text(templates.get('topic_name')).show();
 		} else {
-			$('#header-topic-title').html('').hide();
+			$('#header-topic-title').text('').hide();
 		}
 
 		$($('.posts > .post-row').get().reverse()).each(function() {

From 14b298eda8d9531c0d0453762d34bbbd9257bacd Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 14:29:19 -0500
Subject: [PATCH 052/193] cleaned updateHeader

---
 public/src/forum/topic.js | 18 +++++-------------
 1 file changed, 5 insertions(+), 13 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 78174cd737..2076d7e689 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -983,8 +983,6 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 	};
 
 	function updateHeader() {
-		var paginationEl = $('#pagination');
-
 		$('.pagination-block a').off('click').on('click', function() {
 			return false;
 		});
@@ -997,13 +995,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			app.scrollToBottom();
 		});
 
-		var windowHeight = jQuery(window).height();
-		var scrollTop = jQuery(window).scrollTop();
-		var scrollBottom = scrollTop + windowHeight;
-		var progressBar = $('.progress-bar');
-		var tid = templates.get('topic_id');
-
-		if(scrollTop > 50) {
+		if($(window).scrollTop() > 50) {
 			$('#header-topic-title').text(templates.get('topic_name')).show();
 		} else {
 			$('#header-topic-title').text('').hide();
@@ -1017,8 +1009,8 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				if(index > Topic.postCount) {
 					index = Topic.postCount;
 				}
-				paginationEl.html(index + ' out of ' + Topic.postCount);
-				progressBar.width((index / Topic.postCount * 100) + '%');
+				$('#pagination').html(index + ' out of ' + Topic.postCount);
+				$('.progress-bar').width((index / Topic.postCount * 100) + '%');
 				return false;
 			}
 		});
@@ -1028,9 +1020,9 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			if (elementInView(el)) {
 				var index = parseInt(el.attr('data-index'), 10) + 1;
 				if(index === 0) {
-					localStorage.removeItem("topic:" + tid + ":bookmark");
+					localStorage.removeItem("topic:" + templates.get('topic_id') + ":bookmark");
 				} else {
-					localStorage.setItem("topic:" + tid + ":bookmark", el.attr('data-pid'));
+					localStorage.setItem("topic:" + templates.get('topic_id') + ":bookmark", el.attr('data-pid'));
 				}
 				return false;
 			}

From 7c1a46f25db8874a8bef4a94f59ed0718b9997dc Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 14:41:14 -0500
Subject: [PATCH 053/193] misc cleanup

---
 src/topics.js | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/topics.js b/src/topics.js
index dbf370f645..05d12787b8 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -958,9 +958,7 @@ var async = require('async'),
 			return callback(null, []);
 		}
 
-		async.map(tids, function(tid, next) {
-			Topics.getTeaser(tid, next);
-		}, callback);
+		async.map(tids, Topics.getTeaser, callback)
 	};
 
 	Topics.getTeaser = function(tid, callback) {
@@ -1039,7 +1037,7 @@ var async = require('async'),
 	Topics.isLocked = function(tid, callback) {
 		Topics.getTopicField(tid, 'locked', function(err, locked) {
 			if(err) {
-				return callback(err, null);
+				return callback(err);
 			}
 			callback(null, parseInt(locked, 10) === 1);
 		});
@@ -1074,16 +1072,18 @@ var async = require('async'),
 
 			function getUid(pid, next) {
 				posts.getPostField(pid, 'uid', function(err, uid) {
-					if (err)
+					if (err) {
 						return next(err);
+					}
 					uids[uid] = 1;
-					next(null);
+					next();
 				});
 			}
 
 			async.each(pids, getUid, function(err) {
-				if (err)
-					return callback(err, null);
+				if (err) {
+					return callback(err);
+				}
 
 				callback(null, Object.keys(uids));
 			});

From 4b1c8150d375e58c5e03ae946ba45b0c1fc22cca Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 14:51:11 -0500
Subject: [PATCH 054/193] update post has in address bar, #1126

---
 public/src/forum/topic.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 2076d7e689..5ab2467b33 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -1023,6 +1023,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					localStorage.removeItem("topic:" + templates.get('topic_id') + ":bookmark");
 				} else {
 					localStorage.setItem("topic:" + templates.get('topic_id') + ":bookmark", el.attr('data-pid'));
+					window.location.hash = el.attr('data-pid');
 				}
 				return false;
 			}

From e3ec4e938a75c84ae472b55971cceaa7f6ffa797 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Tue, 25 Feb 2014 14:59:07 -0500
Subject: [PATCH 055/193] Revert "update post has in address bar, #1126"

This reverts commit 4b1c8150d375e58c5e03ae946ba45b0c1fc22cca.
---
 public/src/forum/topic.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 5ab2467b33..2076d7e689 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -1023,7 +1023,6 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					localStorage.removeItem("topic:" + templates.get('topic_id') + ":bookmark");
 				} else {
 					localStorage.setItem("topic:" + templates.get('topic_id') + ":bookmark", el.attr('data-pid'));
-					window.location.hash = el.attr('data-pid');
 				}
 				return false;
 			}

From 55673782343f1470ef2f42823cc220da59b4797c Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 15:10:50 -0500
Subject: [PATCH 056/193] change hash take 2

---
 public/src/forum/topic.js | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 5ab2467b33..80aa2fa4cb 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -1023,7 +1023,12 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					localStorage.removeItem("topic:" + templates.get('topic_id') + ":bookmark");
 				} else {
 					localStorage.setItem("topic:" + templates.get('topic_id') + ":bookmark", el.attr('data-pid'));
-					window.location.hash = el.attr('data-pid');
+
+					if(history.pushState) {
+						history.replaceState(null, window.location.protocol + '//' + window.location.host + window.location.pathname, '#' + el.attr('data-pid'));
+					} else {
+						location.hash = '#' + el.attr('data-pid');
+					}
 				}
 				return false;
 			}

From a3855a47e5e18e1c83aca1f349ee116e84c38d28 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 15:12:42 -0500
Subject: [PATCH 057/193] check replaceState

---
 public/src/forum/topic.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 80aa2fa4cb..7ee9806f5a 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -1024,7 +1024,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				} else {
 					localStorage.setItem("topic:" + templates.get('topic_id') + ":bookmark", el.attr('data-pid'));
 
-					if(history.pushState) {
+					if(history.replaceState) {
 						history.replaceState(null, window.location.protocol + '//' + window.location.host + window.location.pathname, '#' + el.attr('data-pid'));
 					} else {
 						location.hash = '#' + el.attr('data-pid');

From bc1c419722d82e5fe00fa9930b6feea0e0160910 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Tue, 25 Feb 2014 15:20:21 -0500
Subject: [PATCH 058/193] caching compiled CSS locally, so repeated calls to
 the stylesheet serve from cache

---
 src/meta.js         | 4 ++++
 src/routes/theme.js | 9 +++++++--
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/src/meta.js b/src/meta.js
index 419f293648..e494c4fccc 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -337,6 +337,10 @@ var fs = require('fs'),
 		}
 	};
 
+	Meta.css = {
+		cache: undefined
+	};
+
 	Meta.restart = function() {
 		if (process.send) {
 			process.send('nodebb:restart');
diff --git a/src/routes/theme.js b/src/routes/theme.js
index f4e260352f..e8a9052029 100644
--- a/src/routes/theme.js
+++ b/src/routes/theme.js
@@ -9,6 +9,11 @@ var path = require('path'),
 (function (Meta) {
 	Meta.createRoutes = function(app) {
 		app.get('/stylesheet.css', function(req, res) {
+			if (meta.css.cache) {
+				res.type('text/css').send(200, meta.css.cache);
+				return;
+			}
+
 			db.getObjectFields('config', ['theme:type', 'theme:id'], function(err, themeData) {
 				var themeId = (themeData['theme:id'] || 'nodebb-theme-vanilla'),
 					baseThemePath = path.join(nconf.get('themes_path'), themeId),
@@ -31,11 +36,11 @@ var path = require('path'),
 					parser.parse(source, function(err, tree) {
 						if (err) {
 							res.send(500, err.message);
-							console.log(err);
 							return;
 						}
 
-						res.type('text/css').send(200, tree.toCSS());
+						meta.css.cache = tree.toCSS();
+						res.type('text/css').send(200, meta.css.cache);
 					});
 				} else {
 					// Bootswatch theme not supported yet

From b658c68736463e96891d80ba813fe8fb2b5d9015 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 15:26:10 -0500
Subject: [PATCH 059/193] back doesnt work

---
 public/src/forum/topic.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 7ee9806f5a..9d59f4249a 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -1025,7 +1025,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					localStorage.setItem("topic:" + templates.get('topic_id') + ":bookmark", el.attr('data-pid'));
 
 					if(history.replaceState) {
-						history.replaceState(null, window.location.protocol + '//' + window.location.host + window.location.pathname, '#' + el.attr('data-pid'));
+						history.replaceState(null, null, window.location.protocol + '//' + window.location.host + window.location.pathname + '#' + el.attr('data-pid'));
 					} else {
 						location.hash = '#' + el.attr('data-pid');
 					}

From 3c53ebb02e44e347eb76edae585920ce18315e72 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 16:33:22 -0500
Subject: [PATCH 060/193] better scrolling

---
 public/src/forum/topic.js | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 9d59f4249a..b314a2860c 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -1,6 +1,7 @@
 define(['composer', 'forum/pagination'], function(composer, pagination) {
 	var	Topic = {},
-		infiniteLoaderActive = false;
+		infiniteLoaderActive = false,
+		scrollingToPost = false;
 
 	function showBottomPostBar() {
 		if($('#post-container .post-row').length > 1 || !$('#post-container li[data-index="0"]').length) {
@@ -983,6 +984,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 	};
 
 	function updateHeader() {
+
 		$('.pagination-block a').off('click').on('click', function() {
 			return false;
 		});
@@ -1024,10 +1026,15 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				} else {
 					localStorage.setItem("topic:" + templates.get('topic_id') + ":bookmark", el.attr('data-pid'));
 
-					if(history.replaceState) {
-						history.replaceState(null, null, window.location.protocol + '//' + window.location.host + window.location.pathname + '#' + el.attr('data-pid'));
-					} else {
-						location.hash = '#' + el.attr('data-pid');
+					if (!scrollingToPost) {
+						if (history.replaceState) {
+							history.replaceState({
+								url: window.location.pathname.slice(1) + '#' + el.attr('data-pid')
+							}, null,
+								window.location.protocol + '//' + window.location.host + window.location.pathname + '#' + el.attr('data-pid'));
+						} else {
+							location.hash = '#' + el.attr('data-pid');
+						}
 					}
 				}
 				return false;
@@ -1036,7 +1043,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 	}
 
 	function elementInView(el) {
-		var scrollTop = $(window).scrollTop();
+		var scrollTop = $(window).scrollTop() + $('#header-menu').height();
 		var scrollBottom = scrollTop + $(window).height();
 
 		var elTop = el.offset().top;
@@ -1090,11 +1097,12 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				tid = $('#post-container').attr('data-tid');
 
 			function animateScroll() {
+				scrollingToPost = true;
+
 				$("html, body").animate({
 					scrollTop: (scrollTo.offset().top - $('#header-menu').height() - offset) + "px"
 				}, duration !== undefined ? duration : 400, function() {
-					updateHeader();
-
+					scrollingToPost = false;
 					if (highlight) {
 						scrollTo.parent().addClass('highlight');
 						setTimeout(function() {

From 12e3e4539872465cdbef7b1e854e86ff7eb90608 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Tue, 25 Feb 2014 16:50:58 -0500
Subject: [PATCH 061/193] closed #1095 - js is now minified and saved to memory
 the first time it is requested (on prod mode), same with theme CSS

---
 src/meta.js                      | 59 ++++----------------------------
 src/routes/{theme.js => meta.js} | 10 ++++++
 src/webserver.js                 |  4 +--
 3 files changed, 19 insertions(+), 54 deletions(-)
 rename src/routes/{theme.js => meta.js} (85%)

diff --git a/src/meta.js b/src/meta.js
index e494c4fccc..7a41b162ff 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -223,6 +223,7 @@ var fs = require('fs'),
 	};
 
 	Meta.js = {
+		cache: undefined,
 		scripts: [
 			'vendor/jquery/js/jquery.js',
 			'vendor/jquery/js/jquery-ui-1.10.4.custom.js',
@@ -240,7 +241,7 @@ var fs = require('fs'),
 			'src/translator.js',
 			'src/utils.js'
 		],
-		minFile: path.join(__dirname, '..', 'public/src/nodebb.min.js'),
+		minFile: nconf.get('relative_path') + 'nodebb.min.js',
 		get: function (callback) {
 			plugins.fireHook('filter:scripts.get', this.scripts, function(err, scripts) {
 				var ctime,
@@ -265,46 +266,9 @@ var fs = require('fs'),
 
 				Meta.js.scripts = jsPaths.filter(function(path) { return path !== null });
 
-				if (process.env.NODE_ENV !== 'development') {
-					async.parallel({
-						ctime: function (next) {
-							async.map(jsPaths, fs.stat, function (err, stats) {
-								async.reduce(stats, 0, function (memo, item, next) {
-									if(item) {
-										ctime = +new Date(item.ctime);
-										next(null, ctime > memo ? ctime : memo);
-									} else {
-										next(null, memo);
-									}
-								}, next);
-							});
-						},
-						minFile: function (next) {
-							if (!fs.existsSync(Meta.js.minFile)) {
-								winston.info('No minified client-side library found');
-								return next(null, 0);
-							}
-
-							fs.stat(Meta.js.minFile, function (err, stat) {
-								next(err, +new Date(stat.ctime));
-							});
-						}
-					}, function (err, results) {
-						if (results.minFile > results.ctime) {
-							winston.info('No changes to client-side libraries -- skipping minification');
-							callback(null, [path.relative(path.join(__dirname, '../public'), Meta.js.minFile)]);
-						} else {
-							winston.info('Minifying client-side libraries -- please wait');
-							Meta.js.minify(function () {
-								callback(null, [
-									path.relative(path.join(__dirname, '../public'), Meta.js.minFile)
-								]);
-							});
-						}
-					});
-				} else {
-					callback(null, scripts);
-				}
+				callback(null, [
+					Meta.js.minFile
+				]);
 			});
 		},
 		minify: function (callback) {
@@ -317,17 +281,8 @@ var fs = require('fs'),
 			}
 
 			minified = uglifyjs.minify(jsPaths);
-			fs.writeFile(Meta.js.minFile, minified.code, function (err) {
-				if (!err) {
-					if (process.env.NODE_ENV === 'development') {
-						winston.info('Minified client-side libraries');
-					}
-					callback();
-				} else {
-					winston.error('Problem minifying client-side libraries, exiting.');
-					process.exit();
-				}
-			});
+			this.cache = minified.code;
+			callback();
 		}
 	};
 
diff --git a/src/routes/theme.js b/src/routes/meta.js
similarity index 85%
rename from src/routes/theme.js
rename to src/routes/meta.js
index e8a9052029..11a2d3d2fe 100644
--- a/src/routes/theme.js
+++ b/src/routes/meta.js
@@ -48,5 +48,15 @@ var path = require('path'),
 				}
 			});
 		});
+
+		app.get('/nodebb.min.js', function(req, res) {
+			if (meta.js.cache) {
+				res.type('text/javascript').send(meta.js.cache);
+			} else {
+				meta.js.minify(function() {
+					res.type('text/javascript').send(meta.js.cache);
+				});
+			}
+		});
 	};
 })(exports);
\ No newline at end of file
diff --git a/src/webserver.js b/src/webserver.js
index 50fcae9035..9999c1a22e 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -33,7 +33,7 @@ var path = require('path'),
 	userRoute = require('./routes/user'),
 	apiRoute = require('./routes/api'),
 	feedsRoute = require('./routes/feeds'),
-	themeRoute = require('./routes/theme');
+	metaRoute = require('./routes/meta');
 
 if(nconf.get('ssl')) {
 	server = require('https').createServer({
@@ -483,7 +483,7 @@ process.on('uncaughtException', function(err) {
 
 	app.namespace(nconf.get('relative_path'), function () {
 		auth.registerApp(app);
-		themeRoute.createRoutes(app);
+		metaRoute.createRoutes(app);
 		admin.createRoutes(app);
 		userRoute.createRoutes(app);
 		apiRoute.createRoutes(app);

From 8913de4ea89835893a108a004a47ae90a52f398e Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 16:55:05 -0500
Subject: [PATCH 062/193] dont add post-bar to each post

---
 public/src/forum/topic.js  | 5 +++--
 public/templates/topic.tpl | 2 ++
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index b314a2860c..68c6a12471 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -607,9 +607,10 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		socket.on('get_users_in_room', function(data) {
 
 			if(data && data.room.indexOf('topic') !== -1) {
-				var activeEl = $('.thread_active_users');
+				var activeEl = $('li.post-bar[data-index="0"] .thread_active_users');
 
 				function createUserIcon(uid, picture, userslug, username) {
+
 					if(!activeEl.find('[href="'+ RELATIVE_PATH +'/user/' + data.users[i].userslug + '"]').length) {
 						var userIcon = $('<img src="'+ picture +'"/>');
 
@@ -644,7 +645,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				var i=0;
 				// add self
 				for(i = 0; i<data.users.length; ++i) {
-					if(data.users[i].uid == app.uid) {
+					if(parseInt(data.users[i].uid, 10) === parseInt(app.uid, 10)) {
 						var icon = createUserIcon(data.users[i].uid, data.users[i].picture, data.users[i].userslug, data.users[i].username);
 						activeEl.prepend(icon);
 						data.users.splice(i, 1);
diff --git a/public/templates/topic.tpl b/public/templates/topic.tpl
index 2a60224fe6..9dcc7c8b3f 100644
--- a/public/templates/topic.tpl
+++ b/public/templates/topic.tpl
@@ -154,6 +154,7 @@
 				<div style="clear:both;"></div>
 			</li>
 
+			<!-- IF !posts.index -->
 			<li class="well post-bar" data-index="{posts.index}">
 				<div class="inline-block">
 					<small class="topic-stats">
@@ -190,6 +191,7 @@
 				</div>
 				<div style="clear:both;"></div>
 			</li>
+			<!-- ENDIF !posts.index -->
 		<!-- END posts -->
 	</ul>
 

From 309dcaee060ca2bf1ea2f55ca76360efbf97c1cb Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Tue, 25 Feb 2014 17:00:26 -0500
Subject: [PATCH 063/193] fixed issue where even dev mode would call the
 minifier for js

---
 src/meta.js | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/meta.js b/src/meta.js
index 7a41b162ff..cb8f43c3fb 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -266,9 +266,13 @@ var fs = require('fs'),
 
 				Meta.js.scripts = jsPaths.filter(function(path) { return path !== null });
 
-				callback(null, [
-					Meta.js.minFile
-				]);
+				if (process.env.NODE_ENV !== 'development') {
+					callback(null, [
+						Meta.js.minFile
+					]);
+				} else {
+					callback(null, scripts);
+				}
 			});
 		},
 		minify: function (callback) {

From f5d1ba42315eda29a1b57d8c6498f126202f8d5c Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Tue, 25 Feb 2014 17:07:27 -0500
Subject: [PATCH 064/193] closed #1113

---
 public/src/forum/admin/categories.js | 3 ++-
 public/src/forum/admin/themes.js     | 4 +++-
 public/src/modules/chat.js           | 1 +
 3 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js
index 7b4f761c52..8278d7423e 100644
--- a/public/src/forum/admin/categories.js
+++ b/public/src/forum/admin/categories.js
@@ -68,7 +68,8 @@ define(['uploader'], function(uploader) {
 		$('#entry-container').sortable({
 			stop: function( event, ui ) {
 				updateCategoryOrders();
-			}
+			},
+			distance: 10
 		});
 		$('.blockclass').each(function() {
 			$(this).val(this.getAttribute('data-value'));
diff --git a/public/src/forum/admin/themes.js b/public/src/forum/admin/themes.js
index ec474eadb7..e448fb072c 100644
--- a/public/src/forum/admin/themes.js
+++ b/public/src/forum/admin/themes.js
@@ -139,6 +139,7 @@ define(['forum/admin/settings'], function(Settings) {
 			helper: function(e) {
 				return $(e.target).parents('.panel').clone().addClass('block').width($(e.target.parentNode).width());
 			},
+			distance: 10,
 			connectToSortable: ".widget-area"
 		});
 
@@ -148,7 +149,8 @@ define(['forum/admin/settings'], function(Settings) {
 				target = target.attr('data-container-html') ? target : target.parents('[data-container-html]');
 
 				return target.clone().addClass('block').width(target.width()).css('opacity', '0.5');
-			}
+			},
+			distance: 10
 		});
 
 		function appendToggle(el) {
diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js
index cf99aa1d5c..230f4d4ad3 100644
--- a/public/src/modules/chat.js
+++ b/public/src/modules/chat.js
@@ -130,6 +130,7 @@ define(['taskbar', 'string'], function(taskbar, S) {
 					stop:function() {
 						chatModal.find('#chat-message-input').focus();
 					},
+					distance: 10,
 					handle: '.modal-header'
 				});
 

From 654535796928910c60985c06b37b25529b55b3bc Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Tue, 25 Feb 2014 17:15:46 -0500
Subject: [PATCH 065/193] fixing admin stylesheet + lavender minver

---
 package.json                      | 2 +-
 public/templates/admin/header.tpl | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index 7fff110bd6..221a6a781a 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
     "nodebb-widget-essentials": "~0.0",
     "nodebb-theme-vanilla": "~0.0.14",
     "nodebb-theme-cerulean": "~0.0.13",
-    "nodebb-theme-lavender": "~0.0.21",
+    "nodebb-theme-lavender": "~0.0.22",
     "less": "^1.6.3"
   },
   "optionalDependencies": {
diff --git a/public/templates/admin/header.tpl b/public/templates/admin/header.tpl
index 1916453ccb..6e04b32dc5 100644
--- a/public/templates/admin/header.tpl
+++ b/public/templates/admin/header.tpl
@@ -37,7 +37,7 @@
 	<script src="//code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
 	<script src="{relative_path}/src/utils.js"></script>
 
-	<link rel="stylesheet" type="text/css" href="{relative_path}/css/theme.css?{cache-buster}" />
+	<link rel="stylesheet" type="text/css" href="{relative_path}/stylesheet.css?{cache-buster}" />
 </head>
 
 <body class="admin">

From cc8ac2c0266c062378b8f2fc35f96ab646037735 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 17:21:30 -0500
Subject: [PATCH 066/193] get ips if admin or self

---
 src/routes/authentication.js |  2 ++
 src/routes/user.js           | 15 ++++++++++++---
 src/user.js                  |  4 ++++
 3 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/src/routes/authentication.js b/src/routes/authentication.js
index f72836caf2..bde803208b 100644
--- a/src/routes/authentication.js
+++ b/src/routes/authentication.js
@@ -105,7 +105,9 @@
 					req.login({
 						uid: userData.uid
 					}, function() {
+						console.log('TESTING', userData.uid);
 						if (userData.uid) {
+							console.log('FAIL?', userData.uid);
 							user.logIP(userData.uid, req.ip);
 						}
 
diff --git a/src/routes/user.js b/src/routes/user.js
index 45a0c7f20c..47434e5bf0 100644
--- a/src/routes/user.js
+++ b/src/routes/user.js
@@ -230,6 +230,7 @@ var fs = require('fs'),
 		app.get('/api/user/:userslug/posts', isAllowed, getUserPosts);
 		app.get('/api/user/uid/:uid', isAllowed, getUserData);
 		app.get('/api/user/:userslug', isAllowed, getUserProfile);
+
 		app.get('/api/users', isAllowed, getOnlineUsers);
 		app.get('/api/users/sort-posts', isAllowed, getUsersSortedByPosts);
 		app.get('/api/users/sort-reputation', isAllowed, getUsersSortedByReputation);
@@ -590,6 +591,9 @@ var fs = require('fs'),
 					},
 					followStats: function(next) {
 						user.getFollowStats(uid, next);
+					},
+					ips: function(next) {
+						user.getIPs(uid, next);
 					}
 				}, function(err, results) {
 					if(err || !results.userData) {
@@ -599,6 +603,7 @@ var fs = require('fs'),
 					var userData = results.userData;
 					var userSettings = results.userSettings;
 					var isAdmin = results.isAdmin;
+					var self = parseInt(callerUID, 10) === parseInt(userData.uid, 10);
 
 					userData.joindate = utils.toISOString(userData.joindate);
 					if(userData.lastonline) {
@@ -614,19 +619,23 @@ var fs = require('fs'),
 					}
 
 					function canSeeEmail() {
-						return isAdmin || parseInt(callerUID, 10) === parseInt(userData.uid, 10) || (userData.email && userSettings.showemail);
+						return ;
 					}
 
-					if (!canSeeEmail()) {
+					if (!(isAdmin || self || (userData.email && userSettings.showemail))) {
 						userData.email = "";
 					}
 
-					if (parseInt(callerUID, 10) === parseInt(userData.uid, 10) && !userSettings.showemail) {
+					if (self && !userSettings.showemail) {
 						userData.emailClass = "";
 					} else {
 						userData.emailClass = "hide";
 					}
 
+					if (isAdmin || self) {
+						userData.ips = results.ips;
+					}
+
 					userData.websiteName = userData.website.replace('http://', '').replace('https://', '');
 					userData.banned = parseInt(userData.banned, 10) === 1;
 					userData.uid = userData.uid;
diff --git a/src/user.js b/src/user.js
index ac48c4e7f0..9e8e4f7f49 100644
--- a/src/user.js
+++ b/src/user.js
@@ -968,6 +968,10 @@ var bcrypt = require('bcryptjs'),
 		db.sortedSetAdd('uid:' + uid + ':ip', +new Date(), ip || 'Unknown');
 	};
 
+	User.getIPs = function(uid, callback) {
+		db.getSortedSetRevRange('uid:' + uid + ':ip', 0, 5, callback);
+	};
+
 	User.email = {
 		verify: function(uid, email) {
 			if (!plugins.hasListeners('action:email.send')) {

From de3bc84fde76e2c52296147a6fc226091dd9c5bc Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 17:23:13 -0500
Subject: [PATCH 067/193] removed logs

---
 src/routes/authentication.js | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/routes/authentication.js b/src/routes/authentication.js
index bde803208b..f72836caf2 100644
--- a/src/routes/authentication.js
+++ b/src/routes/authentication.js
@@ -105,9 +105,7 @@
 					req.login({
 						uid: userData.uid
 					}, function() {
-						console.log('TESTING', userData.uid);
 						if (userData.uid) {
-							console.log('FAIL?', userData.uid);
 							user.logIP(userData.uid, req.ip);
 						}
 

From 1ba3acfd032aa149c754ac6834d3e2675d2d3561 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 17:34:42 -0500
Subject: [PATCH 068/193] display ips to admins/users on profile

---
 public/language/en_GB/global.json |  1 +
 public/templates/account.tpl      | 12 ++++++++++++
 src/routes/user.js                |  2 +-
 src/user.js                       | 12 ++++++++++--
 4 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/public/language/en_GB/global.json b/public/language/en_GB/global.json
index b1dff9ec46..b41f69b915 100644
--- a/public/language/en_GB/global.json
+++ b/public/language/en_GB/global.json
@@ -65,6 +65,7 @@
 	"in": "in",
 
 	"recentposts": "Recent Posts",
+	"recentips": "Recently Logged In IPs",
 
 	"online": "Online",
 	"away": "Away",
diff --git a/public/templates/account.tpl b/public/templates/account.tpl
index dac12502d9..d099d68767 100644
--- a/public/templates/account.tpl
+++ b/public/templates/account.tpl
@@ -108,6 +108,18 @@
 				</div>
 			</div>
 
+			<!-- IF ips.length -->
+			<div class="panel panel-default">
+				<div class="panel-heading">
+					<h3 class="panel-title">[[global:recentips]]</h3>
+				</div>
+				<div class="panel-body">
+				<!-- BEGIN ips -->
+					{ips.ip}
+				<!-- END ips -->
+				</div>
+			</div>
+			<!-- ENDIF ips.length -->
 
 		</div>
 
diff --git a/src/routes/user.js b/src/routes/user.js
index 47434e5bf0..136473ce1b 100644
--- a/src/routes/user.js
+++ b/src/routes/user.js
@@ -593,7 +593,7 @@ var fs = require('fs'),
 						user.getFollowStats(uid, next);
 					},
 					ips: function(next) {
-						user.getIPs(uid, next);
+						user.getIPs(uid, 4, next);
 					}
 				}, function(err, results) {
 					if(err || !results.userData) {
diff --git a/src/user.js b/src/user.js
index 9e8e4f7f49..ae14774e82 100644
--- a/src/user.js
+++ b/src/user.js
@@ -968,8 +968,16 @@ var bcrypt = require('bcryptjs'),
 		db.sortedSetAdd('uid:' + uid + ':ip', +new Date(), ip || 'Unknown');
 	};
 
-	User.getIPs = function(uid, callback) {
-		db.getSortedSetRevRange('uid:' + uid + ':ip', 0, 5, callback);
+	User.getIPs = function(uid, end, callback) {
+		db.getSortedSetRevRange('uid:' + uid + ':ip', 0, end, function(err, ips) {
+			if(err) {
+				return callback(err);
+			}
+
+			callback(null, ips.map(function(ip) {
+				return {ip:ip};
+			}));
+		});
 	};
 
 	User.email = {

From 101a6ab81270de0652adb4771c42145ae42af261 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Tue, 25 Feb 2014 18:14:06 -0500
Subject: [PATCH 069/193] minifying css

---
 src/routes/meta.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/routes/meta.js b/src/routes/meta.js
index 11a2d3d2fe..ed8ac3a559 100644
--- a/src/routes/meta.js
+++ b/src/routes/meta.js
@@ -39,7 +39,9 @@ var path = require('path'),
 							return;
 						}
 
-						meta.css.cache = tree.toCSS();
+						meta.css.cache = tree.toCSS({
+							compress: true
+						});
 						res.type('text/css').send(200, meta.css.cache);
 					});
 				} else {

From 36bc2967c27c2ecb845ccb1d5ad2f8dd1c48ad9d Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 25 Feb 2014 22:05:07 -0500
Subject: [PATCH 070/193] fix highlight on vanilla, dont replaceState if url
 didnt change

---
 public/src/forum/topic.js  | 25 +++++++++++++++----------
 public/templates/topic.tpl |  2 +-
 2 files changed, 16 insertions(+), 11 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 68c6a12471..f10839ec0e 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -1,7 +1,8 @@
 define(['composer', 'forum/pagination'], function(composer, pagination) {
 	var	Topic = {},
 		infiniteLoaderActive = false,
-		scrollingToPost = false;
+		scrollingToPost = false,
+		currentUrl = '';
 
 	function showBottomPostBar() {
 		if($('#post-container .post-row').length > 1 || !$('#post-container li[data-index="0"]').length) {
@@ -1020,6 +1021,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 
 		$('.posts > .post-row').each(function() {
 			var el = $(this);
+
 			if (elementInView(el)) {
 				var index = parseInt(el.attr('data-index'), 10) + 1;
 				if(index === 0) {
@@ -1028,13 +1030,16 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					localStorage.setItem("topic:" + templates.get('topic_id') + ":bookmark", el.attr('data-pid'));
 
 					if (!scrollingToPost) {
-						if (history.replaceState) {
-							history.replaceState({
-								url: window.location.pathname.slice(1) + '#' + el.attr('data-pid')
-							}, null,
-								window.location.protocol + '//' + window.location.host + window.location.pathname + '#' + el.attr('data-pid'));
-						} else {
-							location.hash = '#' + el.attr('data-pid');
+						var newUrl = window.location.protocol + '//' + window.location.host + window.location.pathname + '#' + el.attr('data-pid')
+						if (newUrl !== currentUrl) {
+							if (history.replaceState) {
+								history.replaceState({
+									url: window.location.pathname.slice(1) + '#' + el.attr('data-pid')
+								}, null, newUrl);
+							} else {
+								location.hash = '#' + el.attr('data-pid');
+							}
+							currentUrl = newUrl;
 						}
 					}
 				}
@@ -1105,9 +1110,9 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				}, duration !== undefined ? duration : 400, function() {
 					scrollingToPost = false;
 					if (highlight) {
-						scrollTo.parent().addClass('highlight');
+						scrollTo.parent().find('.topic-item').addClass('highlight');
 						setTimeout(function() {
-							scrollTo.parent().removeClass('highlight');
+							scrollTo.parent().find('.topic-item').removeClass('highlight');
 						}, 5000);
 					}
 				});
diff --git a/public/templates/topic.tpl b/public/templates/topic.tpl
index 9dcc7c8b3f..ef0d794d88 100644
--- a/public/templates/topic.tpl
+++ b/public/templates/topic.tpl
@@ -38,7 +38,7 @@
 					</a>
 				</div>
 
-				<div class="col-md-11 panel panel-default post-block">
+				<div class="col-md-11 panel panel-default post-block topic-item">
 
 					<a class="main-post avatar" href="{relative_path}/user/{posts.userslug}">
 						<img itemprop="image" src="{posts.picture}" align="left" class="img-thumbnail" width=150 height=150 />

From 3f7e458866f05a8014ff2adcf47d2a3a02558e14 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Tue, 25 Feb 2014 23:06:21 -0500
Subject: [PATCH 071/193] fixed #1127

---
 src/routes/meta.js | 33 +++++++++++++--------------------
 1 file changed, 13 insertions(+), 20 deletions(-)

diff --git a/src/routes/meta.js b/src/routes/meta.js
index ed8ac3a559..0900ca07d8 100644
--- a/src/routes/meta.js
+++ b/src/routes/meta.js
@@ -16,7 +16,7 @@ var path = require('path'),
 
 			db.getObjectFields('config', ['theme:type', 'theme:id'], function(err, themeData) {
 				var themeId = (themeData['theme:id'] || 'nodebb-theme-vanilla'),
-					baseThemePath = path.join(nconf.get('themes_path'), themeId),
+					baseThemePath = path.join(nconf.get('themes_path'), (themeData['theme:type'] && themeData['theme:type'] === 'local' ? themeId : 'nodebb-theme-vanilla')),
 					paths = [baseThemePath, path.join(__dirname, '../../node_modules')],
 					source = '@import "./theme";',
 					x, numLESS;
@@ -26,28 +26,21 @@ var path = require('path'),
 					source += '\n@import "./' + plugins.lessFiles[x] + '";';
 				}
 
-				// Detect if a theme has been selected, and handle appropriately
-				if (!themeData['theme:type'] || themeData['theme:type'] === 'local') {
-					// Local theme
-					var	parser = new (less.Parser)({
-							paths: paths
-						});
+				var	parser = new (less.Parser)({
+						paths: paths
+					});
 
-					parser.parse(source, function(err, tree) {
-						if (err) {
-							res.send(500, err.message);
-							return;
-						}
+				parser.parse(source, function(err, tree) {
+					if (err) {
+						res.send(500, err.message);
+						return;
+					}
 
-						meta.css.cache = tree.toCSS({
-							compress: true
-						});
-						res.type('text/css').send(200, meta.css.cache);
+					meta.css.cache = tree.toCSS({
+						compress: true
 					});
-				} else {
-					// Bootswatch theme not supported yet
-					res.send(500, 'Give me time!');
-				}
+					res.type('text/css').send(200, meta.css.cache);
+				});
 			});
 		});
 

From 8a2266816d3b20eadf5b197b99cf389e03a7b996 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 26 Feb 2014 13:14:48 -0500
Subject: [PATCH 072/193] make plugin names strong

---
 public/templates/admin/plugins.tpl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/templates/admin/plugins.tpl b/public/templates/admin/plugins.tpl
index 3aa7e5f5e1..dd69ffa721 100644
--- a/public/templates/admin/plugins.tpl
+++ b/public/templates/admin/plugins.tpl
@@ -12,7 +12,7 @@
 <ul class="plugins">
 	<!-- BEGIN plugins -->
 	<li data-plugin-id="{plugins.id}">
-		<h2>{plugins.name}</h2>
+		<h2><strong>{plugins.name}</strong></h2>
 		<div class="pull-right">
 			<button data-action="toggleActive" class="btn <!-- IF plugins.active -->btn-warning<!-- ELSE -->btn-success<!-- ENDIF plugins.active -->">{plugins.activeText}</button>
 		</div>

From 7b46d66e681f79f99b23dd92985864268a2baca2 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Wed, 26 Feb 2014 13:42:39 -0500
Subject: [PATCH 073/193] having the loader play nicely with supervisor --
 ./nodebb watch can now hit the restart button

---
 loader.js | 11 +++++++++--
 nodebb    |  2 +-
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/loader.js b/loader.js
index 63027b1603..2263955ef9 100644
--- a/loader.js
+++ b/loader.js
@@ -1,6 +1,6 @@
 var	fork = require('child_process').fork,
 	start = function() {
-		var	nbb = fork('./app', process.argv.slice(2), {
+		nbb = fork('./app', process.argv.slice(2), {
 				env: {
 					'NODE_ENV': process.env.NODE_ENV
 				}
@@ -14,6 +14,13 @@ var	fork = require('child_process').fork,
 				nbb.kill();
 			}
 		});
-	}
+	},
+	stop = function() {
+		nbb.kill();
+	},
+	nbb;
+
+process.on('SIGINT', stop);
+process.on('SIGTERM', stop);
 
 start();
\ No newline at end of file
diff --git a/nodebb b/nodebb
index cdefacd524..0ea67b0a69 100755
--- a/nodebb
+++ b/nodebb
@@ -37,7 +37,7 @@ case "$1" in
 		echo "Launching NodeBB in \"development\" mode."
 		echo "To run the production build of NodeBB, please use \"forever\"."
 		echo "More Information: https://github.com/designcreateplay/NodeBB/wiki/How-to-run-NodeBB"
-		NODE_ENV=development supervisor -q --extensions 'node|js|tpl' -- app "$@"
+		NODE_ENV=development supervisor -q --extensions 'node|js|tpl' -- loader "$@"
 		;;
 
 	*)

From c38e328377901027b67e8df805a6cbdc829bc15e Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Wed, 26 Feb 2014 14:50:44 -0500
Subject: [PATCH 074/193] language files for fi, it, zh_CN, pl, hu

---
 public/language/fi/category.json         |  6 +-
 public/language/fi/footer.json           |  2 +-
 public/language/fi/global.json           | 68 ++++++++---------
 public/language/fi/login.json            |  8 +-
 public/language/fi/modules.json          |  6 +-
 public/language/fi/notifications.json    |  6 +-
 public/language/fi/pages.json            | 22 +++---
 public/language/fi/recent.json           |  4 +-
 public/language/fi/register.json         | 20 ++---
 public/language/fi/reset_password.json   | 10 +--
 public/language/fi/topic.json            | 94 +++++++++++++-----------
 public/language/fi/unread.json           |  4 +-
 public/language/fi/user.json             | 48 ++++++------
 public/language/fi/users.json            |  6 +-
 public/language/hu/topic.json            | 12 ++-
 public/language/it/category.json         |  8 +-
 public/language/it/footer.json           |  2 +-
 public/language/it/global.json           | 56 +++++++-------
 public/language/it/language.json         |  2 +-
 public/language/it/login.json            |  8 +-
 public/language/it/modules.json          |  4 +-
 public/language/it/notifications.json    |  6 +-
 public/language/it/pages.json            | 20 ++---
 public/language/it/recent.json           |  4 +-
 public/language/it/register.json         |  4 +-
 public/language/it/reset_password.json   |  4 +-
 public/language/it/topic.json            | 86 ++++++++++++----------
 public/language/it/unread.json           |  2 +-
 public/language/it/user.json             | 26 +++----
 public/language/it/users.json            |  2 +-
 public/language/pl/global.json           |  2 +
 public/language/pl/pages.json            |  2 +-
 public/language/pl/topic.json            | 10 ++-
 public/language/zh_CN/global.json        | 60 +++++++--------
 public/language/zh_CN/notifications.json |  8 +-
 public/language/zh_CN/pages.json         | 22 +++---
 public/language/zh_CN/recent.json        |  4 +-
 public/language/zh_CN/register.json      |  6 +-
 public/language/zh_CN/topic.json         | 68 +++++++++--------
 public/language/zh_CN/unread.json        |  2 +-
 40 files changed, 391 insertions(+), 343 deletions(-)

diff --git a/public/language/fi/category.json b/public/language/fi/category.json
index 6671ce4b16..3512fae9d5 100644
--- a/public/language/fi/category.json
+++ b/public/language/fi/category.json
@@ -1,12 +1,12 @@
 {
-    "new_topic_button": "Aloita uusi keskustelu.",
-    "no_topics": "<strong>Tällä aihealueella ei ole yhtään viestiketjua.</strong><br />Miksi et aloittaisi yhtä?",
+    "new_topic_button": "Uusi aihe",
+    "no_topics": "<strong>Tällä aihealueella ei ole yhtään aihetta.</strong><br />Miksi et aloittaisi uutta?",
     "sidebar.recent_replies": "Viimeisimmät vastaukset",
     "sidebar.active_participants": "Aktiiviset keskustelijat",
     "sidebar.moderators": "Moderaattorit",
     "posts": "viestit",
     "views": "katsottu",
-    "posted": "lähetetty",
+    "posted": "kirjoitettu",
     "browsing": "selaamassa",
     "no_replies": "Kukaan ei ole vastannut",
     "replied": "vastasi",
diff --git a/public/language/fi/footer.json b/public/language/fi/footer.json
index 422d32464a..46ffa8334d 100644
--- a/public/language/fi/footer.json
+++ b/public/language/fi/footer.json
@@ -1,7 +1,7 @@
 {
     "stats.online": "Online",
     "stats.users": "Käyttäjää",
-    "stats.topics": "Viestiketjua",
+    "stats.topics": "Aihetta",
     "stats.posts": "Viestiä",
     "success": "onnistunut"
 }
\ No newline at end of file
diff --git a/public/language/fi/global.json b/public/language/fi/global.json
index b276cf767e..b5d7aab994 100644
--- a/public/language/fi/global.json
+++ b/public/language/fi/global.json
@@ -1,56 +1,58 @@
 {
     "home": "Etusivu",
-    "search": "Etsi",
+    "search": "Hae",
     "buttons.close": "Sulje",
     "403.title": "Pääsy kielletty",
-    "403.message": "Olet päätynyt sivulle jolle sinulla ei ole tarvittavia oikeuksia. Ehkäpä sinun tulisi <a href='/login'>kirjaudu sisään</a>?",
-    "404.title": "Sivua ei löydy",
-    "404.message": "Olet päätynyt sivulle jota ei ole olemassa. Palaa <a href='/'>etusivulle</a>.",
+    "403.message": "Olet päätynyt sivulle, johon sinulla ei ole tarvittavia oikeuksia. Sinun pitäisi kai <a href='/login'>kirjautua sisään</a>.",
+    "404.title": "Ei löydy",
+    "404.message": "Olet päätynyt sivulle, jota ei ole olemassa. Palaa <a href='/'>etusivulle</a>.",
     "500.title": "Sisäinen virhe.",
     "500.message": "Oho! Jotain meni pieleen!",
     "register": "Rekisteröidy",
     "login": "Kirjaudu",
-    "welcome_back": "Welcome Back ",
-    "you_have_successfully_logged_in": "You have successfully logged in",
+    "please_log_in": "Kirjaudu, ole hyvä",
+    "posting_restriction_info": "Kirjoittaminen on tällä hetkellä rajattu vain rekisteröityneille käyttäjille. Napsauta tätä kirjautuaksesi.",
+    "welcome_back": "Tervetuloa takaisin",
+    "you_have_successfully_logged_in": "Olet onnistuneesti kirjautunut sisään",
     "logout": "Kirjaudu ulos",
-    "logout.title": "Olet nyt kirjaunut ulos.",
+    "logout.title": "Olet nyt kirjautunut ulos.",
     "logout.message": "Olet onnistuneesti kirjautunut ulos NodeBB:stä",
     "save_changes": "Tallenna muutokset",
     "close": "Sulje",
-    "pagination": "Pagination",
-    "header.admin": "Admin",
+    "pagination": "Sivutus",
+    "header.admin": "Ylläpitäjä",
     "header.recent": "Viimeisimmät",
     "header.unread": "Lukemattomat",
-    "header.popular": "Popular",
+    "header.popular": "Suositut",
     "header.users": "Käyttäjät",
-    "header.chats": "Chats",
-    "header.notifications": "Notifications",
-    "header.search": "Etsi",
+    "header.chats": "Keskustelut",
+    "header.notifications": "Ilmoitukset",
+    "header.search": "Hae",
     "header.profile": "Profiili",
-    "notifications.loading": "Ladataan ilmoituksia.",
+    "notifications.loading": "Ladataan ilmoituksia",
     "chats.loading": "Ladataan keskusteluja",
-    "motd.welcome": "Tervetuloa NodeBB:n, tulevaisuuden keskustelualustalle.",
+    "motd.welcome": "Tervetuloa NodeBB:hen, tulevaisuuden keskustelualustalle.",
     "motd.get": "Hanki NodeBB",
-    "motd.fork": "Fork",
+    "motd.fork": "Forkkaa",
     "motd.like": "Tykkää",
     "motd.follow": "Seuraa",
-    "previouspage": "Previous Page",
-    "nextpage": "Next Page",
-    "alert.success": "Success",
-    "alert.error": "Error",
-    "alert.banned": "Banned",
-    "alert.banned.message": "You are banned you will be logged out!",
-    "alert.unfollow": "You are no longer following %1!",
-    "alert.follow": "You are now following %1!",
-    "posts": "Posts",
-    "views": "Views",
-    "posted": "posted",
-    "in": "in",
-    "recentposts": "Recent Posts",
+    "previouspage": "Edellinen sivu",
+    "nextpage": "Seuraava sivu",
+    "alert.success": "Onnistui",
+    "alert.error": "Virhe",
+    "alert.banned": "Estetty",
+    "alert.banned.message": "Sinut on estetty ja kirjaudut ulos!",
+    "alert.unfollow": "Et seuraa enää %1!",
+    "alert.follow": "Seuraat nyt %1!",
+    "posts": "Viestit",
+    "views": "Katsottu",
+    "posted": "kirjoitettu",
+    "in": "alueelle",
+    "recentposts": "Viimeisimmät viestit",
     "online": "Online",
-    "away": "Away",
-    "dnd": "Do not Disturb",
-    "invisible": "Invisible",
+    "away": "Poissa",
+    "dnd": "Älä häiritse",
+    "invisible": "Näkymätön",
     "offline": "Offline",
-    "privacy": "Privacy"
+    "privacy": "Yksityisyys"
 }
\ No newline at end of file
diff --git a/public/language/fi/login.json b/public/language/fi/login.json
index 2d43168d30..5d1a2eb3cd 100644
--- a/public/language/fi/login.json
+++ b/public/language/fi/login.json
@@ -1,10 +1,10 @@
 {
     "login": "Kirjaudu sisään",
-    "username": "Käyttäjän nimi",
+    "username": "Käyttäjänimi",
     "password": "Salasana",
     "remember_me": "Muista minut?",
-    "forgot_password": "Unohtuiko salasana?",
-    "alternative_logins": "Vaihtoehtoiset sisäänkirjaantumistavat",
-    "failed_login_attempt": "Sisäänkirjaantuminen epäonnistui, ole hyvä ja yritä uudestaan.",
+    "forgot_password": "Unohditko salasanasi?",
+    "alternative_logins": "Vaihtoehtoiset kirjautumistavat",
+    "failed_login_attempt": "Kirjautumisyritys epäonnistui, ole hyvä ja yritä uudestaan.",
     "login_successful": "Olet onnistuneesti kirjautunut sisään!"
 }
\ No newline at end of file
diff --git a/public/language/fi/modules.json b/public/language/fi/modules.json
index 612822916a..f1c0a0ee82 100644
--- a/public/language/fi/modules.json
+++ b/public/language/fi/modules.json
@@ -1,6 +1,6 @@
 {
-    "chat.chatting_with": "Juttele <span id=\"chat-with-name\"></span> kanssa",
-    "chat.placeholder": "kirjoita viestisi tähän, paina enter lähettääksesi",
+    "chat.chatting_with": "Keskustele käyttäjän <span id=\"chat-with-name\"></span> kanssa",
+    "chat.placeholder": "kirjoita viestisi tähän ja paina enter lähettääksesi",
     "chat.send": "Lähetä",
-    "chat.no_active": "Sinulla ei ole aktiivisiä keskusteluita."
+    "chat.no_active": "Sinulla ei ole aktiivisia keskusteluita."
 }
\ No newline at end of file
diff --git a/public/language/fi/notifications.json b/public/language/fi/notifications.json
index 469763bde0..9d7f5fbdfc 100644
--- a/public/language/fi/notifications.json
+++ b/public/language/fi/notifications.json
@@ -1,8 +1,8 @@
 {
     "title": "Ilmoitukset",
-    "no_notifs": "You have no new notifications",
-    "see_all": "See all Notifications",
-    "back_to_home": "Takaisin NodeBB:n",
+    "no_notifs": "Sinulla ei ole uusia ilmoituksia",
+    "see_all": "Katso kaikki ilmoitukset",
+    "back_to_home": "Takaisin NodeBB:hen",
     "outgoing_link": "Ulkopuolinen linkki",
     "outgoing_link_message": "Olet nyt poistumassa",
     "continue_to": "Jatka",
diff --git a/public/language/fi/pages.json b/public/language/fi/pages.json
index d60e0a0a9b..49a7dca36a 100644
--- a/public/language/fi/pages.json
+++ b/public/language/fi/pages.json
@@ -1,13 +1,13 @@
 {
-    "home": "Home",
-    "unread": "Unread Topics",
-    "popular": "Popular Topics",
-    "recent": "Recent Topics",
-    "users": "Registered Users",
-    "notifications": "Notifications",
-    "user.edit": "Editing \"%1\"",
-    "user.following": "People %1 Follows",
-    "user.followers": "People who Follow %1",
-    "user.favourites": "%1's Favourite Posts",
-    "user.settings": "User Settings"
+    "home": "Etusivu",
+    "unread": "Lukemattomat aiheet",
+    "popular": "Suositut aiheet",
+    "recent": "Viimeisimmät aiheet",
+    "users": "Rekisteröityneet käyttäjät",
+    "notifications": "Ilmoitukset",
+    "user.edit": "Muokataan \"%1\"",
+    "user.following": "Käyttäjät, joita %1 seuraa",
+    "user.followers": "Käyttäjät, jotka seuraavat käyttäjää %1",
+    "user.favourites": "Käyttäjän %1 suosikkiviestit",
+    "user.settings": "Käyttäjän asetukset"
 }
\ No newline at end of file
diff --git a/public/language/fi/recent.json b/public/language/fi/recent.json
index 314fadb2e1..585a326219 100644
--- a/public/language/fi/recent.json
+++ b/public/language/fi/recent.json
@@ -1,7 +1,7 @@
 {
-    "title": "Recent",
+    "title": "Viimeisimmät",
     "day": "Päivä",
     "week": "Viikko",
     "month": "Kuukausi",
-    "no_recent_topics": "There are no recent topics."
+    "no_recent_topics": "Ei viimeisimpiä aiheita."
 }
\ No newline at end of file
diff --git a/public/language/fi/register.json b/public/language/fi/register.json
index ef0017e5a1..a07f0d311e 100644
--- a/public/language/fi/register.json
+++ b/public/language/fi/register.json
@@ -1,18 +1,18 @@
 {
     "register": "Rekisteröidy",
-    "help.email": "Oletuksena sähköposti osoitettasi ei näytetä muille .",
-    "help.username_restrictions": "Yksilöllinen käyttäjänimi, pitää olla %1 - %2 merkkiä pitkä. Toiset voivat mainita sinut @<span id='yourUsername'>käyttäjänimi</span>.",
+    "help.email": "Oletuksena sähköpostiosoitettasi ei näytetä muille.",
+    "help.username_restrictions": "Yksilöllisen käyttäjätunnuksen pitää olla %1-%2 merkkiä pitkä. Toiset voivat mainita sinut @<span id='yourUsername'>username</span>.",
     "help.minimum_password_length": "Salasanasi pitää olla vähintään %1 merkin mittainen.",
-    "email_address": "Sähköposti",
-    "email_address_placeholder": "Anna sähköpostiosoitteesi",
-    "username": "Käyttäjänimi",
-    "username_placeholder": "Syötä käyttäjänimesi",
+    "email_address": "Sähköpostiosoite",
+    "email_address_placeholder": "Syötä sähköpostiosoitteesi",
+    "username": "Käyttäjätunnus",
+    "username_placeholder": "Syötä käyttäjätunnuksesi",
     "password": "Salasana",
     "password_placeholder": "Syötä salasanasi",
-    "confirm_password": "Vahvista salasana",
-    "confirm_password_placeholder": "Vahvista salasana",
+    "confirm_password": "Vahvista salasanasi",
+    "confirm_password_placeholder": "Vahvista salasanasi",
     "register_now_button": "Rekisteröidy nyt",
     "alternative_registration": "Vaihtoehtoiset rekisteröitymistavat",
-    "terms_of_use": "Terms of Use",
-    "agree_to_terms_of_use": "I agree to the Terms of Use"
+    "terms_of_use": "Käyttöehdot",
+    "agree_to_terms_of_use": "Hyväksyn käyttöehdot"
 }
\ No newline at end of file
diff --git a/public/language/fi/reset_password.json b/public/language/fi/reset_password.json
index 114bbab8d5..864817f60d 100644
--- a/public/language/fi/reset_password.json
+++ b/public/language/fi/reset_password.json
@@ -2,12 +2,12 @@
     "reset_password": "Palauta salasana",
     "update_password": "Päivitä salasana",
     "password_changed.title": "Salasana muutettu",
-    "password_changed.message": "<p>Salasana palautettu onnistunesti, ole hyvä ja <a href=\"/login\">kirjaudu sisään uudestaan.</a>.",
+    "password_changed.message": "<p>Salasanasi on palautettu onnistuneesti, ole hyvä ja <a href=\"/login\">kirjaudu uudestaan</a>.",
     "wrong_reset_code.title": "Väärä palautuskoodi",
-    "wrong_reset_code.message": "Annettu palautuskoodi oli väärä. Ole hyvä yritä uudelleen, tai <a href=\"/reset\">pyydä uutta palautuskoodia</a>.",
+    "wrong_reset_code.message": "Annettu palautuskoodi oli väärä. Ole hyvä ja yritä uudelleen tai <a href=\"/reset\">pyydä uutta palautuskoodia</a>.",
     "new_password": "Uusi salasana",
     "repeat_password": "Vahvista salasana",
-    "enter_email": "Syötä <strong>sähköpostiosoitteesi</strong> niin me lähetämänne Sinulle ohjeet kuinka voit palauttaa käyttäjätilisi.",
-    "password_reset_sent": "Salasanan palautus lähetetty",
-    "invalid_email": "Väärä sähköpostiosoite / Sähköpostiosoitetta ei ole!"
+    "enter_email": "Syötä <strong>sähköpostiosoitteesi</strong>, niin me lähetämme sinulle sähköpostilla ohjeet käyttäjätilisi palauttamiseksi.",
+    "password_reset_sent": "Salasanan palautuskoodi lähetetty",
+    "invalid_email": "Virheellinen sähköpostiosoite / Sähköpostiosoitetta ei ole olemassa!"
 }
\ No newline at end of file
diff --git a/public/language/fi/topic.json b/public/language/fi/topic.json
index 2cacaba33d..c7dcff3a0f 100644
--- a/public/language/fi/topic.json
+++ b/public/language/fi/topic.json
@@ -1,65 +1,73 @@
 {
-    "topic": "Keskustelu",
-    "topics": "Keskustelut",
-    "no_topics_found": "Keskusteluja ei löytynyt!",
-    "no_posts_found": "No posts found!",
+    "topic": "Aihe",
+    "topics": "Aiheet",
+    "no_topics_found": "Aiheita ei löytynyt!",
+    "no_posts_found": "Viestejä ei löytynyt!",
     "profile": "Profiili",
-    "posted_by": "Posted by",
-    "chat": "Juttele",
-    "notify_me": "Ilmoita uusista viesteistä tässä keskustelussa",
+    "posted_by": "Kirjoittanut",
+    "chat": "Keskustele",
+    "notify_me": "Ilmoita, kun tähän keskusteluun tulee uusia viestejä",
     "quote": "Lainaa",
     "reply": "Vastaa",
     "edit": "Muokkaa",
     "delete": "Poista",
     "move": "Siirrä",
     "fork": "Haaroita",
-    "banned": "banned",
-    "link": "Linkkaa",
+    "banned": "estetty",
+    "link": "Linkitä",
     "share": "Jaa",
     "tools": "Työkalut",
-    "flag": "Flag",
-    "flag_title": "Flag this post for moderation",
-    "deleted_message": "This thread has been deleted. Only users with thread management privileges can see it.",
-    "watch": "Watch",
-    "share_this_post": "Share this Post",
-    "thread_tools.title": "Ketjun työkalut",
-    "thread_tools.markAsUnreadForAll": "Merkitse luetuiksi",
-    "thread_tools.pin": "Pin Topic",
-    "thread_tools.unpin": "Unpin Topic",
-    "thread_tools.lock": "Lock Topic",
-    "thread_tools.unlock": "Unlock Topic",
-    "thread_tools.move": "Move Topic",
-    "thread_tools.fork": "Fork Topic",
-    "thread_tools.delete": "Delete Topic",
-    "thread_tools.restore": "Restore Topic",
+    "flag": "Ilmianna",
+    "flag_title": "Ilmianna tämä viesti moderaattoreille",
+    "deleted_message": "Tämä viestiketju on poistettu. Vain käyttäjät, joilla on viestiketjujen hallintaoikeudet, voivat nähdä sen.",
+    "following_topic.title": "Seurataan aihetta",
+    "following_topic.message": "Saat nyt ilmoituksen, kun joku kirjoittaa tähän aiheeseen.",
+    "not_following_topic.title": "Et seuraa aihetta",
+    "not_following_topic.message": "Et saa enää ilmoituksia tästä aiheesta.",
+    "login_to_subscribe": "Ole hyvä ja rekisteröidy tai kirjaudu sisään tilataksesi tämän aiheen",
+    "watch": "Tarkkaile",
+    "share_this_post": "Jaa tämä viesti",
+    "thread_tools.title": "Aiheen työkalut",
+    "thread_tools.markAsUnreadForAll": "Merkitse lukemattomaksi",
+    "thread_tools.pin": "Kiinnitä aihe",
+    "thread_tools.unpin": "Poista aiheen kiinnitys",
+    "thread_tools.lock": "Lukitse aihe",
+    "thread_tools.unlock": "Poista aiheen lukitus",
+    "thread_tools.move": "Siirrä aihe",
+    "thread_tools.fork": "Haaroita aihe",
+    "thread_tools.delete": "Poista aihe",
+    "thread_tools.restore": "Palauta aihe",
     "load_categories": "Ladataan aihealueita",
-    "disabled_categories_note": "Käytöstä poistetut aihealueetta ovat harmaina",
+    "disabled_categories_note": "Käytöstä poistetut aihealueet ovat harmaina",
     "confirm_move": "Siirrä",
     "confirm_fork": "Haaroita",
-    "favourite": "Suosikki",
+    "favourite": "Lisää suosikiksi",
     "favourites": "Suosikit",
-    "favourites.not_logged_in.title": "Ei kirjaantuneena sisään",
-    "favourites.not_logged_in.message": "Kirjaudu sisään jotta voit lisätä tämän viestin suosikkeihisi.",
-    "favourites.has_no_favourites": "Sinulla ei ole yhtään suosikkiviestiä.",
-    "vote.not_logged_in.title": "Not Logged In",
-    "vote.not_logged_in.message": "Please log in in order to vote",
-    "vote.cant_vote_self.title": "Invalid Vote",
-    "vote.cant_vote_self.message": "You cannot vote for your own post",
+    "favourites.not_logged_in.title": "Et ole kirjautunut",
+    "favourites.not_logged_in.message": "Kirjaudu sisään, jotta voit lisätä tämän viestin suosikkeihisi",
+    "favourites.has_no_favourites": "Sinulla ei ole yhtään suosikkiviestiä. Lisää joitakin viestejä suosikeiksi nähdäksesi ne täällä!",
+    "vote.not_logged_in.title": "Et ole kirjautunut",
+    "vote.not_logged_in.message": "Kirjaudu sisään äänestääksesi",
+    "vote.cant_vote_self.title": "Virheellinen ääni",
+    "vote.cant_vote_self.message": "Et voi äänestää omaa viestiäsi",
     "loading_more_posts": "Ladataan lisää viestejä",
-    "move_topic": "Siirrä keskustelu",
+    "move_topic": "Siirrä aihe",
     "move_post": "Siirrä viesti",
     "fork_topic": "Haaroita keskustelu",
-    "topic_will_be_moved_to": "Tämä keskustelu siirretään aihealueelle ",
-    "fork_topic_instruction": "Klikkaa viestejä jotka haluat haaroittaa",
+    "topic_will_be_moved_to": "Tämä keskustelu siirretään aihealueelle",
+    "fork_topic_instruction": "Napsauta viestejä, jotka haluat haaroittaa",
     "fork_no_pids": "Ei valittuja viestejä!",
     "fork_success": "Keskustelu haaroitettu onnistuneesti!",
     "reputation": "Maine",
     "posts": "Viestejä",
-    "composer.title_placeholder": "Enter your topic title here...",
-    "composer.write": "Write",
-    "composer.preview": "Preview",
-    "composer.discard": "Discard",
-    "composer.submit": "Submit",
-    "composer.replying_to": "Replying to",
-    "composer.new_topic": "New Topic"
+    "composer.title_placeholder": "Syötä aiheesi otsikko tähän...",
+    "composer.write": "Kirjoita",
+    "composer.preview": "Esikatsele",
+    "composer.discard": "Hylkää",
+    "composer.submit": "Lähetä",
+    "composer.replying_to": "Vastataan aiheeseen",
+    "composer.new_topic": "Uusi aihe",
+    "composer.drag_and_drop_images": "Vedä ja pudota kuvat tähän",
+    "composer.content_is_parsed_with": "Sisältö jäsennetään muodossa",
+    "composer.upload_instructions": "Lataa kuvia vetämällä & pudottamalla ne."
 }
\ No newline at end of file
diff --git a/public/language/fi/unread.json b/public/language/fi/unread.json
index 0a84b410c7..928290c7ce 100644
--- a/public/language/fi/unread.json
+++ b/public/language/fi/unread.json
@@ -1,6 +1,6 @@
 {
-    "title": "Unread",
-    "no_unread_topics": "Ei lukemattomia keskusteluja.",
+    "title": "Lukematon",
+    "no_unread_topics": "Ei lukemattomia aiheita.",
     "mark_all_read": "Merkitse kaikki luetuiksi",
     "load_more": "Lataa lisää"
 }
\ No newline at end of file
diff --git a/public/language/fi/user.json b/public/language/fi/user.json
index 82c0228b33..5020e50f76 100644
--- a/public/language/fi/user.json
+++ b/public/language/fi/user.json
@@ -1,47 +1,47 @@
 {
-    "banned": "Porttikiellossa",
+    "banned": "Bannattu",
     "offline": "Offline",
     "username": "Käyttäjän nimi",
     "email": "Sähköposti",
-    "fullname": "Kokonimi",
+    "fullname": "Koko nimi",
     "website": "Kotisivu",
     "location": "Sijainti",
     "age": "Ikä",
     "joined": "Liittynyt",
     "lastonline": "Viimeksi online",
-    "profile": "Profile",
-    "profile_views": "Profiilin katselukerrat",
+    "profile": "Profiili",
+    "profile_views": "Profiilia katsottu",
     "reputation": "Maine",
     "posts": "Viestit",
-    "favourites": "Favourites",
+    "favourites": "Suosikit",
     "followers": "Seuraajat",
-    "following": "Seuraa",
+    "following": "Seuratut",
     "signature": "Allekirjoitus",
     "gravatar": "Gravatar",
     "birthday": "Syntymäpäivä",
-    "chat": "Chat",
-    "follow": "Follow",
-    "unfollow": "Unfollow",
-    "change_picture": "Vaihda kuvaa",
+    "chat": "Keskustele",
+    "follow": "Seuraa",
+    "unfollow": "Älä seuraa",
+    "change_picture": "Vaihda kuva",
     "edit": "Muokkaa",
-    "uploaded_picture": "Siirretty kuva",
-    "upload_new_picture": "Siirrä uusi kuva",
-    "current_password": "Current Password",
-    "change_password": "Vaihda salasanaa",
-    "confirm_password": "Vahvista salasanaa",
+    "uploaded_picture": "Ladattu kuva",
+    "upload_new_picture": "Lataa uusi kuva",
+    "current_password": "Nykyinen salasana",
+    "change_password": "Vaihda salasana",
+    "confirm_password": "Vahvista salasana",
     "password": "Salasana",
-    "upload_picture": "Siirrä kuva",
-    "upload_a_picture": "Siirrä kuva",
-    "image_spec": "You may only upload PNG, JPG, or GIF files",
+    "upload_picture": "Lataa kuva",
+    "upload_a_picture": "Lataa kuva",
+    "image_spec": "Voit ladata vain PNG-, JPG- tai GIF-tiedostoja",
     "max": "max.",
-    "settings": "Settings",
+    "settings": "Asetukset",
     "show_email": "Näytä sähköpostiosoitteeni",
-    "has_no_follower": "Tällä käyttäjällä ei ole yhtään seuraaja :(",
+    "has_no_follower": "Kukaan ei seuraa tätä käyttäjää :(",
     "follows_no_one": "Tämä käyttäjä ei seuraa ketään :(",
-    "has_no_posts": "This user didn't post anything yet.",
+    "has_no_posts": "Tämä käyttäjä ei ole kirjoittanut vielä mitään.",
     "email_hidden": "Sähköposti piilotettu",
     "hidden": "piilotettu",
-    "paginate_description": "Paginate topics and posts instead of using infinite scroll.",
-    "topics_per_page": "Topics per Page",
-    "posts_per_page": "Posts per Page"
+    "paginate_description": "Sivuta aiheet ja viestit loputtoman vierittämisen sijaan.",
+    "topics_per_page": "Aihetta per sivu",
+    "posts_per_page": "Viestiä per sivu"
 }
\ No newline at end of file
diff --git a/public/language/fi/users.json b/public/language/fi/users.json
index d5f2b46a69..e7d3520a13 100644
--- a/public/language/fi/users.json
+++ b/public/language/fi/users.json
@@ -1,9 +1,9 @@
 {
-    "latest_users": "Viimeisimmät Käyttäjät",
+    "latest_users": "Viimeisimmät käyttäjät",
     "top_posters": "Aktiivisimmat viestittelijät",
     "most_reputation": "Eniten mainetta",
     "online": "Online",
-    "search": "Etsi",
-    "enter_username": "Syötä käyttäjänimi etsiäksesi",
+    "search": "Hae",
+    "enter_username": "Syötä käyttäjätunnus hakeaksesi",
     "load_more": "Lataa lisää"
 }
\ No newline at end of file
diff --git a/public/language/hu/topic.json b/public/language/hu/topic.json
index 64608fd052..0e64a57877 100644
--- a/public/language/hu/topic.json
+++ b/public/language/hu/topic.json
@@ -20,6 +20,11 @@
     "flag": "Jelentés",
     "flag_title": "A hozzászólás jelentése a moderátoroknál",
     "deleted_message": "Ez a topik törölve lett. Kizárólag azok a felhasználók láthatják, akiknek joga van hozzá.",
+    "following_topic.title": "Following Topic",
+    "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
+    "not_following_topic.title": "Not Following Topic",
+    "not_following_topic.message": "You will no longer receive notifications from this topic.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic",
     "watch": "Watch",
     "share_this_post": "Share this Post",
     "thread_tools.title": "Téma Eszközök",
@@ -57,9 +62,12 @@
     "posts": "Hozzászólás",
     "composer.title_placeholder": "Írd be a témanevet...",
     "composer.write": "Ír",
-    "composer.preview": "Előzénet",
+    "composer.preview": "Előnézet",
     "composer.discard": "Elvet",
     "composer.submit": "Küldés",
     "composer.replying_to": "Válasz erre:",
-    "composer.new_topic": "Új Topik"
+    "composer.new_topic": "Új Topik",
+    "composer.drag_and_drop_images": "Drag and Drop Images Here",
+    "composer.content_is_parsed_with": "Content is parsed with",
+    "composer.upload_instructions": "Upload images by dragging & dropping them."
 }
\ No newline at end of file
diff --git a/public/language/it/category.json b/public/language/it/category.json
index 26ef78ad12..9712a85a6c 100644
--- a/public/language/it/category.json
+++ b/public/language/it/category.json
@@ -1,14 +1,14 @@
 {
-    "new_topic_button": "Nuova Discussione",
+    "new_topic_button": "Nuovo Argomento",
     "no_topics": "<strong>Non ci sono discussioni in questa categoria.</strong><br />Perché non ne inizi una?",
     "sidebar.recent_replies": "Risposte Recenti",
     "sidebar.active_participants": "Partecipanti Attivi",
     "sidebar.moderators": "Moderatori",
     "posts": "post",
     "views": "visualizzazioni",
-    "posted": "inserito",
-    "browsing": "navigazione",
-    "no_replies": "Non ha ancora risposto nessuno",
+    "posted": "postato",
+    "browsing": "visualizzando",
+    "no_replies": "Nessuno ha risposto",
     "replied": "risposto",
     "last_edited_by": "ultima modifica di"
 }
\ No newline at end of file
diff --git a/public/language/it/footer.json b/public/language/it/footer.json
index c48e0c0645..99dcfe3894 100644
--- a/public/language/it/footer.json
+++ b/public/language/it/footer.json
@@ -1,7 +1,7 @@
 {
     "stats.online": "Online",
     "stats.users": "Utenti",
-    "stats.topics": "Discussioni",
+    "stats.topics": "Argomenti",
     "stats.posts": "Post",
     "success": "successo"
 }
\ No newline at end of file
diff --git a/public/language/it/global.json b/public/language/it/global.json
index 7487945551..b9c1700a5a 100644
--- a/public/language/it/global.json
+++ b/public/language/it/global.json
@@ -10,47 +10,49 @@
     "500.message": "Oops! Qualcosa non funziona come si deve!",
     "register": "Registrazione",
     "login": "Login",
-    "welcome_back": "Welcome Back ",
-    "you_have_successfully_logged_in": "You have successfully logged in",
+    "please_log_in": "Per favore Accedi",
+    "posting_restriction_info": "L'inserimento è attualmente ristretto ai soli utenti registrati, clicca qui per effettuare l'accesso.",
+    "welcome_back": "Bentornato",
+    "you_have_successfully_logged_in": "Login avvenuto con successo",
     "logout": "Logout",
     "logout.title": "Disconnessione avvenuta.",
     "logout.message": "Logout effettuato con successo",
-    "save_changes": "Salva",
+    "save_changes": "Salva cambiamenti",
     "close": "Chiudi",
-    "pagination": "Pagination",
+    "pagination": "Paginazione",
     "header.admin": "Amministratore",
     "header.recent": "Recenti",
     "header.unread": "Non letti",
-    "header.popular": "Popular",
+    "header.popular": "Popolare",
     "header.users": "Utenti",
-    "header.chats": "Chats",
-    "header.notifications": "Notifications",
+    "header.chats": "Messaggi",
+    "header.notifications": "Notifiche",
     "header.search": "Cerca",
     "header.profile": "Profilo",
     "notifications.loading": "Caricamento delle Notifiche",
-    "chats.loading": "Caricamento delle Chat",
-    "motd.welcome": "Benvenuti al NodeBB, la piattaforma di discussione del futuro.",
-    "motd.get": "Ottenere NodeBB",
-    "motd.fork": "Fork",
+    "chats.loading": "Caricamento Messaggi",
+    "motd.welcome": "Benvenuti in NodeBB, la piattaforma di discussione del futuro.",
+    "motd.get": "Ottieni NodeBB",
+    "motd.fork": "Dividi",
     "motd.like": "Mi piace",
     "motd.follow": "Segui",
-    "previouspage": "Previous Page",
-    "nextpage": "Next Page",
-    "alert.success": "Success",
-    "alert.error": "Error",
-    "alert.banned": "Banned",
-    "alert.banned.message": "You are banned you will be logged out!",
-    "alert.unfollow": "You are no longer following %1!",
-    "alert.follow": "You are now following %1!",
-    "posts": "Posts",
-    "views": "Views",
-    "posted": "posted",
+    "previouspage": "Pagina Precedente",
+    "nextpage": "Pagina Successiva",
+    "alert.success": "Riuscito",
+    "alert.error": "Errore",
+    "alert.banned": "Bannato",
+    "alert.banned.message": "Sei bannato e verrai disconnesso!",
+    "alert.unfollow": "Non stai più seguendo %1!",
+    "alert.follow": "Stai seguendo %1!",
+    "posts": "Post",
+    "views": "Visualizzazioni",
+    "posted": "postato",
     "in": "in",
-    "recentposts": "Recent Posts",
+    "recentposts": "Post Recenti",
     "online": "Online",
-    "away": "Away",
-    "dnd": "Do not Disturb",
-    "invisible": "Invisible",
-    "offline": "Offline",
+    "away": "Non disponibile",
+    "dnd": "Non disturbare",
+    "invisible": "Invisibile",
+    "offline": "Non in linea",
     "privacy": "Privacy"
 }
\ No newline at end of file
diff --git a/public/language/it/language.json b/public/language/it/language.json
index 76bc29d1e3..b749dfac01 100644
--- a/public/language/it/language.json
+++ b/public/language/it/language.json
@@ -1,5 +1,5 @@
 {
     "name": "Italiano",
-    "code": "it",
+    "code": "it_IT",
     "dir": "ltr"
 }
\ No newline at end of file
diff --git a/public/language/it/login.json b/public/language/it/login.json
index 61a6f7bf9f..18d043e3ce 100644
--- a/public/language/it/login.json
+++ b/public/language/it/login.json
@@ -1,10 +1,10 @@
 {
-    "login": "Login",
+    "login": "Accedi",
     "username": "Nome utente",
     "password": "Password",
     "remember_me": "Memorizzami?",
     "forgot_password": "Password dimenticata?",
-    "alternative_logins": "Login Alternativi",
-    "failed_login_attempt": "Tentativo di login fallito; prova ancora.",
-    "login_successful": "Login avvenuto con successo!"
+    "alternative_logins": "Accessi Alternativi",
+    "failed_login_attempt": "Tentativo di accesso fallito, prova ancora.",
+    "login_successful": "Sei entrato con successo!"
 }
\ No newline at end of file
diff --git a/public/language/it/modules.json b/public/language/it/modules.json
index 71d7fd74b0..b5fa9c0c82 100644
--- a/public/language/it/modules.json
+++ b/public/language/it/modules.json
@@ -1,6 +1,6 @@
 {
     "chat.chatting_with": "Chatta con <span id=\"chat-with-name\"></span>",
-    "chat.placeholder": "scrivi un messaggio qui e premi Invio",
+    "chat.placeholder": "scrivi un messaggio qui, poi premi Invio",
     "chat.send": "Invia",
-    "chat.no_active": "Non hai le chat attive."
+    "chat.no_active": "Non hai discussioni attive."
 }
\ No newline at end of file
diff --git a/public/language/it/notifications.json b/public/language/it/notifications.json
index 5c75b41c5c..a039828ca3 100644
--- a/public/language/it/notifications.json
+++ b/public/language/it/notifications.json
@@ -1,10 +1,10 @@
 {
     "title": "Notifiche",
-    "no_notifs": "You have no new notifications",
-    "see_all": "See all Notifications",
+    "no_notifs": "Non hai nuove notifiche",
+    "see_all": "Vedi tutte le Notifiche",
     "back_to_home": "Torna alla pagina iniziale",
     "outgoing_link": "Link in uscita",
-    "outgoing_link_message": "Ci stai abbandonando",
+    "outgoing_link_message": "Stai lasciando",
     "continue_to": "Continua verso",
     "return_to": "Ritorna a "
 }
\ No newline at end of file
diff --git a/public/language/it/pages.json b/public/language/it/pages.json
index d60e0a0a9b..80f9ef9e72 100644
--- a/public/language/it/pages.json
+++ b/public/language/it/pages.json
@@ -1,13 +1,13 @@
 {
     "home": "Home",
-    "unread": "Unread Topics",
-    "popular": "Popular Topics",
-    "recent": "Recent Topics",
-    "users": "Registered Users",
-    "notifications": "Notifications",
-    "user.edit": "Editing \"%1\"",
-    "user.following": "People %1 Follows",
-    "user.followers": "People who Follow %1",
-    "user.favourites": "%1's Favourite Posts",
-    "user.settings": "User Settings"
+    "unread": "Argomenti non letti",
+    "popular": "Argomenti Popolari",
+    "recent": "Argomenti Recenti",
+    "users": "Utenti Registrati",
+    "notifications": "Notifiche",
+    "user.edit": "Modificando \"%1\"",
+    "user.following": "%1 Persone seguono",
+    "user.followers": "Persone che seguono %1",
+    "user.favourites": "Post Favoriti di %1",
+    "user.settings": "Impostazioni Utente"
 }
\ No newline at end of file
diff --git a/public/language/it/recent.json b/public/language/it/recent.json
index 477ed8e887..4636a6e7a9 100644
--- a/public/language/it/recent.json
+++ b/public/language/it/recent.json
@@ -1,7 +1,7 @@
 {
-    "title": "Recent",
+    "title": "Recenti",
     "day": "Giorno",
     "week": "Settimana",
     "month": "Mese",
-    "no_recent_topics": "There are no recent topics."
+    "no_recent_topics": "Non ci sono discussioni recenti."
 }
\ No newline at end of file
diff --git a/public/language/it/register.json b/public/language/it/register.json
index cbd85d9de4..7fa9289292 100644
--- a/public/language/it/register.json
+++ b/public/language/it/register.json
@@ -13,6 +13,6 @@
     "confirm_password_placeholder": "Conferma la Password",
     "register_now_button": "Registrati",
     "alternative_registration": "Altri metodi di registrazione",
-    "terms_of_use": "Terms of Use",
-    "agree_to_terms_of_use": "I agree to the Terms of Use"
+    "terms_of_use": "Termini di Utilizzo",
+    "agree_to_terms_of_use": "Accetto i Termini di Utilizzo"
 }
\ No newline at end of file
diff --git a/public/language/it/reset_password.json b/public/language/it/reset_password.json
index 3070eafdbe..a8dc6250f4 100644
--- a/public/language/it/reset_password.json
+++ b/public/language/it/reset_password.json
@@ -2,9 +2,9 @@
     "reset_password": "Resetta la Password",
     "update_password": "Cambia la Password",
     "password_changed.title": "Password Modificata",
-    "password_changed.message": "<p>La password è stata resettata con successo. <a href=\"/login\">Effettua di nuovo il log in</a>.",
+    "password_changed.message": "<p>La password è stata resettata con successo. <a href=\"/login\">Effettua di nuovo l'accesso</a>.",
     "wrong_reset_code.title": "Codice di reset non corretto",
-    "wrong_reset_code.message": "Il codice di reset ricevuto non è corretto. Prova ancora, o <a href=\"/reset\">richiedi un nuovo codice</a>.",
+    "wrong_reset_code.message": "Il codice di reset ricevuto non è corretto. Prova ancora, oppure <a href=\"/reset\">richiedi un nuovo codice</a>.",
     "new_password": "Nuova Password",
     "repeat_password": "Conferma la Password",
     "enter_email": "Inserisci il tuo <strong>indirizzo email</strong> e ti invieremo un'email con le istruzioni per resettare il tuo account.",
diff --git a/public/language/it/topic.json b/public/language/it/topic.json
index 0ae8b4b21b..e8e44d9f03 100644
--- a/public/language/it/topic.json
+++ b/public/language/it/topic.json
@@ -1,10 +1,10 @@
 {
-    "topic": "Discussione",
-    "topics": "Discussioni",
+    "topic": "Argomento",
+    "topics": "Argomenti",
     "no_topics_found": "Nessuna discussione trovata!",
-    "no_posts_found": "No posts found!",
+    "no_posts_found": "Nessun post trovato!",
     "profile": "Profilo",
-    "posted_by": "Posted by",
+    "posted_by": "Inviato da",
     "chat": "Chat",
     "notify_me": "Ricevi notifiche di nuove risposte in questa discussione",
     "quote": "Citazione",
@@ -12,54 +12,62 @@
     "edit": "Modifica",
     "delete": "Cancella",
     "move": "Muovi",
-    "fork": "Fork",
+    "fork": "Dividi",
     "banned": "bannato",
     "link": "Link",
-    "share": "Share",
-    "tools": "Tools",
-    "flag": "Flag",
-    "flag_title": "Flag this post for moderation",
-    "deleted_message": "This thread has been deleted. Only users with thread management privileges can see it.",
-    "watch": "Watch",
-    "share_this_post": "Share this Post",
+    "share": "Condividi",
+    "tools": "Strumenti",
+    "flag": "Segnala",
+    "flag_title": "Segnala questo post per la moderazione",
+    "deleted_message": "Questo argomento è stato cancellato. Solo gli utenti che possono gestire gli argomenti riescono a vederlo.",
+    "following_topic.title": "Argomento seguente",
+    "following_topic.message": "Da ora riceverai notifiche quando qualcuno posterà in questa discussione.",
+    "not_following_topic.title": "Non stai seguendo questo argomento",
+    "not_following_topic.message": "Non riceverai più notifiche da questa discussione.",
+    "login_to_subscribe": "Per favore registrati o accedi per sottoscrivere questo argomento",
+    "watch": "Guarda",
+    "share_this_post": "Condividi questo Post",
     "thread_tools.title": "Strumenti per il Thread",
-    "thread_tools.markAsUnreadForAll": "Mark Unread",
-    "thread_tools.pin": "Pin Topic",
-    "thread_tools.unpin": "Unpin Topic",
-    "thread_tools.lock": "Lock Topic",
-    "thread_tools.unlock": "Unlock Topic",
-    "thread_tools.move": "Move Topic",
-    "thread_tools.fork": "Fork Topic",
-    "thread_tools.delete": "Delete Topic",
-    "thread_tools.restore": "Restore Topic",
-    "load_categories": "Caricamento delle Categorie",
+    "thread_tools.markAsUnreadForAll": "Segna come non letto",
+    "thread_tools.pin": "Pinna Argomento",
+    "thread_tools.unpin": "Unpin Argomento",
+    "thread_tools.lock": "Blocca Discussione",
+    "thread_tools.unlock": "Sblocca Discussione",
+    "thread_tools.move": "Sposta Discussione",
+    "thread_tools.fork": "Dividi Discussione",
+    "thread_tools.delete": "Elimina Discussione",
+    "thread_tools.restore": "Ripristina Discussione",
+    "load_categories": "Caricamento Categorie",
     "disabled_categories_note": "Le Categorie disabilitate sono in grigio",
     "confirm_move": "Sposta",
-    "confirm_fork": "Fork",
+    "confirm_fork": "Dividi",
     "favourite": "Preferito",
     "favourites": "Preferiti",
     "favourites.not_logged_in.title": "Non collegato/a",
-    "favourites.not_logged_in.message": "Log in per aggiungere questo post ai preferiti",
+    "favourites.not_logged_in.message": "Accedi per aggiungere questo post ai preferiti",
     "favourites.has_no_favourites": "Non hai ancun post preferito; aggiungi qualche post ai preferiti per vederli qui!",
-    "vote.not_logged_in.title": "Not Logged In",
-    "vote.not_logged_in.message": "Please log in in order to vote",
-    "vote.cant_vote_self.title": "Invalid Vote",
-    "vote.cant_vote_self.message": "You cannot vote for your own post",
+    "vote.not_logged_in.title": "Non loggato",
+    "vote.not_logged_in.message": "Accedi per poter votare",
+    "vote.cant_vote_self.title": "Voto non valido",
+    "vote.cant_vote_self.message": "Non puoi votare per i tuoi post",
     "loading_more_posts": "Caricamento altri post",
-    "move_topic": "Spsota Discussione",
+    "move_topic": "Sposta Discussione",
     "move_post": "Sposta Post",
-    "fork_topic": "Fork Topic",
+    "fork_topic": "Dividi il Topic",
     "topic_will_be_moved_to": "Questa discussione verrà spostata nella categoria",
-    "fork_topic_instruction": "Clicca sui post che vuoi forkare",
+    "fork_topic_instruction": "Clicca sui post che vuoi dividere",
     "fork_no_pids": "Nessun post selezionato!",
-    "fork_success": "Discussione forkata con successo!",
+    "fork_success": "Discussione divisa con successo!",
     "reputation": "Reputazione",
     "posts": "Post",
-    "composer.title_placeholder": "Enter your topic title here...",
-    "composer.write": "Write",
-    "composer.preview": "Preview",
-    "composer.discard": "Discard",
-    "composer.submit": "Submit",
-    "composer.replying_to": "Replying to",
-    "composer.new_topic": "New Topic"
+    "composer.title_placeholder": "Inserisci qui il titolo della discussione...",
+    "composer.write": "Scrivi",
+    "composer.preview": "Anteprima",
+    "composer.discard": "Scarta",
+    "composer.submit": "Invia",
+    "composer.replying_to": "Rispondendo a",
+    "composer.new_topic": "Nuovo Argomento",
+    "composer.drag_and_drop_images": "Trascina e rilascia le immagini qui",
+    "composer.content_is_parsed_with": "Il contenuto è analizzato con",
+    "composer.upload_instructions": "Carica immagini trascinandole e rilasciandole."
 }
\ No newline at end of file
diff --git a/public/language/it/unread.json b/public/language/it/unread.json
index d42f3f38ac..362b11e8f5 100644
--- a/public/language/it/unread.json
+++ b/public/language/it/unread.json
@@ -1,5 +1,5 @@
 {
-    "title": "Unread",
+    "title": "Non letto",
     "no_unread_topics": "Non ci sono discussioni non lette.",
     "mark_all_read": "Segna tutto come già letto",
     "load_more": "Carica Altro"
diff --git a/public/language/it/user.json b/public/language/it/user.json
index 5caa73bd27..25f7a614a1 100644
--- a/public/language/it/user.json
+++ b/public/language/it/user.json
@@ -9,39 +9,39 @@
     "age": "Età",
     "joined": "Iscrizione",
     "lastonline": "Ultima volta in linea",
-    "profile": "Profile",
+    "profile": "Profilo",
     "profile_views": "Visite al profilo",
     "reputation": "Reputazione",
     "posts": "Post",
-    "favourites": "Favourites",
+    "favourites": "Favoriti",
     "followers": "Da chi è seguito",
     "following": "Chi segue",
     "signature": "Firma",
     "gravatar": "Gravatar",
     "birthday": "Data di nascita",
     "chat": "Chat",
-    "follow": "Follow",
-    "unfollow": "Unfollow",
-    "change_picture": "Cambia la foto",
+    "follow": "Segui",
+    "unfollow": "Smetti di seguire",
+    "change_picture": "Cambia Foto",
     "edit": "Modifica",
     "uploaded_picture": "Foto caricata",
     "upload_new_picture": "Carica una nuova foto",
-    "current_password": "Current Password",
+    "current_password": "Password corrente",
     "change_password": "Cambia la Password",
     "confirm_password": "Conferma la Password",
     "password": "Password",
     "upload_picture": "Carica foto",
     "upload_a_picture": "Carica una foto",
-    "image_spec": "You may only upload PNG, JPG, or GIF files",
-    "max": "max.",
-    "settings": "Settings",
+    "image_spec": "Puoi caricare solo file PNG, JPG o GIF",
+    "max": "massimo.",
+    "settings": "Impostazioni",
     "show_email": "Mostra la mia Email",
     "has_no_follower": "Questo utente non è seguito da nessuno :(",
     "follows_no_one": "Questo utente non segue nessuno :(",
-    "has_no_posts": "This user didn't post anything yet.",
+    "has_no_posts": "Questo utente non ha ancora postato nulla.",
     "email_hidden": "Email Nascosta",
     "hidden": "nascosta",
-    "paginate_description": "Paginate topics and posts instead of using infinite scroll.",
-    "topics_per_page": "Topics per Page",
-    "posts_per_page": "Posts per Page"
+    "paginate_description": "Dividi argomenti e post in pagine anziché usare lo scroll infinito.",
+    "topics_per_page": "Argomenti per Pagina",
+    "posts_per_page": "Post per Pagina"
 }
\ No newline at end of file
diff --git a/public/language/it/users.json b/public/language/it/users.json
index a3bbbb54e7..12dcfa09ff 100644
--- a/public/language/it/users.json
+++ b/public/language/it/users.json
@@ -5,5 +5,5 @@
     "online": "In linea",
     "search": "Cerca",
     "enter_username": "Inserisci il nome utente da cercare",
-    "load_more": "Carica di più"
+    "load_more": "Carica altri"
 }
\ No newline at end of file
diff --git a/public/language/pl/global.json b/public/language/pl/global.json
index b8f5162365..9b979e6373 100644
--- a/public/language/pl/global.json
+++ b/public/language/pl/global.json
@@ -10,6 +10,8 @@
     "500.message": "Coś poszło nie tak.",
     "register": "Zarejestruj się",
     "login": "Zaloguj się",
+    "please_log_in": "Proszę się zalogować",
+    "posting_restriction_info": "Pisanie jest dostępne tylko dla zarejestrowanych członków forum, kliknij tutaj aby się zalogować.",
     "welcome_back": "Witaj z powrotem!",
     "you_have_successfully_logged_in": "Zostałeś pomyślnie zalogowany.",
     "logout": "Wyloguj się",
diff --git a/public/language/pl/pages.json b/public/language/pl/pages.json
index ee44ea945b..c91a2daff9 100644
--- a/public/language/pl/pages.json
+++ b/public/language/pl/pages.json
@@ -1,7 +1,7 @@
 {
     "home": "Strona główna",
     "unread": "Nieprzeczytane wątki",
-    "popular": "Popular Topics",
+    "popular": "Popularne wątki",
     "recent": "Ostatnie wątki",
     "users": "Zarejestrowani użytkownicy",
     "notifications": "Powiadomienia",
diff --git a/public/language/pl/topic.json b/public/language/pl/topic.json
index 020d68d6c2..8127f73075 100644
--- a/public/language/pl/topic.json
+++ b/public/language/pl/topic.json
@@ -20,6 +20,11 @@
     "flag": "Zgłoś",
     "flag_title": "Zgłoś post do moderacji",
     "deleted_message": "Ten wątek został usunięty. Tylko użytkownicy z uprawnieniami do zarządzania wątkami mogą go widzieć.",
+    "following_topic.title": "Obserwujesz wątek",
+    "following_topic.message": "Będziesz otrzymywał powiadomienia, gdy ktoś odpowie w tym wątku.",
+    "not_following_topic.title": "Nie obserwujesz wątku",
+    "not_following_topic.message": "Nie będziesz otrzymywał więcej powiadomień z tego wątku.",
+    "login_to_subscribe": "Zaloguj się, aby subskrybować ten wątek.",
     "watch": "Obserwuj",
     "share_this_post": "Udostępnij",
     "thread_tools.title": "Narzędzia wątków",
@@ -61,5 +66,8 @@
     "composer.discard": "Odrzuć",
     "composer.submit": "Wyślij",
     "composer.replying_to": "Odpowiadasz",
-    "composer.new_topic": "Nowy wątek"
+    "composer.new_topic": "Nowy wątek",
+    "composer.drag_and_drop_images": "Przeciągnij i upuść obrazek tutaj.",
+    "composer.content_is_parsed_with": "Tekst jest parsowany przy pomocy",
+    "composer.upload_instructions": "Prześlij obrazki przeciągając i upuszczając je."
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/global.json b/public/language/zh_CN/global.json
index ca76a1ea64..3d930991d7 100644
--- a/public/language/zh_CN/global.json
+++ b/public/language/zh_CN/global.json
@@ -7,50 +7,52 @@
     "404.title": "无法找到该页",
     "404.message": "你所查找的页面并不存在,返回<a href='/'>主页</a>。",
     "500.title": "内部错误",
-    "500.message": "不好!看来是哪里出错了!",
+    "500.message": "哎呀!看来是哪里出错了!",
     "register": "注册",
     "login": "登录",
+    "please_log_in": "请登录",
+    "posting_restriction_info": "发表目前仅限于注册会员,点击这里登录。",
     "welcome_back": "欢迎回来",
-    "you_have_successfully_logged_in": "You have successfully logged in",
+    "you_have_successfully_logged_in": "你已经退出登录",
     "logout": "退出",
     "logout.title": "你已经退出。",
     "logout.message": "你已经成功退出登录。",
     "save_changes": "保存修改",
     "close": "关闭",
-    "pagination": "Pagination",
+    "pagination": "分页",
     "header.admin": "管理",
     "header.recent": "最近",
     "header.unread": "未读",
-    "header.popular": "Popular",
+    "header.popular": "流行",
     "header.users": "用户",
-    "header.chats": "Chats",
-    "header.notifications": "Notifications",
+    "header.chats": "聊天",
+    "header.notifications": "通知",
     "header.search": "搜索",
     "header.profile": "设置",
     "notifications.loading": "消息载入中",
     "chats.loading": "聊天载入中",
-    "motd.welcome": "Welcome to NodeBB, the discussion platform of the future.",
-    "motd.get": "Get NodeBB",
-    "motd.fork": "Fork",
-    "motd.like": "讚",
+    "motd.welcome": "欢迎来到NodeBB,未来的社区论坛平台。",
+    "motd.get": "获取NodeBB",
+    "motd.fork": "分支",
+    "motd.like": "赞",
     "motd.follow": "关注",
-    "previouspage": "Previous Page",
-    "nextpage": "Next Page",
-    "alert.success": "Success",
-    "alert.error": "Error",
-    "alert.banned": "Banned",
-    "alert.banned.message": "You are banned you will be logged out!",
-    "alert.unfollow": "You are no longer following %1!",
-    "alert.follow": "You are now following %1!",
-    "posts": "Posts",
-    "views": "Views",
-    "posted": "posted",
-    "in": "in",
-    "recentposts": "Recent Posts",
-    "online": "Online",
-    "away": "Away",
-    "dnd": "Do not Disturb",
-    "invisible": "Invisible",
-    "offline": "Offline",
-    "privacy": "Privacy"
+    "previouspage": "上一页",
+    "nextpage": "下一页",
+    "alert.success": "成功",
+    "alert.error": "错误",
+    "alert.banned": "禁止",
+    "alert.banned.message": "你被禁止了将会退出登录。",
+    "alert.unfollow": "你不再是关注的那1%!",
+    "alert.follow": "你现在属于关注的1%!",
+    "posts": "帖子",
+    "views": "浏览",
+    "posted": "发布",
+    "in": "在",
+    "recentposts": "最新发表",
+    "online": " 在线",
+    "away": "离开",
+    "dnd": "不打扰",
+    "invisible": "不可见",
+    "offline": "离线",
+    "privacy": "隐私"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/notifications.json b/public/language/zh_CN/notifications.json
index 0e9a54f527..8ecb526603 100644
--- a/public/language/zh_CN/notifications.json
+++ b/public/language/zh_CN/notifications.json
@@ -1,10 +1,10 @@
 {
-    "title": "消息",
-    "no_notifs": "You have no new notifications",
-    "see_all": "See all Notifications",
+    "title": "通知",
+    "no_notifs": "你没有新的通知",
+    "see_all": "查看所有通知",
     "back_to_home": "返回主页",
     "outgoing_link": "站外链接",
-    "outgoing_link_message": "你正在离开本站。",
+    "outgoing_link_message": "你正在离开本站",
     "continue_to": "继续前往",
     "return_to": "返回"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/pages.json b/public/language/zh_CN/pages.json
index d60e0a0a9b..d4218f3f08 100644
--- a/public/language/zh_CN/pages.json
+++ b/public/language/zh_CN/pages.json
@@ -1,13 +1,13 @@
 {
-    "home": "Home",
-    "unread": "Unread Topics",
-    "popular": "Popular Topics",
-    "recent": "Recent Topics",
-    "users": "Registered Users",
-    "notifications": "Notifications",
-    "user.edit": "Editing \"%1\"",
-    "user.following": "People %1 Follows",
-    "user.followers": "People who Follow %1",
-    "user.favourites": "%1's Favourite Posts",
-    "user.settings": "User Settings"
+    "home": "主页",
+    "unread": "未读",
+    "popular": "受欢迎的主题",
+    "recent": "最新主题",
+    "users": "已注册用户",
+    "notifications": "提醒",
+    "user.edit": "编辑 \"%1\"",
+    "user.following": "%1的人关注",
+    "user.followers": "%1关注的人",
+    "user.favourites": "%1 喜爱的帖子",
+    "user.settings": "用户设置"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/recent.json b/public/language/zh_CN/recent.json
index 179cf32202..41eadeaa81 100644
--- a/public/language/zh_CN/recent.json
+++ b/public/language/zh_CN/recent.json
@@ -1,7 +1,7 @@
 {
-    "title": "Recent",
+    "title": "最近",
     "day": "今日",
     "week": "本周",
     "month": "本月",
-    "no_recent_topics": "There are no recent topics."
+    "no_recent_topics": "没有最近的话题。"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/register.json b/public/language/zh_CN/register.json
index 1093ad2879..6cd050de46 100644
--- a/public/language/zh_CN/register.json
+++ b/public/language/zh_CN/register.json
@@ -3,7 +3,7 @@
     "help.email": "默认情况下,你的邮箱不会公开。",
     "help.username_restrictions": "用户名由%1到%2个字符组成。其他人可以通过 @<span id='yourUsername'>用户名</span> 点名你。",
     "help.minimum_password_length": "密码必须至少包含%1个字符。",
-    "email_address": "Email",
+    "email_address": "邮箱地址",
     "email_address_placeholder": "输入邮箱地址",
     "username": "用户名",
     "username_placeholder": "输入用户名",
@@ -13,6 +13,6 @@
     "confirm_password_placeholder": "再次输入密码",
     "register_now_button": "现在注册",
     "alternative_registration": "其他方式注册",
-    "terms_of_use": "Terms of Use",
-    "agree_to_terms_of_use": "I agree to the Terms of Use"
+    "terms_of_use": "使用条款",
+    "agree_to_terms_of_use": "我同意使用条款"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/topic.json b/public/language/zh_CN/topic.json
index 30ae7c73fe..381c310b52 100644
--- a/public/language/zh_CN/topic.json
+++ b/public/language/zh_CN/topic.json
@@ -2,9 +2,9 @@
     "topic": "主题",
     "topics": "主题",
     "no_topics_found": "没有找到主题!",
-    "no_posts_found": "No posts found!",
+    "no_posts_found": "没有找到帖子!",
     "profile": "资料",
-    "posted_by": "Posted by",
+    "posted_by": "发表",
     "chat": "聊天",
     "notify_me": "该主题有新回复时通知我",
     "quote": "引用",
@@ -13,25 +13,30 @@
     "delete": "删除",
     "move": "移动",
     "fork": "作为主题",
-    "banned": "封禁",
+    "banned": "禁止",
     "link": "链接",
-    "share": "Share",
-    "tools": "Tools",
-    "flag": "Flag",
-    "flag_title": "Flag this post for moderation",
-    "deleted_message": "This thread has been deleted. Only users with thread management privileges can see it.",
-    "watch": "Watch",
-    "share_this_post": "Share this Post",
+    "share": "分享",
+    "tools": "工具",
+    "flag": "标志",
+    "flag_title": "标志受限的帖子",
+    "deleted_message": "这个帖子已经删除,只有帖子的拥有者才有权限去查看。",
+    "following_topic.title": "关注该主题",
+    "following_topic.message": "当有回复提交的时候你将会收到通知。",
+    "not_following_topic.title": "非关注主题",
+    "not_following_topic.message": "你将不再接受来自该帖子的通知。",
+    "login_to_subscribe": "请注册或登录以订阅该主题",
+    "watch": "查看",
+    "share_this_post": "分享帖子",
     "thread_tools.title": "管理工具",
-    "thread_tools.markAsUnreadForAll": "Mark Unread",
-    "thread_tools.pin": "Pin Topic",
-    "thread_tools.unpin": "Unpin Topic",
-    "thread_tools.lock": "Lock Topic",
-    "thread_tools.unlock": "Unlock Topic",
-    "thread_tools.move": "Move Topic",
-    "thread_tools.fork": "Fork Topic",
-    "thread_tools.delete": "Delete Topic",
-    "thread_tools.restore": "Restore Topic",
+    "thread_tools.markAsUnreadForAll": "标记未读",
+    "thread_tools.pin": "置顶主题",
+    "thread_tools.unpin": "解除置顶",
+    "thread_tools.lock": "锁定主题",
+    "thread_tools.unlock": "解除锁定",
+    "thread_tools.move": "移动主题",
+    "thread_tools.fork": "分叉主题",
+    "thread_tools.delete": "删除主题",
+    "thread_tools.restore": "恢复主题",
     "load_categories": "版面载入中",
     "disabled_categories_note": "停用的版面为灰色",
     "confirm_move": "移动",
@@ -41,10 +46,10 @@
     "favourites.not_logged_in.title": "未登录",
     "favourites.not_logged_in.message": "收藏帖子之前请先登录。",
     "favourites.has_no_favourites": "你还没有任何收藏,收藏的帖子将会出现在这里!",
-    "vote.not_logged_in.title": "Not Logged In",
-    "vote.not_logged_in.message": "Please log in in order to vote",
-    "vote.cant_vote_self.title": "Invalid Vote",
-    "vote.cant_vote_self.message": "You cannot vote for your own post",
+    "vote.not_logged_in.title": "未登录",
+    "vote.not_logged_in.message": "收藏帖子之前请先登录。",
+    "vote.cant_vote_self.title": "废票 ",
+    "vote.cant_vote_self.message": "你不能为自己的帖子投票",
     "loading_more_posts": "载入更多帖子",
     "move_topic": "移动主题",
     "move_post": "移动帖子",
@@ -55,11 +60,14 @@
     "fork_success": "成功将帖子作为主题!",
     "reputation": "声望",
     "posts": "发帖数",
-    "composer.title_placeholder": "Enter your topic title here...",
-    "composer.write": "Write",
-    "composer.preview": "Preview",
-    "composer.discard": "Discard",
-    "composer.submit": "Submit",
-    "composer.replying_to": "Replying to",
-    "composer.new_topic": "New Topic"
+    "composer.title_placeholder": "在这里输入你的主题标题...",
+    "composer.write": "书写",
+    "composer.preview": "预览",
+    "composer.discard": "丢弃",
+    "composer.submit": "提交",
+    "composer.replying_to": "回复",
+    "composer.new_topic": "新主题",
+    "composer.drag_and_drop_images": "把图像拖到此处",
+    "composer.content_is_parsed_with": "内容已经被解析",
+    "composer.upload_instructions": "拖拽图片以上传"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/unread.json b/public/language/zh_CN/unread.json
index a880df356e..b5107e3b47 100644
--- a/public/language/zh_CN/unread.json
+++ b/public/language/zh_CN/unread.json
@@ -1,5 +1,5 @@
 {
-    "title": "Unread",
+    "title": "未读",
     "no_unread_topics": "没有未读主题。",
     "mark_all_read": "标记全部为已读",
     "load_more": "载入更多"

From 74d6392bec856e69acd0551b8f5cda25941bf09b Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Wed, 26 Feb 2014 14:55:45 -0500
Subject: [PATCH 075/193] added Dutch language

---
 .tx/config                             | 15 ++++++
 public/language/nl/category.json       | 14 +++++
 public/language/nl/footer.json         |  7 +++
 public/language/nl/global.json         | 58 ++++++++++++++++++++
 public/language/nl/language.json       |  5 ++
 public/language/nl/login.json          | 10 ++++
 public/language/nl/modules.json        |  6 +++
 public/language/nl/notifications.json  | 10 ++++
 public/language/nl/pages.json          | 13 +++++
 public/language/nl/recent.json         |  7 +++
 public/language/nl/register.json       | 18 +++++++
 public/language/nl/reset_password.json | 13 +++++
 public/language/nl/topic.json          | 73 ++++++++++++++++++++++++++
 public/language/nl/unread.json         |  6 +++
 public/language/nl/user.json           | 47 +++++++++++++++++
 public/language/nl/users.json          |  9 ++++
 16 files changed, 311 insertions(+)
 create mode 100644 public/language/nl/category.json
 create mode 100644 public/language/nl/footer.json
 create mode 100644 public/language/nl/global.json
 create mode 100644 public/language/nl/language.json
 create mode 100644 public/language/nl/login.json
 create mode 100644 public/language/nl/modules.json
 create mode 100644 public/language/nl/notifications.json
 create mode 100644 public/language/nl/pages.json
 create mode 100644 public/language/nl/recent.json
 create mode 100644 public/language/nl/register.json
 create mode 100644 public/language/nl/reset_password.json
 create mode 100644 public/language/nl/topic.json
 create mode 100644 public/language/nl/unread.json
 create mode 100644 public/language/nl/user.json
 create mode 100644 public/language/nl/users.json

diff --git a/.tx/config b/.tx/config
index b09f5b11ec..6f403b6cf1 100644
--- a/.tx/config
+++ b/.tx/config
@@ -15,6 +15,7 @@ trans.he = public/language/he/category.json
 trans.hu = public/language/hu/category.json
 trans.it = public/language/it/category.json
 trans.nb = public/language/nb/category.json
+trans.nl = public/language/nl/category.json
 trans.pl = public/language/pl/category.json
 trans.pt_BR = public/language/pt_BR/category.json
 trans.ru = public/language/ru/category.json
@@ -39,6 +40,7 @@ trans.he = public/language/he/login.json
 trans.hu = public/language/hu/login.json
 trans.it = public/language/it/login.json
 trans.nb = public/language/nb/login.json
+trans.nl = public/language/nl/login.json
 trans.pl = public/language/pl/login.json
 trans.pt_BR = public/language/pt_BR/login.json
 trans.ru = public/language/ru/login.json
@@ -62,6 +64,7 @@ trans.he = public/language/he/recent.json
 trans.hu = public/language/hu/recent.json
 trans.it = public/language/it/recent.json
 trans.nb = public/language/nb/recent.json
+trans.nl = public/language/nl/recent.json
 trans.pl = public/language/pl/recent.json
 trans.pt_BR = public/language/pt_BR/recent.json
 trans.ru = public/language/ru/recent.json
@@ -85,6 +88,7 @@ trans.he = public/language/he/unread.json
 trans.hu = public/language/hu/unread.json
 trans.it = public/language/it/unread.json
 trans.nb = public/language/nb/unread.json
+trans.nl = public/language/nl/unread.json
 trans.pl = public/language/pl/unread.json
 trans.pt_BR = public/language/pt_BR/unread.json
 trans.ru = public/language/ru/unread.json
@@ -108,6 +112,7 @@ trans.he = public/language/he/footer.json
 trans.hu = public/language/hu/footer.json
 trans.it = public/language/it/footer.json
 trans.nb = public/language/nb/footer.json
+trans.nl = public/language/nl/footer.json
 trans.pl = public/language/pl/footer.json
 trans.pt_BR = public/language/pt_BR/footer.json
 trans.ru = public/language/ru/footer.json
@@ -131,6 +136,7 @@ trans.he = public/language/he/modules.json
 trans.hu = public/language/hu/modules.json
 trans.it = public/language/it/modules.json
 trans.nb = public/language/nb/modules.json
+trans.nl = public/language/nl/modules.json
 trans.pl = public/language/pl/modules.json
 trans.pt_BR = public/language/pt_BR/modules.json
 trans.ru = public/language/ru/modules.json
@@ -154,6 +160,7 @@ trans.he = public/language/he/register.json
 trans.hu = public/language/hu/register.json
 trans.it = public/language/it/register.json
 trans.nb = public/language/nb/register.json
+trans.nl = public/language/nl/register.json
 trans.pl = public/language/pl/register.json
 trans.pt_BR = public/language/pt_BR/register.json
 trans.ru = public/language/ru/register.json
@@ -177,6 +184,7 @@ trans.he = public/language/he/user.json
 trans.hu = public/language/hu/user.json
 trans.it = public/language/it/user.json
 trans.nb = public/language/nb/user.json
+trans.nl = public/language/nl/user.json
 trans.pl = public/language/pl/user.json
 trans.pt_BR = public/language/pt_BR/user.json
 trans.ru = public/language/ru/user.json
@@ -200,6 +208,7 @@ trans.he = public/language/he/global.json
 trans.hu = public/language/hu/global.json
 trans.it = public/language/it/global.json
 trans.nb = public/language/nb/global.json
+trans.nl = public/language/nl/global.json
 trans.pl = public/language/pl/global.json
 trans.pt_BR = public/language/pt_BR/global.json
 trans.ru = public/language/ru/global.json
@@ -223,6 +232,7 @@ trans.he = public/language/he/notifications.json
 trans.hu = public/language/hu/notifications.json
 trans.it = public/language/it/notifications.json
 trans.nb = public/language/nb/notifications.json
+trans.nl = public/language/nl/notifications.json
 trans.pl = public/language/pl/notifications.json
 trans.pt_BR = public/language/pt_BR/notifications.json
 trans.ru = public/language/ru/notifications.json
@@ -246,6 +256,7 @@ trans.he = public/language/he/reset_password.json
 trans.hu = public/language/hu/reset_password.json
 trans.it = public/language/it/reset_password.json
 trans.nb = public/language/nb/reset_password.json
+trans.nl = public/language/nl/reset_password.json
 trans.pl = public/language/pl/reset_password.json
 trans.pt_BR = public/language/pt_BR/reset_password.json
 trans.ru = public/language/ru/reset_password.json
@@ -269,6 +280,7 @@ trans.he = public/language/he/users.json
 trans.hu = public/language/hu/users.json
 trans.it = public/language/it/users.json
 trans.nb = public/language/nb/users.json
+trans.nl = public/language/nl/users.json
 trans.pl = public/language/pl/users.json
 trans.pt_BR = public/language/pt_BR/users.json
 trans.ru = public/language/ru/users.json
@@ -292,6 +304,7 @@ trans.he = public/language/he/language.json
 trans.hu = public/language/hu/language.json
 trans.it = public/language/it/language.json
 trans.nb = public/language/nb/language.json
+trans.nl = public/language/nl/language.json
 trans.pl = public/language/pl/language.json
 trans.pt_BR = public/language/pt_BR/language.json
 trans.ru = public/language/ru/language.json
@@ -315,6 +328,7 @@ trans.he = public/language/he/pages.json
 trans.hu = public/language/hu/pages.json
 trans.it = public/language/it/pages.json
 trans.nb = public/language/nb/pages.json
+trans.nl = public/language/nl/pages.json
 trans.pl = public/language/pl/pages.json
 trans.pt_BR = public/language/pt_BR/pages.json
 trans.ru = public/language/ru/pages.json
@@ -338,6 +352,7 @@ trans.he = public/language/he/topic.json
 trans.hu = public/language/hu/topic.json
 trans.it = public/language/it/topic.json
 trans.nb = public/language/nb/topic.json
+trans.nl = public/language/nl/topic.json
 trans.pl = public/language/pl/topic.json
 trans.pt_BR = public/language/pt_BR/topic.json
 trans.ru = public/language/ru/topic.json
diff --git a/public/language/nl/category.json b/public/language/nl/category.json
new file mode 100644
index 0000000000..af6ba62510
--- /dev/null
+++ b/public/language/nl/category.json
@@ -0,0 +1,14 @@
+{
+    "new_topic_button": "Nieuw onderwerp",
+    "no_topics": "<strong>Er zijn geen onderwerpen in deze categorie.</strong><br />Waarom maak je er niet een aan?",
+    "sidebar.recent_replies": "Recente Reacties",
+    "sidebar.active_participants": "Actieve Deelnemers",
+    "sidebar.moderators": "Moderators",
+    "posts": "berichten",
+    "views": "weergaven",
+    "posted": "geplaatst",
+    "browsing": "verkennen",
+    "no_replies": "Niemand heeft gereageerd",
+    "replied": "gereageerd",
+    "last_edited_by": "voor het laatst aangepast door"
+}
\ No newline at end of file
diff --git a/public/language/nl/footer.json b/public/language/nl/footer.json
new file mode 100644
index 0000000000..7ed4195115
--- /dev/null
+++ b/public/language/nl/footer.json
@@ -0,0 +1,7 @@
+{
+    "stats.online": "Online",
+    "stats.users": "Gebruikers",
+    "stats.topics": "Onderwerpen",
+    "stats.posts": "Berichten",
+    "success": "succes"
+}
\ No newline at end of file
diff --git a/public/language/nl/global.json b/public/language/nl/global.json
new file mode 100644
index 0000000000..d9a3048fd6
--- /dev/null
+++ b/public/language/nl/global.json
@@ -0,0 +1,58 @@
+{
+    "home": "Home",
+    "search": "Zoeken",
+    "buttons.close": "Sluiten",
+    "403.title": "Toegang Geweigerd",
+    "403.message": "Het lijkt erop dat je op een pagina beland bent waar je geen toegang tot hebt. Misschien moet je <a href='/login'>inloggen</a>?",
+    "404.title": "Niet Gevonden",
+    "404.message": "Het lijkt erop dat je op een pagina beland bent die niet bestaat. Ga terug naar de <a href='/'>home pagina</a>.",
+    "500.title": "Interne fout.",
+    "500.message": "Oeps! Het lijkt erop dat iets is fout gegaan!",
+    "register": "Registeren",
+    "login": "Inloggen",
+    "please_log_in": "Please Log In",
+    "posting_restriction_info": "Posting is currently restricted to registered members only, click here to log in.",
+    "welcome_back": "Welcome Back ",
+    "you_have_successfully_logged_in": "You have successfully logged in",
+    "logout": "Uitloggen",
+    "logout.title": "Je bent nu uitgelogd.",
+    "logout.message": "Je bent met succes uitgelogd van NodeBB",
+    "save_changes": "Aanpassingen Opslaan",
+    "close": "Sluiten",
+    "pagination": "Pagination",
+    "header.admin": "Admin",
+    "header.recent": "Recent",
+    "header.unread": "Ongelezen",
+    "header.popular": "Populair",
+    "header.users": "Gebruikers",
+    "header.chats": "Chats",
+    "header.notifications": "Notificaties",
+    "header.search": "Zoeken",
+    "header.profile": "Profiel",
+    "notifications.loading": "Notificaties Laden",
+    "chats.loading": "Chats Laden",
+    "motd.welcome": "Welkom bij NodeBB, het discussie platform van de toekomst.",
+    "motd.get": "Verkrijg NodeBB",
+    "motd.fork": "Fork",
+    "motd.like": "Like",
+    "motd.follow": "Volgen",
+    "previouspage": "Vorige Pagina",
+    "nextpage": "Volgende Pagina",
+    "alert.success": "Succes",
+    "alert.error": "Fout",
+    "alert.banned": "Verbannen",
+    "alert.banned.message": "Je bent verbannen en zal uitgelogd worden!",
+    "alert.unfollow": "Je volgt niet langer %1!",
+    "alert.follow": "Je volgt nu %1!",
+    "posts": "Berichten",
+    "views": "Weergaven",
+    "posted": "geplaatst",
+    "in": "in",
+    "recentposts": "Recente Berichten",
+    "online": "Online",
+    "away": "Afwezig",
+    "dnd": "Niet Storen",
+    "invisible": "Onzichtbaar",
+    "offline": "Offline",
+    "privacy": "Privacy"
+}
\ No newline at end of file
diff --git a/public/language/nl/language.json b/public/language/nl/language.json
new file mode 100644
index 0000000000..5490106fc4
--- /dev/null
+++ b/public/language/nl/language.json
@@ -0,0 +1,5 @@
+{
+    "name": "Nederlands",
+    "code": "nl",
+    "dir": "ltr"
+}
\ No newline at end of file
diff --git a/public/language/nl/login.json b/public/language/nl/login.json
new file mode 100644
index 0000000000..497b623098
--- /dev/null
+++ b/public/language/nl/login.json
@@ -0,0 +1,10 @@
+{
+    "login": "Inloggen",
+    "username": "Gebruikersnaam",
+    "password": "Wachtwoord",
+    "remember_me": "Mij Onthouden?",
+    "forgot_password": "Wachtwoord Vergeten?",
+    "alternative_logins": "Alternatieve Logins",
+    "failed_login_attempt": "Mislukte inlog poging, probeer het later opnieuw.",
+    "login_successful": "Je bent succesvol ingelogd!"
+}
\ No newline at end of file
diff --git a/public/language/nl/modules.json b/public/language/nl/modules.json
new file mode 100644
index 0000000000..6a9b77b68b
--- /dev/null
+++ b/public/language/nl/modules.json
@@ -0,0 +1,6 @@
+{
+    "chat.chatting_with": "Chat met <span id=\"chat-with-name\"></span>",
+    "chat.placeholder": "type chat bericht hier, druk op enter om te verzenden",
+    "chat.send": "Verzenden",
+    "chat.no_active": "Je hebt geen actieve chats."
+}
\ No newline at end of file
diff --git a/public/language/nl/notifications.json b/public/language/nl/notifications.json
new file mode 100644
index 0000000000..1b1b4bbf8c
--- /dev/null
+++ b/public/language/nl/notifications.json
@@ -0,0 +1,10 @@
+{
+    "title": "Notificaties",
+    "no_notifs": "You have no new notifications",
+    "see_all": "Bekijk alle Notificaties",
+    "back_to_home": "Terug naar NodeBB",
+    "outgoing_link": "Uitgaande Link",
+    "outgoing_link_message": "Je verlaat nu",
+    "continue_to": "Doorgaan naar",
+    "return_to": "Teruggaan naar"
+}
\ No newline at end of file
diff --git a/public/language/nl/pages.json b/public/language/nl/pages.json
new file mode 100644
index 0000000000..07a6963b89
--- /dev/null
+++ b/public/language/nl/pages.json
@@ -0,0 +1,13 @@
+{
+    "home": "Home",
+    "unread": "Ongelezen Onderwerpen",
+    "popular": "Popular Topics",
+    "recent": "Recente Onderwerpen",
+    "users": "Geregistreerde Gebruikers",
+    "notifications": "Notificaties",
+    "user.edit": "\"%1\" aanpassen",
+    "user.following": "Mensen %1 Volgt",
+    "user.followers": "Mensen die %1 Volgen",
+    "user.favourites": "%1's Favoriete Berichten",
+    "user.settings": "Gebruikersinstellingen"
+}
\ No newline at end of file
diff --git a/public/language/nl/recent.json b/public/language/nl/recent.json
new file mode 100644
index 0000000000..c9ab5e0b73
--- /dev/null
+++ b/public/language/nl/recent.json
@@ -0,0 +1,7 @@
+{
+    "title": "Recent",
+    "day": "Dag",
+    "week": "Week",
+    "month": "Maand",
+    "no_recent_topics": "Er zijn geen recente reacties."
+}
\ No newline at end of file
diff --git a/public/language/nl/register.json b/public/language/nl/register.json
new file mode 100644
index 0000000000..09dc7f786a
--- /dev/null
+++ b/public/language/nl/register.json
@@ -0,0 +1,18 @@
+{
+    "register": "Registreren",
+    "help.email": "Je email is standaard verborgen voor andere gebruikers.",
+    "help.username_restrictions": "Een unieke gebruikersnaam tussen %1 en %2 karakters. Anderen kunnen je vermelden met @<span id='yourUsername'>gebruikersnaam</span>.",
+    "help.minimum_password_length": "Je wachtwoord moet tenminste %1 karakters lang zijn.",
+    "email_address": "Email Adres",
+    "email_address_placeholder": "Vul Email Adres in",
+    "username": "Gebruikersnaam",
+    "username_placeholder": "Vul Gebruikersnaam in",
+    "password": "Wachtwoord",
+    "password_placeholder": "Vul Wachtwoord in",
+    "confirm_password": "Bevestig Wachtwoord",
+    "confirm_password_placeholder": "Bevestig Wachtwoord",
+    "register_now_button": "Nu Registreren",
+    "alternative_registration": "Alternatieve Registratie",
+    "terms_of_use": "Gebruiksvoorwaarden",
+    "agree_to_terms_of_use": "Ik ga akkoord van de Gebruiksvoorwaarden"
+}
\ No newline at end of file
diff --git a/public/language/nl/reset_password.json b/public/language/nl/reset_password.json
new file mode 100644
index 0000000000..823816168c
--- /dev/null
+++ b/public/language/nl/reset_password.json
@@ -0,0 +1,13 @@
+{
+    "reset_password": "Wachtwoord opnieuw instellen",
+    "update_password": "Wachtwoord Updaten",
+    "password_changed.title": "Wachtwoord Veranderd",
+    "password_changed.message": "<p>Wachtwoord is met succes gereset, log a.u.b. <a href=\"/login\">opnieuw in</a>.",
+    "wrong_reset_code.title": "Incorrecte Reset Code",
+    "wrong_reset_code.message": "De ontvangen reset code is incorrect. Probeer het opnieuw, of <a href=\"/reset\">vraag een nieuwe code aan</a>.",
+    "new_password": "Nieuw Wachtwoord",
+    "repeat_password": "Bevestig Wachtwoord",
+    "enter_email": "Vul a.u.b. je <strong>email address</strong> in en we versturen je een email met de stappen hoe je je account reset.",
+    "password_reset_sent": "Wachtwoord Reset Verzonden",
+    "invalid_email": "Fout Email Adres / Email Adres bestaat niet!"
+}
\ No newline at end of file
diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json
new file mode 100644
index 0000000000..a0d1dd9ffb
--- /dev/null
+++ b/public/language/nl/topic.json
@@ -0,0 +1,73 @@
+{
+    "topic": "Onderwerp",
+    "topics": "Onderwerpen",
+    "no_topics_found": "Geen onderwerpen gevonden!",
+    "no_posts_found": "Geen berichten gevonden!",
+    "profile": "Profiel",
+    "posted_by": "Geplaatst door",
+    "chat": "Chat",
+    "notify_me": "Krijg notificaties van nieuwe reacties op dit onderwerp",
+    "quote": "Citeren",
+    "reply": "Reageren",
+    "edit": "Aanpassen",
+    "delete": "Verwijderen",
+    "move": "Verplaatsen",
+    "fork": "Fork",
+    "banned": "verbannen",
+    "link": "Link",
+    "share": "Delen",
+    "tools": "Gereedschap",
+    "flag": "Markeren",
+    "flag_title": "Dit bericht markeren voor moderatie",
+    "deleted_message": "Dit onderwerp is verwijderd. Alleen gebruikers met onderwerp management privileges kunnen dit onderwerp zien.",
+    "following_topic.title": "Following Topic",
+    "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
+    "not_following_topic.title": "Not Following Topic",
+    "not_following_topic.message": "You will no longer receive notifications from this topic.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic",
+    "watch": "Watch",
+    "share_this_post": "Share this Post",
+    "thread_tools.title": "Thread Gereedschap",
+    "thread_tools.markAsUnreadForAll": "Ongelezen Markeren",
+    "thread_tools.pin": "Onderwerp Vastmaken",
+    "thread_tools.unpin": "Onderwerp Losmaken",
+    "thread_tools.lock": "Onderwerp Sluiten",
+    "thread_tools.unlock": "Onderwerp Openen",
+    "thread_tools.move": "Onderwerp Verplaatsen",
+    "thread_tools.fork": "Onderwerp Forken",
+    "thread_tools.delete": "Onderwerp Verwijderen",
+    "thread_tools.restore": "Onderwerp Herstellen",
+    "load_categories": "Categorieën Laden",
+    "disabled_categories_note": "Uitgeschakelde Categorieën zijn grijs",
+    "confirm_move": "Verplaatsen",
+    "confirm_fork": "Fork",
+    "favourite": "Favoriet",
+    "favourites": "Favorieten",
+    "favourites.not_logged_in.title": "Niet Ingelogd",
+    "favourites.not_logged_in.message": "Log a.u.b. in om dit bericht als Favoriet op te slaan",
+    "favourites.has_no_favourites": "Je hebt geen favorieten, sla een aantal berichten op als favoriet om ze hier te zien!",
+    "vote.not_logged_in.title": "Niet Ingelogd",
+    "vote.not_logged_in.message": "Log a.u.b. in om te kunnen stemmen",
+    "vote.cant_vote_self.title": "Ongeldige Stem",
+    "vote.cant_vote_self.message": "Je kan niet op je eigen berichten stemmen",
+    "loading_more_posts": "Meer Berichten Laden",
+    "move_topic": "Onderwerp Verplaatsen",
+    "move_post": "Bericht Verplaatsen",
+    "fork_topic": "Onderwerp Forken",
+    "topic_will_be_moved_to": "Dit onderwerp zal verplaatst worden naar de categorie",
+    "fork_topic_instruction": "Klik op de berichten die je wilt forken",
+    "fork_no_pids": "Geen berichten geselecteerd!",
+    "fork_success": "Onderwerp is met succes geforkt!",
+    "reputation": "Reputatie",
+    "posts": "Berichten",
+    "composer.title_placeholder": "Vul de titel voor het onderwerp hier in...",
+    "composer.write": "Schrijven",
+    "composer.preview": "Voorbeeld",
+    "composer.discard": "Annuleren",
+    "composer.submit": "Opslaan",
+    "composer.replying_to": "Reageren op",
+    "composer.new_topic": "Nieuw Onderwerp",
+    "composer.drag_and_drop_images": "Drag and Drop Images Here",
+    "composer.content_is_parsed_with": "Content is parsed with",
+    "composer.upload_instructions": "Upload images by dragging & dropping them."
+}
\ No newline at end of file
diff --git a/public/language/nl/unread.json b/public/language/nl/unread.json
new file mode 100644
index 0000000000..ce0b9a43e9
--- /dev/null
+++ b/public/language/nl/unread.json
@@ -0,0 +1,6 @@
+{
+    "title": "Ongelezen",
+    "no_unread_topics": "Er zijn geen ongelezen onderwerpen",
+    "mark_all_read": "Alles markeren als Gelezen",
+    "load_more": "Meer Laden"
+}
\ No newline at end of file
diff --git a/public/language/nl/user.json b/public/language/nl/user.json
new file mode 100644
index 0000000000..ec931aee06
--- /dev/null
+++ b/public/language/nl/user.json
@@ -0,0 +1,47 @@
+{
+    "banned": "Verbannen",
+    "offline": "Offline",
+    "username": "Gebruikersnaam",
+    "email": "Email",
+    "fullname": "Volledige Naam",
+    "website": "Website",
+    "location": "Locatie",
+    "age": "Leeftijd",
+    "joined": "Geregistreerd",
+    "lastonline": "Laatst Online",
+    "profile": "Profiel",
+    "profile_views": "Profiel weergaven",
+    "reputation": "Reputatie",
+    "posts": "Berichten",
+    "favourites": "Favorieten",
+    "followers": "Volgers",
+    "following": "Volgend",
+    "signature": "Handtekening",
+    "gravatar": "Gravatar",
+    "birthday": "Verjaardag",
+    "chat": "Chat",
+    "follow": "Follow",
+    "unfollow": "Unfollow",
+    "change_picture": "Afbeelding Aanpassen",
+    "edit": "Aanpassen",
+    "uploaded_picture": "Afbeelding Uploaden",
+    "upload_new_picture": "Nieuwe Afbeelding Uploaden",
+    "current_password": "Current Password",
+    "change_password": "Wachtwoord Aanpassen",
+    "confirm_password": "Bevestig Wachtwoord",
+    "password": "Wachtwoord",
+    "upload_picture": "Afbeelding Uploaden",
+    "upload_a_picture": "Upload een afbeelding",
+    "image_spec": "You may only upload PNG, JPG, or GIF files",
+    "max": "max.",
+    "settings": "Instellingen",
+    "show_email": "Laat Mijn Email Zien",
+    "has_no_follower": "Deze gebruiker heeft geen volgers :(",
+    "follows_no_one": "Deze gebruiker volgt niemand :(",
+    "has_no_posts": "Deze gebruiker heeft nog geen berichten geplaatst",
+    "email_hidden": "Email Verborgen",
+    "hidden": "verborgen",
+    "paginate_description": "Paginate topics and posts instead of using infinite scroll.",
+    "topics_per_page": "Topics per Page",
+    "posts_per_page": "Posts per Page"
+}
\ No newline at end of file
diff --git a/public/language/nl/users.json b/public/language/nl/users.json
new file mode 100644
index 0000000000..613f94a45d
--- /dev/null
+++ b/public/language/nl/users.json
@@ -0,0 +1,9 @@
+{
+    "latest_users": "Nieuwste Gebruikers",
+    "top_posters": "Meest Actief",
+    "most_reputation": "Meeste Reputatie",
+    "online": "Online",
+    "search": "Zoeken",
+    "enter_username": "Vul een gebruikersnaam in om te zoeken",
+    "load_more": "Meer Laden"
+}
\ No newline at end of file

From 8da7a6f2f385df7d357c11f013678b41a24c3019 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 26 Feb 2014 15:32:32 -0500
Subject: [PATCH 076/193] cleanup

---
 src/categories.js      |  2 +-
 src/favourites.js      | 49 +++++-------------------
 src/socket.io/posts.js | 46 +++++++++++++----------
 src/topics.js          | 84 ++++++++++++------------------------------
 4 files changed, 62 insertions(+), 119 deletions(-)

diff --git a/src/categories.js b/src/categories.js
index da9f84a8af..5eafaff6cb 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -424,6 +424,6 @@ var db = require('./database'),
 
 			Categories.addActiveUser(cid, uid, timestamp);
 		});
-	}
+	};
 
 }(exports));
\ No newline at end of file
diff --git a/src/favourites.js b/src/favourites.js
index 7792cee454..8084647bdd 100644
--- a/src/favourites.js
+++ b/src/favourites.js
@@ -153,24 +153,13 @@ var async = require('async'),
 			downvoted: function(next) {
 				db.isSetMember('pid:' + pid + ':downvote', uid, next);
 			}
-		}, function(err, results) {
-			callback(err, results)
-		});
+		}, callback);
 	};
 
 	Favourites.getVoteStatusByPostIDs = function(pids, uid, callback) {
-		var data = {};
-
-		function iterator(pid, next) {
-			Favourites.hasVoted(pid, uid, function(err, voteStatus) {
-				data[pid] = voteStatus;
-				next()
-			});
-		}
-
-		async.each(pids, iterator, function(err) {
-			callback(data);
-		});
+		async.map(pids, function(pid, next) {
+			Favourites.hasVoted(pid, uid, next);
+		}, callback);
 	};
 
 	Favourites.favourite = function (pid, room_id, uid, socket) {
@@ -248,33 +237,15 @@ var async = require('async'),
 	};
 
 	Favourites.getFavouritesByPostIDs = function(pids, uid, callback) {
-		var data = {};
-
-		function iterator(pid, next) {
-			Favourites.hasFavourited(pid, uid, function(err, hasFavourited) {
-				data[pid] = hasFavourited;
-				next()
-			});
-		}
-
-		async.each(pids, iterator, function(err) {
-			callback(data);
-		});
+		async.map(pids, function(pid, next) {
+			Favourites.hasFavourited(pid, uid, next);
+		}, callback);
 	};
 
 	Favourites.getFavouritedUidsByPids = function(pids, callback) {
-		var data = {};
-
-		function getUids(pid, next) {
-			db.getSetMembers('pid:' + pid + ':users_favourited', function(err, uids) {
-				data[pid] = uids;
-				next();
-			});
-		}
-
-		async.each(pids, getUids, function(err) {
-			callback(data);
-		});
+		async.map(pids, function(pid, next) {
+			db.getSetMembers('pid:' + pid + ':users_favourited', next);
+		}, callback)
 	};
 
 }(exports));
\ No newline at end of file
diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js
index 7a0475354e..049735b71c 100644
--- a/src/socket.io/posts.js
+++ b/src/socket.io/posts.js
@@ -207,30 +207,38 @@ SocketPosts.getPrivileges = function(socket, pid, callback) {
 
 SocketPosts.getFavouritedUsers = function(socket, pid, callback) {
 
-	favourites.getFavouritedUidsByPids([pid], function(data) {
+	favourites.getFavouritedUidsByPids([pid], function(err, data) {
+
+		if(err) {
+			return callback(err);
+		}
+
+		if(!Array.isArray(data) || !data.length) {
+			callback(null, "");
+		}
+
+		console.log(data);
 		var max = 5; //hardcoded
 		var usernames = "";
 
-		var pid_uids = data[pid];
+		var pid_uids = data[0];
 		var rest_amount = 0;
-		if (data.hasOwnProperty(pid) && pid_uids.length > 0) {
-			if (pid_uids.length > max) {
-				rest_amount = pid_uids.length - max;
-				pid_uids = pid_uids.slice(0, max);
-			}
-			user.getUsernamesByUids(pid_uids, function(err, result) {
-				if(err) {
-					return callback(err);
-				}
-
-				usernames = result.join(', ') + (rest_amount > 0
-					? " and " + rest_amount + (rest_amount > 1 ? " others" : " other")
-					: "");
-				callback(null, usernames);
-			});
-		} else {
-			callback(null, "");
+
+		if (pid_uids.length > max) {
+			rest_amount = pid_uids.length - max;
+			pid_uids = pid_uids.slice(0, max);
 		}
+
+		user.getUsernamesByUids(pid_uids, function(err, result) {
+			if(err) {
+				return callback(err);
+			}
+
+			usernames = result.join(', ') + (rest_amount > 0
+				? " and " + rest_amount + (rest_amount > 1 ? " others" : " other")
+				: "");
+			callback(null, usernames);
+		});
 	});
 };
 
diff --git a/src/topics.js b/src/topics.js
index 05d12787b8..fe062b8b52 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -350,12 +350,7 @@ var async = require('async'),
 		});
 	};
 
-	Topics.getTopicPosts = function(tid, start, end, current_user, reverse, callback) {
-		if (typeof reverse === 'function') {
-			callback = reverse;
-			reverse = false;
-		}
-
+	Topics.getTopicPosts = function(tid, start, end, uid, reverse, callback) {
 		posts.getPostsByTid(tid, start, end, reverse, function(err, postData) {
 			if(err) {
 				return callback(err);
@@ -373,65 +368,34 @@ var async = require('async'),
 				return post.pid;
 			});
 
-			function getFavouritesData(next) {
-				favourites.getFavouritesByPostIDs(pids, current_user, function(fav_data) {
-					next(null, fav_data);
-				});
-			}
-
-			function getVoteStatusData(next) {
-				favourites.getVoteStatusByPostIDs(pids, current_user, function(vote_data) {
-					next(null, vote_data);
-				})
-			}
-
-			function addUserInfoToPosts(next) {
-				function iterator(post, callback) {
-					posts.addUserInfoToPost(post, function() {
-						callback(null);
-					});
-				}
-
-				async.each(postData, iterator, function(err) {
-					next(err, null);
-				});
-			}
-
-			function getPrivileges(next) {
-				var privs = {};
-				async.each(pids, getPostPrivileges, function(err) {
-					next(err, privs);
-				});
-
-				function getPostPrivileges(pid, next) {
-					postTools.privileges(pid, current_user, function(err, postPrivileges) {
-						if(err) {
-							return next(err);
-						}
-						privs[pid] = postPrivileges;
-						next();
-					});
+			async.parallel({
+				favourites : function(next) {
+					favourites.getFavouritesByPostIDs(pids, uid, next);
+				},
+				voteData : function(next) {
+					favourites.getVoteStatusByPostIDs(pids, uid, next);
+				},
+				userData : function(next) {
+					async.each(postData, posts.addUserInfoToPost, next);
+				},
+				privileges : function(next) {
+					async.map(pids, function (pid, next) {
+						postTools.privileges(pid, uid, next);
+					}, next);
 				}
-			}
-
-			async.parallel([getFavouritesData, addUserInfoToPosts, getPrivileges, getVoteStatusData], function(err, results) {
+			}, function(err, results) {
 				if(err) {
 					return callback(err);
 				}
 
-				var fav_data = results[0],
-					privileges = results[2],
-					voteStatus = results[3];
-
 				for (var i = 0; i < postData.length; ++i) {
-					var pid = postData[i].pid;
-					postData[i].favourited = fav_data[pid];
-					postData[i].upvoted = voteStatus[pid].upvoted;
-					postData[i].downvoted = voteStatus[pid].downvoted;
+					postData[i].favourited = results.favourites[i];
+					postData[i].upvoted = results.voteData[i].upvoted;
+					postData[i].downvoted = results.voteData[i].downvoted;
 					postData[i].votes = postData[i].votes || 0;
-					postData[i].display_moderator_tools = (current_user != 0) && privileges[pid].editable;
-					postData[i].display_move_tools = privileges[pid].move;
-					if(parseInt(postData[i].deleted, 10) === 1 && !privileges[pid].view_deleted) {
+					postData[i].display_moderator_tools = (uid != 0) && results.privileges[i].editable;
+					postData[i].display_move_tools = results.privileges[i].move;
+					if(parseInt(postData[i].deleted, 10) === 1 && !results.privileges[i].view_deleted) {
 						postData[i].content = 'This post is deleted!';
 					}
 				}
@@ -696,7 +660,7 @@ var async = require('async'),
 		function isTopicVisible(topicData, topicInfo) {
 			var deleted = parseInt(topicData.deleted, 10) !== 0;
 
-			return !deleted || (deleted && topicInfo.privileges.view_deleted) || topicData.uid === current_user;
+			return !deleted || (deleted && topicInfo.privileges.view_deleted) || parseInt(topicData.uid, 10) === parseInt(current_user, 10);
 		}
 
 		function loadTopic(tid, next) {
@@ -765,7 +729,7 @@ var async = require('async'),
 			}
 
 			function getTopicPosts(next) {
-				Topics.getTopicPosts(tid, start, end, current_user, next);
+				Topics.getTopicPosts(tid, start, end, current_user, false, next);
 			}
 
 			function getPrivileges(next) {

From 5145ba1aaca8ea9fa5272e4c74d9ab4456b59a32 Mon Sep 17 00:00:00 2001
From: psychobunny <psycho.bunny@hotmail.com>
Date: Wed, 26 Feb 2014 15:58:42 -0500
Subject: [PATCH 077/193] added a route to get moderators by category id

---
 src/routes/api.js | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/routes/api.js b/src/routes/api.js
index 7cdd7a8589..2d22903989 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -503,6 +503,14 @@ var path = require('path'),
 			app.get('/500', function(req, res) {
 				res.json({errorMessage: 'testing'});
 			});
+
+			app.namespace('/categories', function() {
+				app.get(':cid/moderators', function(req, res) {
+					categories.getModerators(req.params.cid, function(err, moderators) {
+						res.json({moderators: moderators});
+					})
+				});
+			});
 		});
 	}
 }(exports));

From ea6cf3bbd576be7a9f06f155fc804b14f58daf55 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 26 Feb 2014 16:43:21 -0500
Subject: [PATCH 078/193] more cleanup and changes to topics

---
 public/templates/category.tpl          |  12 +--
 public/templates/noscript/category.tpl |   6 +-
 public/templates/noscript/topic.tpl    |   4 +-
 public/templates/topic.tpl             |  12 +--
 src/categories.js                      |  42 ++++------
 src/routes/api.js                      |  53 ++++++++-----
 src/routes/feeds.js                    |  12 +--
 src/socket.io/posts.js                 |   1 -
 src/socket.io/topics.js                |   2 +-
 src/topics.js                          | 103 +++++++++----------------
 src/webserver.js                       |  16 ++--
 tests/categories.js                    |   4 +-
 12 files changed, 119 insertions(+), 148 deletions(-)

diff --git a/public/templates/category.tpl b/public/templates/category.tpl
index 24afd18f2d..d536a201cb 100644
--- a/public/templates/category.tpl
+++ b/public/templates/category.tpl
@@ -1,9 +1,15 @@
+
+<input type="hidden" template-variable="category_id" value="{cid}" />
+<input type="hidden" template-variable="category_name" value="{name}" />
+<input type="hidden" template-variable="currentPage" value="{currentPage}" />
+<input type="hidden" template-variable="pageCount" value="{pageCount}" />
+
 <ol class="breadcrumb">
 	<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
 		<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
 	</li>
 	<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
-		<span itemprop="title">{category_name} <a target="_blank" href="../{category_id}.rss"><i class="fa fa-rss-square"></i></a></span>
+		<span itemprop="title">{name} <a target="_blank" href="../{cid}.rss"><i class="fa fa-rss-square"></i></a></span>
 	</li>
 </ol>
 
@@ -109,7 +115,3 @@
 	<!-- ENDIF topics.length -->
 </div>
 
-<input type="hidden" template-variable="category_id" value="{category_id}" />
-<input type="hidden" template-variable="category_name" value="{category_name}" />
-<input type="hidden" template-variable="currentPage" value="{currentPage}" />
-<input type="hidden" template-variable="pageCount" value="{pageCount}" />
\ No newline at end of file
diff --git a/public/templates/noscript/category.tpl b/public/templates/noscript/category.tpl
index ccfeffd740..52122e121c 100644
--- a/public/templates/noscript/category.tpl
+++ b/public/templates/noscript/category.tpl
@@ -3,17 +3,17 @@
 				<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
 			</li>
 			<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
-				<span itemprop="title">{category_name} <a target="_blank" href="../{category_id}.rss"><i class="fa fa-rss-square"></i></a></span>
+				<span itemprop="title">{name} <a target="_blank" href="../{cid}.rss"><i class="fa fa-rss-square"></i></a></span>
 			</li>
 		</ol>
 		<ul class="topics" itemscope itemtype="http://www.schema.org/ItemList" data-nextstart="{nextStart}">
 			<!-- BEGIN topics -->
 			<li itemprop="itemListElement">
 				<meta itemprop="name" content="{topics.title}">
-				<span class="timestamp">{topics.teaser_timestamp}</span>
+				<span class="timestamp">{topics.teaser.timestamp}</span>
 				<a href="../../topic/{topics.slug}" itemprop="url">{topics.title} ({topics.postcount})</a>
 				<div class="teaser">
-					<img class="img-thumbnail" src="{topics.teaser_userpicture}" />
+					<img class="img-thumbnail" src="{topics.teaser.picture}" />
 					<div class="clear"></div>
 				</div>
 			</li>
diff --git a/public/templates/noscript/topic.tpl b/public/templates/noscript/topic.tpl
index f3d5180fe2..cf8b968a90 100644
--- a/public/templates/noscript/topic.tpl
+++ b/public/templates/noscript/topic.tpl
@@ -3,10 +3,10 @@
 				<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
 			</li>
 			<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
-				<a href="{relative_path}/category/{category_slug}" itemprop="url"><span itemprop="title">{category_name}</span></a>
+				<a href="{relative_path}/category/{category.slug}" itemprop="url"><span itemprop="title">{category.name}</span></a>
 			</li>
 			<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
-				<span itemprop="title">{topic_name} <a target="_blank" href="../{topic_id}.rss"><i class="fa fa-rss-square"></i></a></span>
+				<span itemprop="title">{title} <a target="_blank" href="../{tid}.rss"><i class="fa fa-rss-square"></i></a></span>
 			</li>
 		</ol>
 		<ul class="posts">
diff --git a/public/templates/topic.tpl b/public/templates/topic.tpl
index ef0d794d88..3b6cfa72e8 100644
--- a/public/templates/topic.tpl
+++ b/public/templates/topic.tpl
@@ -1,11 +1,11 @@
 <input type="hidden" template-variable="expose_tools" value="{expose_tools}" />
-<input type="hidden" template-variable="topic_id" value="{topic_id}" />
+<input type="hidden" template-variable="topic_id" value="{tid}" />
 <input type="hidden" template-variable="currentPage" value="{currentPage}" />
 <input type="hidden" template-variable="pageCount" value="{pageCount}" />
 <input type="hidden" template-variable="locked" value="{locked}" />
 <input type="hidden" template-variable="deleted" value="{deleted}" />
 <input type="hidden" template-variable="pinned" value="{pinned}" />
-<input type="hidden" template-variable="topic_name" value="{topic_name}" />
+<input type="hidden" template-variable="topic_name" value="{title}" />
 <input type="hidden" template-variable="postcount" value="{postcount}" />
 
 <div class="topic">
@@ -14,14 +14,14 @@
 			<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
 		</li>
 		<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
-			<a href="{relative_path}/category/{category_slug}" itemprop="url"><span itemprop="title">{category_name}</span></a>
+			<a href="{relative_path}/category/{category.slug}" itemprop="url"><span itemprop="title">{category.name}</span></a>
 		</li>
 		<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
-			<span itemprop="title">{topic_name} <a target="_blank" href="../{topic_id}.rss"><i class="fa fa-rss-square"></i></a></span>
+			<span itemprop="title">{title} <a target="_blank" href="../{tid}.rss"><i class="fa fa-rss-square"></i></a></span>
 		</li>
 	</ol>
 
-	<ul id="post-container" class="posts" data-tid="{topic_id}">
+	<ul id="post-container" class="posts" data-tid="{tid}">
 		<!-- BEGIN posts -->
 			<li class="post-row infiniteloaded" data-pid="{posts.pid}" data-uid="{posts.uid}" data-username="{posts.username}" data-userslug="{posts.userslug}" data-index="{posts.index}" data-deleted="{posts.deleted}" itemscope itemtype="http://schema.org/Comment">
 				<a id="post_anchor_{posts.pid}" name="{posts.pid}"></a>
@@ -44,7 +44,7 @@
 						<img itemprop="image" src="{posts.picture}" align="left" class="img-thumbnail" width=150 height=150 />
 					</a>
 					<h3 class="main-post">
-						<p id="topic_title_{posts.pid}" class="topic-title" itemprop="name">{topic_name}</p>
+						<p id="topic_title_{posts.pid}" class="topic-title" itemprop="name">{title}</p>
 					</h3>
 
 					<div class="topic-buttons">
diff --git a/src/categories.js b/src/categories.js
index 5eafaff6cb..577fa61fac 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -59,39 +59,27 @@ var db = require('./database'),
 			Categories.markAsRead(cid, uid);
 		}
 
-		function getCategoryData(next) {
-			Categories.getCategoryData(cid, next);
-		}
-
-		function getTopics(next) {
-			Categories.getCategoryTopics(cid, start, end, uid, next);
-		}
-
-		function getPageCount(next) {
-			Categories.getPageCount(cid, uid, next);
-		}
-
 		async.parallel({
-			'category': getCategoryData,
-			'topics': getTopics,
-			'pageCount': getPageCount
+			category: function(next) {
+				Categories.getCategoryData(cid, next);
+			},
+			topics : function(next) {
+				Categories.getCategoryTopics(cid, start, end, uid, next);
+			},
+			pageCount: function(next) {
+				Categories.getPageCount(cid, uid, next);
+			}
 		}, function(err, results) {
 			if(err) {
 				return callback(err);
 			}
 
-			var category = {
-				'category_name': results.category.name,
-				'category_description': results.category.description,
-				'link': results.category.link,
-				'disabled': results.category.disabled,
-				'topic_row_size': 'col-md-9',
-				'category_id': cid,
-				'topics': results.topics.topics,
-				'nextStart': results.topics.nextStart,
-				'pageCount': results.pageCount,
-				'disableSocialButtons': meta.config.disableSocialButtons !== undefined ? parseInt(meta.config.disableSocialButtons, 10) !== 0 : false,
-			};
+			var category = results.category;
+			category.topics = results.topics.topics;
+			category.nextStart = results.topics.nextStart;
+			category.pageCount = results.pageCount;
+			category.disableSocialButtons = meta.config.disableSocialButtons !== undefined ? parseInt(meta.config.disableSocialButtons, 10) !== 0 : false;
+			category.topic_row_size = 'col-md-9';
 
 			callback(null, category);
 		});
diff --git a/src/routes/api.js b/src/routes/api.js
index 7cdd7a8589..89ec9fe2f4 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -169,7 +169,8 @@ var path = require('path'),
 			});
 
 			app.get('/topic/:id/:slug?', function (req, res, next) {
-				var uid = (req.user) ? req.user.uid : 0;
+				var uid = req.user? parseInt(req.user.uid, 10) : 0;
+				var tid = req.params.id;
 				var page = 1;
 				if(req.query && req.query.page) {
 					page = req.query.page;
@@ -187,29 +188,41 @@ var path = require('path'),
 					var start = (page - 1) * settings.postsPerPage;
 					var end = start + settings.postsPerPage - 1;
 
-					ThreadTools.privileges(req.params.id, uid, function(err, privileges) {
-						if (privileges.read) {
-							topics.getTopicWithPosts(req.params.id, uid, start, end, false, function (err, data) {
-								if(err) {
-									return next(err);
-								}
+					ThreadTools.privileges(tid, uid, function(err, privileges) {
+						if(err) {
+							return next(err);
+						}
 
-								if(page > data.pageCount) {
-									return res.send(404);
-								}
+						if(!privileges.read) {
+							res.send(403);
+						}
 
-								data.currentPage = page;
-								data.privileges = privileges;
+						topics.getTopicWithPosts(tid, uid, start, end, function (err, data) {
+							if(err) {
+								return next(err);
+							}
 
-								if (parseInt(data.deleted, 10) === 1 && parseInt(data.expose_tools, 10) === 0) {
-									return res.json(404, {});
-								}
+							if(page > data.pageCount) {
+								return res.send(404);
+							}
 
-								res.json(data);
-							});
-						} else {
-							res.send(403);
-						}
+							if (parseInt(data.deleted, 10) === 1 && parseInt(data.expose_tools, 10) === 0) {
+								return res.json(404, {});
+							}
+
+							data.currentPage = page;
+							data.privileges = privileges;
+
+							if (uid) {
+								topics.markAsRead(tid, uid, function(err) {
+									topics.pushUnreadCount(uid);
+								});
+							}
+
+							topics.increaseViewCount(tid);
+
+							res.json(data);
+						});
 					});
 				});
 			});
diff --git a/src/routes/feeds.js b/src/routes/feeds.js
index 1b18024742..e377614fb0 100644
--- a/src/routes/feeds.js
+++ b/src/routes/feeds.js
@@ -55,7 +55,7 @@
 	function generateForTopic(req, res, next) {
 		var tid = req.params.topic_id;
 
-		topics.getTopicWithPosts(tid, 0, 0, 25, true, function (err, topicData) {
+		topics.getTopicWithPosts(tid, 0, 0, 25, function (err, topicData) {
 			if (err) {
 				return next(err);
 			}
@@ -65,7 +65,7 @@
 			var author = topicData.posts.length ? topicData.posts[0].username : '';
 
 			var feed = new rss({
-					title: topicData.topic_name,
+					title: topicData.title,
 					description: description,
 					feed_url: nconf.get('url') + '/topic/' + tid + '.rss',
 					site_url: nconf.get('url') + '/topic/' + topicData.slug,
@@ -85,7 +85,7 @@
 					dateStamp = new Date(parseInt(parseInt(postData.edited, 10) === 0 ? postData.timestamp : postData.edited, 10)).toUTCString();
 
 					feed.item({
-						title: 'Reply to ' + topicData.topic_name + ' on ' + dateStamp,
+						title: 'Reply to ' + topicData.title + ' on ' + dateStamp,
 						description: postData.content,
 						url: nconf.get('url') + '/topic/' + topicData.slug + '#' + postData.pid,
 						author: postData.username,
@@ -109,10 +109,10 @@
 			}
 
 			var feed = new rss({
-					title: categoryData.category_name,
-					description: categoryData.category_description,
+					title: categoryData.name,
+					description: categoryData.description,
 					feed_url: nconf.get('url') + '/category/' + cid + '.rss',
-					site_url: nconf.get('url') + '/category/' + categoryData.category_id,
+					site_url: nconf.get('url') + '/category/' + categoryData.cid,
 					ttl: 60
 				});
 
diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js
index 049735b71c..dcbf85daa1 100644
--- a/src/socket.io/posts.js
+++ b/src/socket.io/posts.js
@@ -217,7 +217,6 @@ SocketPosts.getFavouritedUsers = function(socket, pid, callback) {
 			callback(null, "");
 		}
 
-		console.log(data);
 		var max = 5; //hardcoded
 		var usernames = "";
 
diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js
index 0950e9fd6c..e96fc2611d 100644
--- a/src/socket.io/topics.js
+++ b/src/socket.io/topics.js
@@ -247,7 +247,7 @@ SocketTopics.loadMore = function(socket, data, callback) {
 
 		async.parallel({
 			posts: function(next) {
-				topics.getTopicPosts(data.tid, start, end, socket.uid, next);
+				topics.getTopicPosts(data.tid, start, end, socket.uid, false, next);
 			},
 			privileges: function(next) {
 				threadTools.privileges(data.tid, socket.uid, next);
diff --git a/src/topics.js b/src/topics.js
index fe062b8b52..328f50ae9f 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -634,7 +634,7 @@ var async = require('async'),
 		});
 	};
 
-	Topics.getTopicsByTids = function(tids, cid, current_user, callback) {
+	Topics.getTopicsByTids = function(tids, cid, uid, callback) {
 
 		if (!Array.isArray(tids) || tids.length === 0) {
 			return callback(null, []);
@@ -643,13 +643,13 @@ var async = require('async'),
 		function getTopicInfo(topicData, callback) {
 			async.parallel({
 				hasread : function (next) {
-					Topics.hasReadTopic(topicData.tid, current_user, next);
+					Topics.hasReadTopic(topicData.tid, uid, next);
 				},
 				teaser : function (next) {
 					Topics.getTeaser(topicData.tid, next);
 				},
 				privileges : function (next) {
-					categoryTools.privileges(topicData.cid, current_user, next);
+					categoryTools.privileges(topicData.cid, uid, next);
 				},
 				categoryData : function (next) {
 					categories.getCategoryFields(topicData.cid, ['name', 'slug', 'icon'], next);
@@ -660,7 +660,7 @@ var async = require('async'),
 		function isTopicVisible(topicData, topicInfo) {
 			var deleted = parseInt(topicData.deleted, 10) !== 0;
 
-			return !deleted || (deleted && topicInfo.privileges.view_deleted) || parseInt(topicData.uid, 10) === parseInt(current_user, 10);
+			return !deleted || (deleted && topicInfo.privileges.view_deleted) || parseInt(topicData.uid, 10) === parseInt(uid, 10);
 		}
 
 		function loadTopic(tid, next) {
@@ -686,7 +686,7 @@ var async = require('async'),
 					topicData.pinned = parseInt(topicData.pinned, 10) === 1;
 					topicData.locked = parseInt(topicData.locked, 10) === 1;
 					topicData.deleted = parseInt(topicData.deleted, 10) === 1;
-					topicData.unread = !(topicInfo.hasread && parseInt(current_user, 10) !== 0);
+					topicData.unread = !(topicInfo.hasread && parseInt(uid, 10) !== 0);
 					topicData.unreplied = parseInt(topicData.postcount, 10) === 1;
 
 					topicData.category = topicInfo.categoryData;
@@ -710,78 +710,47 @@ var async = require('async'),
 		});
 	};
 
-	Topics.getTopicWithPosts = function(tid, current_user, start, end, quiet, callback) {
+	Topics.getTopicWithPosts = function(tid, uid, start, end, callback) {
 		threadTools.exists(tid, function(err, exists) {
 			if (err || !exists) {
 				return callback(err || new Error('Topic tid \'' + tid + '\' not found'));
 			}
 
-			// "quiet" is used for things like RSS feed updating, HTML parsing for non-js users, etc
-			if (!quiet) {
-				Topics.markAsRead(tid, current_user, function(err) {
-					Topics.pushUnreadCount(current_user);
-				});
-				Topics.increaseViewCount(tid);
-			}
-
-			function getTopicData(next) {
-				Topics.getTopicData(tid, next);
-			}
-
-			function getTopicPosts(next) {
-				Topics.getTopicPosts(tid, start, end, current_user, false, next);
-			}
-
-			function getPrivileges(next) {
-				threadTools.privileges(tid, current_user, next);
-			}
-
-			function getCategoryData(next) {
-				Topics.getCategoryData(tid, next);
-			}
-
-			function getPageCount(next) {
-				Topics.getPageCount(tid, current_user, next);
-			}
-
-			function getThreadTools(next) {
-				Plugins.fireHook('filter:topic.thread_tools', [], function(err, threadTools) {
-					next(err, threadTools);
-				});
-			}
-
-			async.parallel([getTopicData, getTopicPosts, getPrivileges, getCategoryData, getPageCount, getThreadTools], function(err, results) {
+			async.parallel({
+				topicData : function(next) {
+					Topics.getTopicData(tid, next);
+				},
+				posts : function(next) {
+					Topics.getTopicPosts(tid, start, end, uid, false, next);
+				},
+				privileges : function(next) {
+					threadTools.privileges(tid, uid, next);
+				},
+				category : function(next) {
+					Topics.getCategoryData(tid, next);
+				},
+				pageCount : function(next) {
+					Topics.getPageCount(tid, uid, next);
+				},
+				threadTools : function(next) {
+					Plugins.fireHook('filter:topic.thread_tools', [], next);
+				}
+			}, function(err, results) {
 				if (err) {
 					winston.error('[Topics.getTopicWithPosts] Could not retrieve topic data: ', err.message);
 					return callback(err);
 				}
 
-				var topicData = results[0],
-					privileges = results[2],
-					categoryData = results[3],
-					pageCount = results[4],
-					threadTools = results[5];
-
-				callback(null, {
-					'topic_name': topicData.title,
-					'category_name': categoryData.name,
-					'category_slug': categoryData.slug,
-					'locked': topicData.locked,
-					'deleted': topicData.deleted,
-					'pinned': topicData.pinned,
-					'timestamp': topicData.timestamp,
-					'slug': topicData.slug,
-					'thumb': topicData.thumb,
-					'postcount': topicData.postcount,
-					'viewcount': topicData.viewcount,
-					'pageCount': pageCount,
-					'unreplied': parseInt(topicData.postcount, 10) === 1,
-					'topic_id': tid,
-					'expose_tools': privileges.editable ? 1 : 0,
-					'thread_tools': threadTools,
-					'disableSocialButtons': meta.config.disableSocialButtons !== undefined ? parseInt(meta.config.disableSocialButtons, 10) !== 0 : false,
-					'posts': results[1]
-				});
+				var topicData = results.topicData;
+				topicData.category = results.category;
+				topicData.posts = results.posts;
+				topicData.thread_tools = results.threadTools;
+				topicData.pageCount = results.pageCount;
+				topicData.unreplied = parseInt(topicData.postcount, 10) === 1;
+				topicData.expose_tools = results.privileges.editable ? 1 : 0;
+				topicData.disableSocialButtons = meta.config.disableSocialButtons !== undefined ? parseInt(meta.config.disableSocialButtons, 10) !== 0 : false;
+
+				callback(null, topicData);
 			});
 		});
 	};
diff --git a/src/webserver.js b/src/webserver.js
index 9999c1a22e..1fe43f5743 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -596,7 +596,7 @@ process.on('uncaughtException', function(err) {
 						var start = (page - 1) * settings.topicsPerPage,
 							end = start + settings.topicsPerPage - 1;
 
-						topics.getTopicWithPosts(tid, uid, start, end, true, function (err, topicData) {
+						topics.getTopicWithPosts(tid, uid, start, end, function (err, topicData) {
 							if (topicData) {
 								if (parseInt(topicData.deleted, 10) === 1 && parseInt(topicData.expose_tools, 10) === 0) {
 									return next(new Error('Topic deleted'), null);
@@ -642,7 +642,7 @@ process.on('uncaughtException', function(err) {
 						metaTags: [
 							{
 								name: "title",
-								content: topicData.topic_name
+								content: topicData.title
 							},
 							{
 								name: "description",
@@ -650,7 +650,7 @@ process.on('uncaughtException', function(err) {
 							},
 							{
 								property: 'og:title',
-								content: topicData.topic_name
+								content: topicData.title
 							},
 							{
 								property: 'og:description',
@@ -682,7 +682,7 @@ process.on('uncaughtException', function(err) {
 							},
 							{
 								property: 'article:section',
-								content: topicData.category_name
+								content: topicData.category.name
 							}
 						],
 						linkTags: [
@@ -693,7 +693,7 @@ process.on('uncaughtException', function(err) {
 							},
 							{
 								rel: 'up',
-								href: nconf.get('url') + '/category/' + topicData.category_slug
+								href: nconf.get('url') + '/category/' + topicData.category.slug
 							}
 						]
 					}, function (err, header) {
@@ -783,15 +783,15 @@ process.on('uncaughtException', function(err) {
 						metaTags: [
 							{
 								name: 'title',
-								content: categoryData.category_name
+								content: categoryData.name
 							},
 							{
 								property: 'og:title',
-								content: categoryData.category_name
+								content: categoryData.name
 							},
 							{
 								name: 'description',
-								content: categoryData.category_description
+								content: categoryData.description
 							},
 							{
 								property: "og:type",
diff --git a/tests/categories.js b/tests/categories.js
index 770ad0b498..1c1e1fac32 100644
--- a/tests/categories.js
+++ b/tests/categories.js
@@ -33,8 +33,8 @@ describe('Categories', function() {
 		it('should retrieve a newly created category by its ID', function(done) {
 			Categories.getCategoryById(categoryObj.cid, 0, -1, 0, function(err, categoryData) {
 				assert(categoryData);
-				assert.equal(categoryObj.name, categoryData.category_name);
-				assert.equal(categoryObj.description, categoryData.category_description);
+				assert.equal(categoryObj.name, categoryData.name);
+				assert.equal(categoryObj.description, categoryData.description);
 
 				done();
 			});

From 7f2d70d7f6cda562692e687d6f64987fda7b3aa1 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 26 Feb 2014 16:58:02 -0500
Subject: [PATCH 079/193] minor cleanups

---
 src/categories.js | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/src/categories.js b/src/categories.js
index 577fa61fac..248fb4417f 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -63,7 +63,7 @@ var db = require('./database'),
 			category: function(next) {
 				Categories.getCategoryData(cid, next);
 			},
-			topics : function(next) {
+			topics: function(next) {
 				Categories.getCategoryTopics(cid, start, end, uid, next);
 			},
 			pageCount: function(next) {
@@ -287,14 +287,14 @@ var db = require('./database'),
 
 	Categories.getCategories = function(cids, uid, callback) {
 		if (!cids || !Array.isArray(cids) || cids.length === 0) {
-			return callback(new Error('invalid-cids'), null);
+			return callback(new Error('invalid-cids'));
 		}
 
 		function getCategory(cid, callback) {
 			Categories.getCategoryData(cid, function(err, categoryData) {
 				if (err) {
 					winston.warn('Attempted to retrieve cid ' + cid + ', but nothing was returned!');
-					return callback(err, null);
+					return callback(err);
 				}
 
 				Categories.hasReadCategory(cid, uid, function(hasRead) {
@@ -326,7 +326,7 @@ var db = require('./database'),
 
 		db.getSortedSetRange('uid:' + uid + ':posts', 0, -1, function(err, pids) {
 			if (err) {
-				return callback(err, null);
+				return callback(err);
 			}
 
 			var index = 0,
@@ -347,15 +347,11 @@ var db = require('./database'),
 						}
 
 						++index;
-						callback(null);
+						callback();
 					});
 				},
 				function(err) {
-					if (err) {
-						return callback(err, null);
-					}
-
-					callback(null, active);
+					callback(err, active);
 				}
 			);
 		});

From aee2b2ecd098dd3b7ec531d1710cb015c8b4f1ab Mon Sep 17 00:00:00 2001
From: psychobunny <psycho.bunny@hotmail.com>
Date: Wed, 26 Feb 2014 17:00:03 -0500
Subject: [PATCH 080/193] allow express to serve parsed tpls via res.render

---
 public/src/templates.js | 27 ++++++++++++++++++++++++++-
 src/webserver.js        |  4 ++++
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/public/src/templates.js b/public/src/templates.js
index 243bc59d68..2ac8f0b7fb 100644
--- a/public/src/templates.js
+++ b/public/src/templates.js
@@ -3,6 +3,7 @@
 	var config = {},
 		templates,
 		fs = null,
+		path = null,
 		available_templates = [],
 		parsed_variables = {},
 		apiXHR;
@@ -12,7 +13,8 @@
 	};
 
 	try {
-		fs = require('fs');
+		fs = require('fs'),
+		path = require('path');
 	} catch (e) {}
 
 	templates.force_refresh = function (tpl) {
@@ -127,6 +129,27 @@
 		loadTemplates(templates_to_load || [], custom_templates || false);
 	}
 
+	templates.render = function(filename, options, fn) {
+		if ('function' === typeof options) {
+			fn = options, options = false;
+		}
+
+		var tpl = filename
+			.replace(path.join(__dirname + '/../templates/'), '')
+			.replace('.' + options.settings['view engine'], '');
+
+		if (!templates[tpl]) {
+			fs.readFile(filename, function (err, html) {
+				templates[tpl] = html.toString();
+				templates.prepare(templates[tpl]);
+
+				return fn(err, templates[tpl].parse(options));
+			});
+		} else {
+			return fn(null, templates[tpl].parse(options));
+		}
+	}
+
 	templates.getTemplateNameFromUrl = function (url) {
 		var parts = url.split('?')[0].split('/');
 
@@ -419,6 +442,8 @@
 		})(data, "", template);
 	}
 
+	module.exports.__express = module.exports.render;
+
 	if ('undefined' !== typeof window) {
 		window.templates = module.exports;
 		templates.init();
diff --git a/src/webserver.js b/src/webserver.js
index 9999c1a22e..530f434105 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -226,6 +226,10 @@ process.on('uncaughtException', function(err) {
 
 	// Middlewares
 	app.configure(function() {
+		app.engine('tpl', templates.__express);
+		app.set('view engine', 'tpl');
+		app.set('views', path.join(__dirname, '../public/templates'));
+
 		async.series([
 			function(next) {
 				// Pre-router middlewares

From 56bbeb9950102f18d81c7546a36663fd7a6c21a0 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 26 Feb 2014 17:16:55 -0500
Subject: [PATCH 081/193] use disableSocialButtons from config

---
 public/templates/category.tpl | 4 ++--
 public/templates/topic.tpl    | 4 ++--
 src/categories.js             | 1 -
 src/routes/api.js             | 1 +
 src/topics.js                 | 2 +-
 5 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/public/templates/category.tpl b/public/templates/category.tpl
index d536a201cb..25bc8c0a31 100644
--- a/public/templates/category.tpl
+++ b/public/templates/category.tpl
@@ -17,13 +17,13 @@
 	<!-- IF privileges.write -->
 	<button id="new_post" class="btn btn-primary">[[category:new_topic_button]]</button>
 	<!-- ENDIF privileges.write -->
-	<!-- IF !disableSocialButtons -->
+	<!-- IF !config.disableSocialButtons -->
 	<div class="inline-block pull-right">
 		<a href="#" id="facebook-share"><i class="fa fa-facebook-square fa-2x"></i></a>&nbsp;
 		<a href="#" id="twitter-share"><i class="fa fa-twitter-square fa-2x"></i></a>&nbsp;
 		<a href="#" id="google-share"><i class="fa fa-google-plus-square fa-2x"></i></a>&nbsp;
 	</div>
-	<!-- ENDIF !disableSocialButtons -->
+	<!-- ENDIF !config.disableSocialButtons -->
 </div>
 
 <hr/>
diff --git a/public/templates/topic.tpl b/public/templates/topic.tpl
index 3b6cfa72e8..6b58c9f8a8 100644
--- a/public/templates/topic.tpl
+++ b/public/templates/topic.tpl
@@ -99,11 +99,11 @@
 								<div class="dropdown share-dropdown">
 									<button title="[[topic:share]]"class="btn btn-sm btn-default share" data-toggle="dropdown" href="#"><i class="fa fa-share-square-o"></i></button>
 									<ul class="dropdown-menu text-center pull-right" role="menu" aria-labelledby="dLabel">
-										<!-- IF !disableSocialButtons -->
+										<!-- IF !config.disableSocialButtons -->
 										<li class="btn btn-sm btn-default facebook-share" type="button" title=""><i class="fa fa-facebook fa-fw"></i></li>
 										<li class="btn btn-sm btn-default twitter-share" type="button" title=""><i class="fa fa-twitter fa-fw"></i></li>
 										<li class="btn btn-sm btn-default google-share" type="button" title=""><i class="fa fa-google-plus fa-fw"></i></li>
-										<!-- ENDIF !disableSocialButtons -->
+										<!-- ENDIF !config.disableSocialButtons -->
 										<li>
 											<input type="text" id="post_{posts.pid}_link" value="" class="form-control pull-right post-link" style=""></input>
 										<li>
diff --git a/src/categories.js b/src/categories.js
index 248fb4417f..2433953e35 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -78,7 +78,6 @@ var db = require('./database'),
 			category.topics = results.topics.topics;
 			category.nextStart = results.topics.nextStart;
 			category.pageCount = results.pageCount;
-			category.disableSocialButtons = meta.config.disableSocialButtons !== undefined ? parseInt(meta.config.disableSocialButtons, 10) !== 0 : false;
 			category.topic_row_size = 'col-md-9';
 
 			callback(null, category);
diff --git a/src/routes/api.js b/src/routes/api.js
index 9c1f745ab6..4e2f601c8f 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -60,6 +60,7 @@ var path = require('path'),
 				config.allowFileUploads = parseInt(meta.config.allowFileUploads, 10) === 1;
 				config.allowTopicsThumbnail = parseInt(meta.config.allowTopicsThumbnail, 10) === 1;
 				config.usePagination = parseInt(meta.config.usePagination, 10) === 1;
+				config.disableSocialButtons = parseInt(meta.config.disableSocialButtons, 10) === 1;
 				config.topicsPerPage = meta.config.topicsPerPage || 20;
 				config.postsPerPage = meta.config.postsPerPage || 20;
 				config.maximumFileSize = meta.config.maximumFileSize;
diff --git a/src/topics.js b/src/topics.js
index 328f50ae9f..2fdb2f151a 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -395,6 +395,7 @@ var async = require('async'),
 					postData[i].votes = postData[i].votes || 0;
 					postData[i].display_moderator_tools = (uid != 0) && results.privileges[i].editable;
 					postData[i].display_move_tools = results.privileges[i].move;
+
 					if(parseInt(postData[i].deleted, 10) === 1 && !results.privileges[i].view_deleted) {
 						postData[i].content = 'This post is deleted!';
 					}
@@ -748,7 +749,6 @@ var async = require('async'),
 				topicData.pageCount = results.pageCount;
 				topicData.unreplied = parseInt(topicData.postcount, 10) === 1;
 				topicData.expose_tools = results.privileges.editable ? 1 : 0;
-				topicData.disableSocialButtons = meta.config.disableSocialButtons !== undefined ? parseInt(meta.config.disableSocialButtons, 10) !== 0 : false;
 
 				callback(null, topicData);
 			});

From ad3771597283b873223a86c02a009bac1457e32d Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 26 Feb 2014 17:34:02 -0500
Subject: [PATCH 082/193] empty array if not topics in catgory

---
 src/categories.js | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/categories.js b/src/categories.js
index 2433953e35..cb81add98b 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -93,23 +93,23 @@ var db = require('./database'),
 				topics.getTopicsByTids(tids, cid, uid, next);
 			},
 			function(topics, next) {
-				if (topics && topics.length > 0) {
-					db.sortedSetRevRank('categories:' + cid + ':tid', topics[topics.length - 1].tid, function(err, rank) {
-						if(err) {
-							return next(err);
-						}
-
-						next(null, {
-							topics: topics,
-							nextStart: parseInt(rank, 10) + 1
-						});
+				if (!topics || !topics.length) {
+					return next(null, {
+						topics: [],
+						nextStart: 1
 					});
-				} else {
+				}
+
+				db.sortedSetRevRank('categories:' + cid + ':tid', topics[topics.length - 1].tid, function(err, rank) {
+					if(err) {
+						return next(err);
+					}
+
 					next(null, {
 						topics: topics,
-						nextStart: 1
+						nextStart: parseInt(rank, 10) + 1
 					});
-				}
+				});
 			}
 		], callback);
 	};

From 1ca1ace053fe8794e52e58797d52f6260773f30c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?=
 <barisusakli@gmail.com>
Date: Wed, 26 Feb 2014 19:45:07 -0500
Subject: [PATCH 083/193] Update README.md

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index 36c8253255..ab02209c57 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
 # <img alt="NodeBB" src="http://i.imgur.com/3yj1n6N.png" />
 [![Dependency Status](https://david-dm.org/designcreateplay/nodebb.png)](https://david-dm.org/designcreateplay/nodebb)
+[![Code Climate](https://codeclimate.com/github/designcreateplay/NodeBB.png)](https://codeclimate.com/github/designcreateplay/NodeBB)
 
 **NodeBB Forum Software** is powered by Node.js and built on a Redis database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB is compatible down to IE8 and has many modern features out of the box such as social network integration and streaming discussions.
 

From 99bf882a1c8a9dd2364c78a316804b57086bd2f9 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 26 Feb 2014 19:55:28 -0500
Subject: [PATCH 084/193] removed cid from getTopicsByTids

---
 src/categories.js |  2 +-
 src/topics.js     | 10 +++++-----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/categories.js b/src/categories.js
index cb81add98b..48af337280 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -90,7 +90,7 @@ var db = require('./database'),
 				Categories.getTopicIds(cid, start, stop, next);
 			},
 			function(tids, next) {
-				topics.getTopicsByTids(tids, cid, uid, next);
+				topics.getTopicsByTids(tids, uid, next);
 			},
 			function(topics, next) {
 				if (!topics || !topics.length) {
diff --git a/src/topics.js b/src/topics.js
index 2fdb2f151a..77b90ae645 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -134,7 +134,7 @@ var async = require('async'),
 				next(null, postData);
 			},
 			function(postData, next) {
-				Topics.getTopicsByTids([postData.tid], data.cid, uid, function(err, topicData) {
+				Topics.getTopicsByTids([postData.tid], uid, function(err, topicData) {
 					if(err) {
 						return next(err);
 					}
@@ -449,7 +449,7 @@ var async = require('async'),
 				next(!err && privileges.read);
 			});
 		}, function(tids) {
-			Topics.getTopicsByTids(tids, 0, uid, function(err, topicData) {
+			Topics.getTopicsByTids(tids, uid, function(err, topicData) {
 				if(err) {
 					return callback(err);
 				}
@@ -573,7 +573,7 @@ var async = require('async'),
 
 		function sendUnreadTopics(topicIds) {
 
-			Topics.getTopicsByTids(topicIds, 0, uid, function(err, topicData) {
+			Topics.getTopicsByTids(topicIds, uid, function(err, topicData) {
 				if(err) {
 					return callback(err);
 				}
@@ -635,7 +635,7 @@ var async = require('async'),
 		});
 	};
 
-	Topics.getTopicsByTids = function(tids, cid, uid, callback) {
+	Topics.getTopicsByTids = function(tids, uid, callback) {
 
 		if (!Array.isArray(tids) || tids.length === 0) {
 			return callback(null, []);
@@ -1100,4 +1100,4 @@ var async = require('async'),
 			});
 		});
 	};
-}(exports));
+}(exports));
\ No newline at end of file

From f1f7b59d58aa1fcb83e31e4e303f4210e4e9089f Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 26 Feb 2014 20:31:30 -0500
Subject: [PATCH 085/193] missed this one

---
 src/routes/api.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/routes/api.js b/src/routes/api.js
index 4e2f601c8f..7737f49e38 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -415,7 +415,7 @@ var path = require('path'),
 							return callback(err, null);
 						}
 
-						topics.getTopicsByTids(tids, 0, 0, callback);
+						topics.getTopicsByTids(tids, 0, callback);
 					});
 				}
 

From 6a962655e486ec1560df32ec55442dfa745a911f Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 26 Feb 2014 20:38:49 -0500
Subject: [PATCH 086/193] jshint for topics.js

---
 src/topics.js | 48 +++++++++++++++++++++++++-----------------------
 1 file changed, 25 insertions(+), 23 deletions(-)

diff --git a/src/topics.js b/src/topics.js
index 77b90ae645..1c2727f5ff 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -1,3 +1,5 @@
+"use strict";
+
 var async = require('async'),
 	gravatar = require('gravatar'),
 	path = require('path'),
@@ -53,7 +55,7 @@ var async = require('async'),
 			};
 
 			if(thumb) {
-				topicData['thumb'] = thumb;
+				topicData.thumb = thumb;
 			}
 
 			db.setObject('topic:' + tid, topicData, function(err) {
@@ -109,8 +111,8 @@ var async = require('async'),
 				categoryTools.exists(cid, next);
 			},
 			function(categoryExists, next) {
-				if(!categoryExists) {
-					return next(new Error('category doesn\'t exist'))
+				if (!categoryExists) {
+					return next(new Error('category doesn\'t exist'));
 				}
 				categoryTools.privileges(cid, uid, next);
 			},
@@ -364,7 +366,7 @@ var async = require('async'),
 				postData[i].index = start + i;
 			}
 
-			pids = postData.map(function(post) {
+			var pids = postData.map(function(post) {
 				return post.pid;
 			});
 
@@ -393,7 +395,7 @@ var async = require('async'),
 					postData[i].upvoted = results.voteData[i].upvoted;
 					postData[i].downvoted = results.voteData[i].downvoted;
 					postData[i].votes = postData[i].votes || 0;
-					postData[i].display_moderator_tools = (uid != 0) && results.privileges[i].editable;
+					postData[i].display_moderator_tools = parseInt(uid, 10) !== 0 && results.privileges[i].editable;
 					postData[i].display_move_tools = results.privileges[i].move;
 
 					if(parseInt(postData[i].deleted, 10) === 1 && !results.privileges[i].view_deleted) {
@@ -460,7 +462,7 @@ var async = require('async'),
 
 				db.sortedSetRevRank(set, topicData[topicData.length - 1].tid, function(err, rank) {
 					if(err) {
-						return calllback(err);
+						return callback(err);
 					}
 
 					returnTopics.nextStart = parseInt(rank, 10) + 1;
@@ -478,7 +480,7 @@ var async = require('async'),
 			month: 2592000000
 		};
 
-		var since = terms['day'];
+		var since = terms.day;
 		if(terms[term]) {
 			since = terms[term];
 		}
@@ -828,7 +830,7 @@ var async = require('async'),
 			if(err) {
 				return callback(err);
 			}
-			Topics.markCategoryUnreadForAll(tid, callback)
+			Topics.markCategoryUnreadForAll(tid, callback);
 		});
 	};
 
@@ -891,7 +893,7 @@ var async = require('async'),
 			return callback(null, []);
 		}
 
-		async.map(tids, Topics.getTeaser, callback)
+		async.map(tids, Topics.getTeaser, callback);
 	};
 
 	Topics.getTeaser = function(tid, callback) {
@@ -926,19 +928,19 @@ var async = require('async'),
 				});
 			});
 		});
-	}
+	};
 
 	Topics.getTopicField = function(tid, field, callback) {
 		db.getObjectField('topic:' + tid, field, callback);
-	}
+	};
 
 	Topics.getTopicFields = function(tid, fields, callback) {
 		db.getObjectFields('topic:' + tid, fields, callback);
-	}
+	};
 
 	Topics.setTopicField = function(tid, field, value, callback) {
 		db.setObjectField('topic:' + tid, field, value, callback);
-	}
+	};
 
 	Topics.increasePostCount = function(tid, callback) {
 		db.incrObjectField('topic:' + tid, 'postcount', function(err, value) {
@@ -947,7 +949,7 @@ var async = require('async'),
 			}
 			db.sortedSetAdd('topics:posts', value, tid, callback);
 		});
-	}
+	};
 
 	Topics.decreasePostCount = function(tid, callback) {
 		db.decrObjectField('topic:' + tid, 'postcount', function(err, value) {
@@ -956,7 +958,7 @@ var async = require('async'),
 			}
 			db.sortedSetAdd('topics:posts', value, tid, callback);
 		});
-	}
+	};
 
 	Topics.increaseViewCount = function(tid, callback) {
 		db.incrObjectField('topic:' + tid, 'viewcount', function(err, value) {
@@ -965,7 +967,7 @@ var async = require('async'),
 			}
 			db.sortedSetAdd('topics:views', value, tid, callback);
 		});
-	}
+	};
 
 	Topics.isLocked = function(tid, callback) {
 		Topics.getTopicField(tid, 'locked', function(err, locked) {
@@ -974,30 +976,30 @@ var async = require('async'),
 			}
 			callback(null, parseInt(locked, 10) === 1);
 		});
-	}
+	};
 
 	Topics.updateTimestamp = function(tid, timestamp) {
 		db.sortedSetAdd('topics:recent', timestamp, tid);
 		Topics.setTopicField(tid, 'lastposttime', timestamp);
-	}
+	};
 
 	Topics.onNewPostMade = function(tid, pid, timestamp, callback) {
 		Topics.increasePostCount(tid);
 		Topics.updateTimestamp(tid, timestamp);
 		Topics.addPostToTopic(tid, pid, timestamp, callback);
-	}
+	};
 
 	Topics.addPostToTopic = function(tid, pid, timestamp, callback) {
 		db.sortedSetAdd('tid:' + tid + ':posts', timestamp, pid, callback);
-	}
+	};
 
 	Topics.removePostFromTopic = function(tid, pid, callback) {
 		db.sortedSetRemove('tid:' + tid + ':posts', pid, callback);
-	}
+	};
 
 	Topics.getPids = function(tid, callback) {
 		db.getSortedSetRange('tid:' + tid + ':posts', 0, -1, callback);
-	}
+	};
 
 	Topics.getUids = function(tid, callback) {
 		var uids = {};
@@ -1021,7 +1023,7 @@ var async = require('async'),
 				callback(null, Object.keys(uids));
 			});
 		});
-	}
+	};
 
 	Topics.updateTopicCount = function(callback) {
 		db.sortedSetCard('topics:recent', function(err, count) {

From 81f476768794192e784e157c38c1ca098b7c466f Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 26 Feb 2014 21:04:20 -0500
Subject: [PATCH 087/193] user.js hint

---
 src/user.js | 56 ++++++++++++++++++++++-------------------------------
 1 file changed, 23 insertions(+), 33 deletions(-)

diff --git a/src/user.js b/src/user.js
index ae14774e82..29065c7577 100644
--- a/src/user.js
+++ b/src/user.js
@@ -1,3 +1,5 @@
+'use strict';
+
 var bcrypt = require('bcryptjs'),
 	async = require('async'),
 	nconf = require('nconf'),
@@ -17,7 +19,7 @@ var bcrypt = require('bcryptjs'),
 	Emailer = require('./emailer');
 
 (function(User) {
-	'use strict';
+
 	User.create = function(userData, callback) {
 		userData = userData || {};
 		userData.userslug = utils.slugify(userData.username);
@@ -210,7 +212,7 @@ var bcrypt = require('bcryptjs'),
 			}
 
 			if(!settings) {
-				settings = {}
+				settings = {};
 			}
 
 			settings.showemail = settings.showemail ? parseInt(settings.showemail, 10) !== 0 : false;
@@ -220,7 +222,7 @@ var bcrypt = require('bcryptjs'),
 
 			callback(null, settings);
 		});
-	}
+	};
 
 	User.saveSettings = function(uid, data, callback) {
 
@@ -234,7 +236,7 @@ var bcrypt = require('bcryptjs'),
 			topicsPerPage: data.topicsPerPage,
 			postsPerPage: data.postsPerPage
 		}, callback);
-	}
+	};
 
 	User.updateLastOnlineTime = function(uid, callback) {
 		User.getUserField(uid, 'status', function(err, status) {
@@ -418,7 +420,7 @@ var bcrypt = require('bcryptjs'),
 			}
 			callback();
 		});
-	}
+	};
 
 	User.isEmailAvailable = function(email, callback) {
 		db.isObjectField('email:uid', email, function(err, exists) {
@@ -561,29 +563,28 @@ var bcrypt = require('bcryptjs'),
 		});
 	};
 
-	// thanks to @akhoury
 	User.getUsersCSV = function(callback) {
 		var csvContent = "";
 
 		db.getObjectValues('username:uid', function(err, uids) {
+			if(err) {
+				return callback(err);
+			}
+
 			async.each(uids, function(uid, next) {
 				User.getUserFields(uid, ['email', 'username'], function(err, userData) {
 					if(err) {
 						return next(err);
 					}
 
-					csvContent += userData.email+ ',' + userData.username + ',' + uid +'\n';
+					csvContent += userData.email + ',' + userData.username + ',' + uid + '\n';
 					next();
 				});
 			}, function(err) {
-				if (err) {
-					throw err;
-				}
-
 				callback(err, csvContent);
 			});
 		});
-	}
+	};
 
 	User.search = function(query, callback) {
 		if (!query || query.length === 0) {
@@ -727,7 +728,7 @@ var bcrypt = require('bcryptjs'),
 				User.getFollowerCount(uid, next);
 			}
 		}, callback);
-	}
+	};
 
 	User.getDataForUsers = function(uids, callback) {
 
@@ -850,22 +851,11 @@ var bcrypt = require('bcryptjs'),
 	};
 
 	User.isModerator = function(uid, cid, callback) {
-		groups.isMemberByGroupName(uid, 'cid:' + cid + ':privileges:mod', function(err, isMember) {
-			if(err) {
-				return calback(err);
-			}
-			callback(err, isMember);
-		});
+		groups.isMemberByGroupName(uid, 'cid:' + cid + ':privileges:mod', callback);
 	};
 
 	User.isAdministrator = function(uid, callback) {
-		groups.getGidFromName('administrators', function(err, gid) {
-			if(err) {
-				return callback(err);
-			}
-
-			groups.isMember(uid, gid, callback);
-		});
+		groups.isMemberByGroupName(uid, 'administrators', callback);
 	};
 
 	User.reset = {
@@ -882,7 +872,7 @@ var bcrypt = require('bcryptjs'),
 							return callback(err);
 						}
 
-						if (expiry >= +Date.now() / 1000 | 0) {
+						if (parseInt(expiry, 10) >= Date.now() / 1000) {
 							callback(null, true);
 						} else {
 							// Expired, delete from db
@@ -909,15 +899,15 @@ var bcrypt = require('bcryptjs'),
 				// Generate a new reset code
 				var reset_code = utils.generateUUID();
 				db.setObjectField('reset:uid', reset_code, uid);
-				db.setObjectField('reset:expiry', reset_code, (60 * 60) + new Date() / 1000 | 0); // Active for one hour
+				db.setObjectField('reset:expiry', reset_code, (60 * 60) + Math.floor(Date.now() / 1000));
 
 				var reset_link = nconf.get('url') + '/reset/' + reset_code;
 
 				Emailer.send('reset', uid, {
-					'site_title': (meta.config['title'] || 'NodeBB'),
+					'site_title': (meta.config.title || 'NodeBB'),
 					'reset_link': reset_link,
 
-					subject: 'Password Reset Requested - ' + (meta.config['title'] || 'NodeBB') + '!',
+					subject: 'Password Reset Requested - ' + (meta.config.title || 'NodeBB') + '!',
 					template: 'reset',
 					uid: uid
 				});
@@ -1003,11 +993,11 @@ var bcrypt = require('bcryptjs'),
 				// Send intro email w/ confirm code
 				User.getUserField(uid, 'username', function(err, username) {
 					Emailer.send('welcome', uid, {
-						'site_title': (meta.config['title'] || 'NodeBB'),
+						'site_title': (meta.config.title || 'NodeBB'),
 						username: username,
 						'confirm_link': confirm_link,
 
-						subject: 'Welcome to ' + (meta.config['title'] || 'NodeBB') + '!',
+						subject: 'Welcome to ' + (meta.config.title || 'NodeBB') + '!',
 						template: 'welcome',
 						uid: uid
 					});
@@ -1086,7 +1076,7 @@ var bcrypt = require('bcryptjs'),
 				}
 			}, function(err, notifications) {
 				if(err) {
-					return calback(err);
+					return callback(err);
 				}
 
 				// Remove empties

From 04b2887d883f587196b0686f913456c25ebe6950 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Wed, 26 Feb 2014 21:10:05 -0500
Subject: [PATCH 088/193] fixed it language code

---
 public/language/it/language.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/language/it/language.json b/public/language/it/language.json
index b749dfac01..76bc29d1e3 100644
--- a/public/language/it/language.json
+++ b/public/language/it/language.json
@@ -1,5 +1,5 @@
 {
     "name": "Italiano",
-    "code": "it_IT",
+    "code": "it",
     "dir": "ltr"
 }
\ No newline at end of file

From b59c10a1e979b285e3a45c4bc0449fdc5fda43d9 Mon Sep 17 00:00:00 2001
From: akhoury <bentael@gmail.com>
Date: Wed, 26 Feb 2014 21:49:22 -0500
Subject: [PATCH 089/193] took out some overrides out utils to a new
 overrides.js

---
 public/src/overrides.js           | 51 +++++++++++++++++++++++++++++++
 public/templates/admin/header.tpl |  2 +-
 src/meta.js                       |  1 +
 3 files changed, 53 insertions(+), 1 deletion(-)
 create mode 100644 public/src/overrides.js

diff --git a/public/src/overrides.js b/public/src/overrides.js
new file mode 100644
index 0000000000..df4ab27f24
--- /dev/null
+++ b/public/src/overrides.js
@@ -0,0 +1,51 @@
+if ('undefined' !== typeof window) {
+
+	(function ($, undefined) {
+		$.fn.getCursorPosition = function() {
+			var el = $(this).get(0);
+			var pos = 0;
+			if('selectionStart' in el) {
+				pos = el.selectionStart;
+			} else if('selection' in document) {
+				el.focus();
+				var Sel = document.selection.createRange();
+				var SelLength = document.selection.createRange().text.length;
+				Sel.moveStart('character', -el.value.length);
+				pos = Sel.text.length - SelLength;
+			}
+			return pos;
+		};
+
+		$.fn.selectRange = function(start, end) {
+			if(!end) end = start;
+			return this.each(function() {
+				if (this.setSelectionRange) {
+					this.focus();
+					this.setSelectionRange(start, end);
+				} else if (this.createTextRange) {
+					var range = this.createTextRange();
+					range.collapse(true);
+					range.moveEnd('character', end);
+					range.moveStart('character', start);
+					range.select();
+				}
+			});
+		};
+
+		//http://stackoverflow.com/questions/511088/use-javascript-to-place-cursor-at-end-of-text-in-text-input-element
+		$.fn.putCursorAtEnd = function() {
+			return this.each(function() {
+				$(this).focus();
+
+				if (this.setSelectionRange) {
+					var len = $(this).val().length * 2;
+					this.setSelectionRange(len, len);
+				} else {
+					$(this).val($(this).val());
+				}
+				this.scrollTop = 999999;
+			});
+		};
+
+	})(jQuery || {fn:{}});
+}
\ No newline at end of file
diff --git a/public/templates/admin/header.tpl b/public/templates/admin/header.tpl
index 6e04b32dc5..ba4ec0425c 100644
--- a/public/templates/admin/header.tpl
+++ b/public/templates/admin/header.tpl
@@ -35,9 +35,9 @@
 		});
 	</script>
 	<script src="//code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
+	<script src="{relative_path}/src/overrides.js"></script>
 	<script src="{relative_path}/src/utils.js"></script>
 
-	<link rel="stylesheet" type="text/css" href="{relative_path}/stylesheet.css?{cache-buster}" />
 </head>
 
 <body class="admin">
diff --git a/src/meta.js b/src/meta.js
index cb8f43c3fb..f4411904fd 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -239,6 +239,7 @@ var fs = require('fs'),
 			'src/templates.js',
 			'src/ajaxify.js',
 			'src/translator.js',
+			'src/overrides.js',
 			'src/utils.js'
 		],
 		minFile: nconf.get('relative_path') + 'nodebb.min.js',

From 1b207d8276c93325b73b6dd6932d4bb837bf6cd9 Mon Sep 17 00:00:00 2001
From: akhoury <bentael@gmail.com>
Date: Wed, 26 Feb 2014 21:55:29 -0500
Subject: [PATCH 090/193] IE8 support, general cleanups, from native to $
 objects ...

---
 public/src/ajaxify.js                |  14 +-
 public/src/app.js                    |  30 +--
 public/src/forum/accountheader.js    |   4 +-
 public/src/forum/admin/categories.js | 174 ++++++++----------
 public/src/forum/admin/footer.js     |  36 ++--
 public/src/forum/admin/groups.js     |  77 ++++----
 public/src/forum/admin/languages.js  |   2 +-
 public/src/forum/admin/settings.js   |  47 ++---
 public/src/forum/admin/themes.js     | 144 +++++++--------
 public/src/forum/admin/topics.js     |  85 ++++-----
 public/src/forum/admin/users.js      |  17 +-
 public/src/forum/category.js         |   8 +-
 public/src/forum/footer.js           |  12 +-
 public/src/forum/login.js            |   2 +-
 public/src/forum/recent.js           |   9 +-
 public/src/forum/register.js         |   2 +-
 public/src/forum/reset.js            |  29 ++-
 public/src/forum/reset_code.js       |  39 ++--
 public/src/forum/topic.js            | 104 +++++------
 public/src/forum/users.js            |  17 +-
 public/src/modules/chat.js           |  54 +++---
 public/src/modules/composer.js       | 264 ++++++++-------------------
 public/src/modules/taskbar.js        |  85 +++++----
 public/src/modules/uploader.js       |  22 ++-
 public/src/templates.js              |   8 +-
 public/src/translator.js             |   7 +-
 public/src/utils.js                  | 112 +++++-------
 public/templates/admin/header.tpl    |  15 +-
 public/templates/composer.tpl        |  34 ++--
 public/templates/header.tpl          |   7 +
 src/routes/admin.js                  |  51 +++---
 src/routes/api.js                    |  17 +-
 32 files changed, 715 insertions(+), 813 deletions(-)

diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
index c332722894..03ea7f1a1d 100644
--- a/public/src/ajaxify.js
+++ b/public/src/ajaxify.js
@@ -2,16 +2,13 @@
 
 var ajaxify = {};
 
-(function ($) {
+(function () {
 	/*global app, templates, utils*/
 
 	var location = document.location || window.location,
 		rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : ''),
 		content = null;
 
-	var current_state = null;
-	var executed = {};
-
 	var events = [];
 	ajaxify.register_events = function (new_page_events) {
 		for (var i = 0, ii = events.length; i < ii; i++) {
@@ -114,7 +111,7 @@ var ajaxify = {};
 
 				require(['vendor/async'], function(async) {
 					$('#content [widget-area]').each(function() {
-						widgetLocations.push(this.getAttribute('widget-area'));
+						widgetLocations.push($(this).attr('widget-area'));
 					});
 
 					async.each(widgetLocations, function(location, next) {
@@ -127,7 +124,8 @@ var ajaxify = {};
 
 							if (!renderedWidgets.length) {
 								$('body [no-widget-class]').each(function() {
-									this.className = this.getAttribute('no-widget-class');
+									var $this = $(this);
+									$this.addClass($this.attr('no-widget-class'));
 								});
 							}
 							
@@ -174,7 +172,7 @@ var ajaxify = {};
 				app.previousUrl = window.location.href;
 			}
 
-			if (this.getAttribute('data-ajaxify') === 'false') {
+			if ($(this).attr('data-ajaxify') === 'false') {
 				return;
 			}
 
@@ -198,4 +196,4 @@ var ajaxify = {};
 		});
 	});
 
-}(jQuery));
+}());
diff --git a/public/src/app.js b/public/src/app.js
index 39cfc8e7b3..ffa8f5129d 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -1,10 +1,10 @@
 var socket,
 	config,
 	app = {
-		"username": null,
-		"uid": null,
-		"isFocused": true,
-		"currentRoom": null
+		'username': null,
+		'uid': null,
+		'isFocused': true,
+		'currentRoom': null
 	};
 
 (function () {
@@ -277,7 +277,7 @@ var socket,
 	app.populateOnlineUsers = function () {
 		var uids = [];
 
-		jQuery('.post-row').each(function () {
+		$('.post-row').each(function () {
 			var uid = $(this).attr('data-uid');
 			if(uids.indexOf(uid) === -1) {
 				uids.push(uid);
@@ -286,8 +286,8 @@ var socket,
 
 		socket.emit('user.getOnlineUsers', uids, function (err, users) {
 
-			jQuery('.username-field').each(function (index, element) {
-				var el = jQuery(this),
+			$('.username-field').each(function (index, element) {
+				var el = $(this),
 					uid = el.parents('li').attr('data-uid');
 
 					if (uid && users[uid]) {
@@ -307,19 +307,19 @@ var socket,
 			parts = path.split('/'),
 			active = parts[parts.length - 1];
 
-		jQuery('#main-nav li').removeClass('active');
+		$('#main-nav li').removeClass('active');
 		if (active) {
-			jQuery('#main-nav li a').each(function () {
-				var href = this.getAttribute('href');
+			$('#main-nav li a').each(function () {
+				var href = $(this).attr('href');
 				if (active == "sort-posts" || active == "sort-reputation" || active == "search" || active == "latest" || active == "online")
 					active = 'users';
 				if (href && href.match(active)) {
-					jQuery(this.parentNode).addClass('active');
+					$(this.parentNode).addClass('active');
 					return false;
 				}
 			});
 		}
-	};
+	}
 
 	app.createUserTooltips = function() {
 		$('img[title].teaser-pic,img[title].user-img').each(function() {
@@ -335,7 +335,7 @@ var socket,
 			selector:'.fa-circle.status',
 			placement: 'top'
 		});
-	}
+	};
 
 	app.makeNumbersHumanReadable = function(elements) {
 		elements.each(function() {
@@ -452,7 +452,7 @@ var socket,
 			}
 			previousScrollTop = currentScrollTop;
 		});
-	}
+	};
 
 	var	titleObj = {
 			active: false,
@@ -589,7 +589,7 @@ var socket,
 		});
 	};
 
-	jQuery('document').ready(function () {
+	$('document').ready(function () {
 		$('#search-form').on('submit', function () {
 			var input = $(this).find('input');
 			ajaxify.go("search/" + input.val());
diff --git a/public/src/forum/accountheader.js b/public/src/forum/accountheader.js
index 15c94f573e..e49c1eddac 100644
--- a/public/src/forum/accountheader.js
+++ b/public/src/forum/accountheader.js
@@ -9,7 +9,7 @@ define(function() {
 		hideLinks();
 
 		selectActivePill();
-	}
+	};
 
 	AccountHeader.createMenu = function() {
 		var userslug = $('.account-username-box').attr('data-userslug');
@@ -30,7 +30,7 @@ define(function() {
 			selectActivePill();
 			hideLinks();
 		});
-	}
+	};
 
 	function hideLinks() {
 		var yourid = templates.get('yourid'),
diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js
index 8278d7423e..f963c6918d 100644
--- a/public/src/forum/admin/categories.js
+++ b/public/src/forum/admin/categories.js
@@ -57,7 +57,7 @@ define(['uploader'], function(uploader) {
 
 		function updateCategoryOrders() {
 			var categories = $('.admin-categories #entry-container').children();
-			for(var i=0; i<categories.length; ++i) {
+			for(var i = 0; i<categories.length; ++i) {
 				var input = $(categories[i]).find('input[data-name="order"]');
 
 				input.val(i+1).attr('data-value', i+1);
@@ -66,13 +66,14 @@ define(['uploader'], function(uploader) {
 		}
 
 		$('#entry-container').sortable({
-			stop: function( event, ui ) {
+			stop: function(event, ui) {
 				updateCategoryOrders();
 			},
 			distance: 10
 		});
 		$('.blockclass').each(function() {
-			$(this).val(this.getAttribute('data-value'));
+			var $this = $(this);
+			$this.val($this.attr('data-value'));
 		});
 
 
@@ -115,29 +116,30 @@ define(['uploader'], function(uploader) {
 		}
 
 		function enableColorPicker(idx, inputEl) {
-			var	jinputEl = $(inputEl),
-				previewEl = jinputEl.parents('[data-cid]').find('.preview-box');
+			var	$inputEl = $(inputEl),
+				previewEl = $inputEl.parents('[data-cid]').find('.preview-box');
 
-			jinputEl.ColorPicker({
-				color: jinputEl.val() || '#000',
+			$inputEl.ColorPicker({
+				color: $inputEl.val() || '#000',
 				onChange: function(hsb, hex) {
-					jinputEl.val('#' + hex);
-					if (inputEl.getAttribute('data-name') === 'bgColor') previewEl.css('background', '#' + hex);
-					else if (inputEl.getAttribute('data-name') === 'color') previewEl.css('color', '#' + hex);
-					modified(inputEl);
+					$inputEl.val('#' + hex);
+					if ($inputEl.attr('data-name') === 'bgColor') previewEl.css('background', '#' + hex);
+					else if ($inputEl.attr('data-name') === 'color') previewEl.css('color', '#' + hex);
+					modified($inputEl[0]);
 				}
 			});
 		}
 
-		$('document').ready(function() {
+		$(function() {
 			var url = window.location.href,
 				parts = url.split('/'),
 				active = parts[parts.length - 1];
 
 			$('.nav-pills li').removeClass('active');
 			$('.nav-pills li a').each(function() {
-				if (this.getAttribute('href').match(active)) {
-					$(this.parentNode).addClass('active');
+				var $this = $(this);
+				if ($this.attr('href').match(active)) {
+					$this.parent().addClass('active');
 					return false;
 				}
 			});
@@ -159,11 +161,11 @@ define(['uploader'], function(uploader) {
 			});
 
 			$('.dropdown').on('click', '[data-disabled]', function(ev) {
-				var btn = $(this);
-				var categoryRow = btn.parents('li');
-				var cid = categoryRow.attr('data-cid');
+				var btn = $(this),
+					categoryRow = btn.parents('li'),
+					cid = categoryRow.attr('data-cid'),
+					disabled = btn.attr('data-disabled') === 'false' ? '1' : '0';
 
-				var disabled = this.getAttribute('data-disabled') === 'false' ? '1' : '0';
 				categoryRow.remove();
 				modified_categories[cid] = modified_categories[cid] || {};
 				modified_categories[cid]['disabled'] = disabled;
@@ -185,14 +187,15 @@ define(['uploader'], function(uploader) {
 
 
 			$('.admin-categories').on('click', '.upload-button', function() {
-				var inputEl = this;
-				var	cid = $(this).parents('li[data-cid]').attr('data-cid');
-				uploader.open(RELATIVE_PATH + '/admin/category/uploadpicture', {cid:cid}, 0, function(imageUrlOnServer) {
-					inputEl.value = imageUrlOnServer;
-					var previewBox = $(inputEl).parents('li[data-cid]').find('.preview-box');
+				var inputEl = $(this),
+					cid = inputEl.parents('li[data-cid]').attr('data-cid');
+
+				uploader.open(RELATIVE_PATH + '/admin/category/uploadpicture', {cid: cid}, 0, function(imageUrlOnServer) {
+					inputEl.val(imageUrlOnServer);
+					var previewBox = inputEl.parents('li[data-cid]').find('.preview-box');
 					previewBox.css('background', 'url(' + imageUrlOnServer + '?' + new Date().getTime() + ')')
 						.css('background-size', 'cover');
-					modified(inputEl);
+					modified(inputEl[0]);
 				});
 			});
 
@@ -202,12 +205,12 @@ define(['uploader'], function(uploader) {
 					preview = parent.find('.preview-box'),
 					bgColor = parent.find('.category_bgColor').val();
 
-				inputEl.value = '';
-				modified(inputEl);
+				inputEl.val('');
+				modified(inputEl[0]);
 
 				preview.css('background', bgColor);
 
-				$(this).hide();
+				$(this).addClass('hide').hide();
 			});
 		});
 	};
@@ -221,8 +224,8 @@ define(['uploader'], function(uploader) {
 
 		searchEl.off().on('keyup', function() {
 			var	searchEl = this,
-				resultsFrag = document.createDocumentFragment(),
-				liEl = document.createElement('li');
+				liEl;
+
 			clearTimeout(searchDelay);
 
 			searchDelay = setTimeout(function() {
@@ -236,23 +239,21 @@ define(['uploader'], function(uploader) {
 
 					var	numResults = results.length,
 						resultObj;
-					for(var x=0;x<numResults;x++) {
+					for(var x = 0; x < numResults; x++) {
 						resultObj = results[x];
-
-						liEl.setAttribute('data-uid', resultObj.uid);
-						liEl.innerHTML =	'<div class="pull-right">' +
-												'<div class="btn-group">' +
-													'<button type="button" data-priv="+r" class="btn btn-default' + (resultObj.privileges['+r'] ? ' active' : '') + '">Read</button>' +
-													'<button type="button" data-priv="+w" class="btn btn-default' + (resultObj.privileges['+w'] ? ' active' : '') + '">Write</button>' +
-													'<button type="button" data-priv="mods" class="btn btn-default' + (resultObj.privileges['mods'] ? ' active' : '') + '">Moderator</button>' +
-												'</div>' +
-											'</div>' +
-											'<img src="' + resultObj.picture + '" /> ' + resultObj.username;
-
-						resultsFrag.appendChild(liEl.cloneNode(true));
+						liEl = $('<li />')
+							.attr('data-uid', resultObj.uid)
+							.html('<div class="pull-right">' +
+								'<div class="btn-group">' +
+								'<button type="button" data-priv="+r" class="btn btn-default' + (resultObj.privileges['+r'] ? ' active' : '') + '">Read</button>' +
+								'<button type="button" data-priv="+w" class="btn btn-default' + (resultObj.privileges['+w'] ? ' active' : '') + '">Write</button>' +
+								'<button type="button" data-priv="mods" class="btn btn-default' + (resultObj.privileges['mods'] ? ' active' : '') + '">Moderator</button>' +
+								'</div>' +
+								'</div>' +
+								'<img src="' + resultObj.picture + '" /> ' + resultObj.username);
+
+						resultsEl.append(liEl);
 					}
-
-					resultsEl.html(resultsFrag);
 				});
 			}, 250);
 		});
@@ -262,7 +263,7 @@ define(['uploader'], function(uploader) {
 		resultsEl.off().on('click', '[data-priv]', function(e) {
 			var	btnEl = $(this),
 				uid = btnEl.parents('li[data-uid]').attr('data-uid'),
-				privilege = this.getAttribute('data-priv');
+				privilege = btnEl.attr('data-priv');
 			e.preventDefault();
 
 			socket.emit('admin.categories.setPrivilege', {
@@ -278,7 +279,7 @@ define(['uploader'], function(uploader) {
 		});
 
 		modal.off().on('click', '.members li > img', function() {
-			searchEl.val(this.getAttribute('title'));
+			searchEl.val($(this).attr('title'));
 			searchEl.keyup();
 		});
 
@@ -287,33 +288,31 @@ define(['uploader'], function(uploader) {
 			if(err) {
 				return app.alertError(err.message);
 			}
-			var groupsFrag = document.createDocumentFragment(),
-				numResults = results.length,
-				trEl = document.createElement('tr'),
+			var numResults = results.length,
+				trEl,
 			    resultObj;
 
-			for(var x=0;x<numResults;x++) {
+			for(var x = 0; x < numResults; x++) {
 				resultObj = results[x];
-				trEl.setAttribute('data-gid', resultObj.gid);
-				trEl.innerHTML =	'<td><h4>' + resultObj.name + '</h4></td>' +
-									'<td>' +
-										'<div class="btn-group pull-right">' +
-											'<button type="button" data-gpriv="g+r" class="btn btn-default' + (resultObj.privileges['g+r'] ? ' active' : '') + '">Read</button>' +
-											'<button type="button" data-gpriv="g+w" class="btn btn-default' + (resultObj.privileges['g+w'] ? ' active' : '') + '">Write</button>' +
-										'</div>' +
-									'</td>';
-
-				groupsFrag.appendChild(trEl.cloneNode(true));
+				trEl = $('<tr />')
+					.attr('data-gid', resultObj.gid)
+					.html('<td><h4>' + resultObj.name + '</h4></td>' +
+						'<td>' +
+						'<div class="btn-group pull-right">' +
+						'<button type="button" data-gpriv="g+r" class="btn btn-default' + (resultObj.privileges['g+r'] ? ' active' : '') + '">Read</button>' +
+						'<button type="button" data-gpriv="g+w" class="btn btn-default' + (resultObj.privileges['g+w'] ? ' active' : '') + '">Write</button>' +
+						'</div>' +
+						'</td>');
+				groupsResultsEl.append(trEl);
 			}
-
-			groupsResultsEl.html(groupsFrag);
 		});
 
 		groupsResultsEl.off().on('click', '[data-gpriv]', function(e) {
 			var	btnEl = $(this),
 				gid = btnEl.parents('tr[data-gid]').attr('data-gid'),
-				privilege = this.getAttribute('data-gpriv');
+				privilege = btnEl.attr('data-gpriv');
 			e.preventDefault();
+
 			socket.emit('admin.categories.setGroupPrivilege', {
 				cid: cid,
 				gid: gid,
@@ -324,7 +323,7 @@ define(['uploader'], function(uploader) {
 					btnEl.toggleClass('active');
 				}
 			});
-		})
+		});
 
 		modal.modal();
 	};
@@ -338,57 +337,40 @@ define(['uploader'], function(uploader) {
 			var	readLength = privilegeList['+r'].length,
 				writeLength = privilegeList['+w'].length,
 				modLength = privilegeList['mods'].length,
-				readFrag = document.createDocumentFragment(),
-				writeFrag = document.createDocumentFragment(),
-				modFrag = document.createDocumentFragment(),
-				liEl = document.createElement('li'),
-				x, userObj;
+				liEl, x, userObj;
 
 			if (readLength > 0) {
-				for(x=0;x<readLength;x++) {
+				for(x = 0; x < readLength; x++) {
 					userObj = privilegeList['+r'][x];
-					liEl.setAttribute('data-uid', userObj.uid);
-
-					liEl.innerHTML = '<img src="' + userObj.picture + '" title="' + userObj.username + '" />';
-					readFrag.appendChild(liEl.cloneNode(true));
+					liEl = $('<li/>').attr('data-uid', userObj.uid).html('<img src="' + userObj.picture + '" title="' + userObj.username + '" />');
+					readMembers.append(liEl);
 				}
 			} else {
-				liEl.className = 'empty';
-				liEl.innerHTML = 'All users can read and see this category';
-				readFrag.appendChild(liEl.cloneNode(true));
+				liEl = $('<li/>').addClass('empty').html('All users can read and see this category');
+				readMembers.append(liEl);
 			}
 
 			if (writeLength > 0) {
 				for(x=0;x<writeLength;x++) {
 					userObj = privilegeList['+w'][x];
-					liEl.setAttribute('data-uid', userObj.uid);
-
-					liEl.innerHTML = '<img src="' + userObj.picture + '" title="' + userObj.username + '" />';
-					writeFrag.appendChild(liEl.cloneNode(true));
+					$('<li />').attr('data-uid', userObj.uid).html('<img src="' + userObj.picture + '" title="' + userObj.username + '" />');
+					writeMembers.append(liEl);
 				}
 			} else {
-				liEl.className = 'empty';
-				liEl.innerHTML = 'All users can write to this category';
-				writeFrag.appendChild(liEl.cloneNode(true));
+				liEl = $('<li />').addClass('empty').html('All users can write to this category');
+				writeMembers.append(liEl);
 			}
 
 			if (modLength > 0) {
-				for(x=0;x<modLength;x++) {
+				for(x = 0;x < modLength; x++) {
 					userObj = privilegeList['mods'][x];
-					liEl.setAttribute('data-uid', userObj.uid);
-
-					liEl.innerHTML = '<img src="' + userObj.picture + '" title="' + userObj.username + '" />';
-					modFrag.appendChild(liEl.cloneNode(true));
+					liEl = $('<li />').attr('data-uid', userObj.uid).html('<img src="' + userObj.picture + '" title="' + userObj.username + '" />');
+					moderatorsEl.append(liEl);
 				}
 			} else {
-				liEl.className = 'empty';
-				liEl.innerHTML = 'No moderators';
-				modFrag.appendChild(liEl.cloneNode(true));
+				liEl = $('<li />').addClass('empty').html('No moderators');
+				moderatorsEl.appendChild(liEl);
 			}
-
-			readMembers.html(readFrag);
-			writeMembers.html(writeFrag);
-			moderatorsEl.html(modFrag);
 		});
 	};
 
diff --git a/public/src/forum/admin/footer.js b/public/src/forum/admin/footer.js
index f81e23d110..cff9f24f90 100644
--- a/public/src/forum/admin/footer.js
+++ b/public/src/forum/admin/footer.js
@@ -1,18 +1,28 @@
-jQuery('document').ready(function() {
-	// On menu click, change "active" state
-	var menuEl = document.querySelector('.sidebar-nav'),
-		liEls = menuEl.querySelectorAll('li')
-		parentEl = null;
+$(function() {
+
+	var menuEl = $('.sidebar-nav'),
+		liEls = menuEl.find('li'),
+		parentEl,
+		activate = function(li) {
+			liEls.removeClass('active');
+			li.addClass('active');
+		};
 
-	menuEl.addEventListener('click', function(e) {
-		parentEl = e.target.parentNode;
-		if (parentEl.nodeName === 'LI') {
-			for (var x = 0, numLis = liEls.length; x < numLis; x++) {
-				if (liEls[x] !== parentEl) jQuery(liEls[x]).removeClass('active');
-				else jQuery(parentEl).addClass('active');
-			}
+	// also on ready, check the pathname, maybe it was a page refresh and no item was clicked
+	liEls.each(function(i, li){
+		li = $(li);
+		if ((li.find('a').attr('href') || '').indexOf(location.pathname) >= 0) {
+			activate(li);
+		}
+	});
+
+	// On menu click, change "active" state
+	menuEl.on('click', function(e) {
+		parentEl = $(e.target).parent();
+		if (parentEl.is('li')) {
+			activate(parentEl);
 		}
-	}, false);
+	});
 });
 
 socket.emit('admin.config.get', function(err, config) {
diff --git a/public/src/forum/admin/groups.js b/public/src/forum/admin/groups.js
index ad530c12a9..2e68c929f6 100644
--- a/public/src/forum/admin/groups.js
+++ b/public/src/forum/admin/groups.js
@@ -3,9 +3,9 @@ define(function() {
 
 	Groups.init = function() {
 		var yourid = templates.get('yourid'),
-		    createEl = document.getElementById('create'),
+		    createEl = $('#create'),
 			createModal = $('#create-modal'),
-			createSubmitBtn = document.getElementById('create-modal-go'),
+			createSubmitBtn = $('#create-modal-go'),
 			createNameEl = $('#create-group-name'),
 			detailsModal = $('#group-details-modal'),
 			detailsSearch = detailsModal.find('#group-details-search'),
@@ -15,14 +15,14 @@ define(function() {
 			searchDelay = undefined,
 			listEl = $('#groups-list');
 
-		createEl.addEventListener('click', function() {
+		createEl.on('click', function() {
 			createModal.modal('show');
 			setTimeout(function() {
 				createNameEl.focus();
 			}, 250);
 		}, false);
 
-		createSubmitBtn.addEventListener('click', function() {
+		createSubmitBtn.on('click', function() {
 			var submitObj = {
 				name: createNameEl.val(),
 				description: $('#create-group-desc').val()
@@ -37,7 +37,7 @@ define(function() {
 							errorText = '<strong>Please choose another name</strong><p>There seems to be a group with this name already.</p>';
 							break;
 						case 'name-too-short':
-							errorText = '<strong>Please specify a grou name</strong><p>A group name is required for administrative purposes.</p>';
+							errorText = '<strong>Please specify a group name</strong><p>A group name is required for administrative purposes.</p>';
 							break;
 						default:
 							errorText = '<strong>Uh-Oh</strong><p>There was a problem creating your group. Please try again later!</p>';
@@ -57,8 +57,9 @@ define(function() {
 		});
 
 		listEl.on('click', 'button[data-action]', function() {
-			var action = this.getAttribute('data-action'),
-				gid = $(this).parents('li[data-gid]').attr('data-gid');
+			var el = $(this),
+				action = el.attr('data-action'),
+				gid = el.parents('li[data-gid]').attr('data-gid');
 
 			switch (action) {
 				case 'delete':
@@ -80,28 +81,20 @@ define(function() {
 						var formEl = detailsModal.find('form'),
 							nameEl = formEl.find('#change-group-name'),
 							descEl = formEl.find('#change-group-desc'),
-							memberIcon = document.createElement('li'),
 							numMembers = groupObj.members.length,
-							membersFrag = document.createDocumentFragment(),
-							memberIconImg, x;
-
+							x;
 
 						nameEl.val(groupObj.name);
 						descEl.val(groupObj.description);
 
-						// Member list
-						memberIcon.innerHTML = '<img /><span></span>';
-						memberIconImg = memberIcon.querySelector('img');
-						memberIconLabel = memberIcon.querySelector('span');
 						if (numMembers > 0) {
+							groupMembersEl.empty();
 							for (x = 0; x < numMembers; x++) {
-								memberIconImg.src = groupObj.members[x].picture;
-								memberIconLabel.innerHTML = groupObj.members[x].username;
-								memberIcon.setAttribute('data-uid', groupObj.members[x].uid);
-								membersFrag.appendChild(memberIcon.cloneNode(true));
+								var memberIcon = $('<li />')
+									.append($('<img />').attr('src', groupObj.members[x].picture))
+									.append($('<span />').attr('data-uid', groupObj.members[x].uid).html(groupObj.members[x].username));
+								groupMembersEl.append(memberIcon);
 							}
-							groupMembersEl.html('');
-							groupMembersEl[0].appendChild(membersFrag);
 						}
 
 						detailsModal.attr('data-gid', groupObj.gid);
@@ -118,43 +111,39 @@ define(function() {
 
 			searchDelay = setTimeout(function() {
 				var searchText = searchEl.value,
-					resultsEl = document.getElementById('group-details-search-results'),
-					foundUser = document.createElement('li'),
-					foundUserImg, foundUserLabel;
-
-				foundUser.innerHTML = '<img /><span></span>';
-				foundUserImg = foundUser.getElementsByTagName('img')[0];
-				foundUserLabel = foundUser.getElementsByTagName('span')[0];
+					resultsEl = $('#group-details-search-results'),
+					foundUser;
 
 				socket.emit('admin.user.search', searchText, function(err, results) {
 					if (!err && results && results.users.length > 0) {
-						var numResults = results.users.length,
-							resultsSlug = document.createDocumentFragment(),
-							x;
+						var numResults = results.users.length, x;
 						if (numResults > 4) numResults = 4;
+
+						resultsEl.empty();
 						for (x = 0; x < numResults; x++) {
-							foundUserImg.src = results.users[x].picture;
-							foundUserLabel.innerHTML = results.users[x].username;
-							foundUser.setAttribute('title', results.users[x].username);
-							foundUser.setAttribute('data-uid', results.users[x].uid);
-							resultsSlug.appendChild(foundUser.cloneNode(true));
-						}
+							foundUser = $('<li />');
+							foundUser
+								.attr({title: results.users[x].username, 'data-uid': results.users[x].uid})
+								.append($('<img />').attr('src', results.users[x].picture))
+								.append($('<span />').html(results.users[x].username));
 
-						resultsEl.innerHTML = '';
-						resultsEl.appendChild(resultsSlug);
-					} else resultsEl.innerHTML = '<li>No Users Found</li>';
+							resultsEl.appendChild(foundUser);
+						}
+					} else {
+						resultsEl.html('<li>No Users Found</li>');
+					}
 				});
 			}, 200);
 		});
 
 		searchResults.on('click', 'li[data-uid]', function() {
-			var userLabel = this,
-				uid = parseInt(this.getAttribute('data-uid')),
+			var userLabel = $(this),
+				uid = parseInt(userLabel.attr('data-uid')),
 				gid = detailsModal.attr('data-gid'),
 				members = [];
 
 			groupMembersEl.find('li[data-uid]').each(function() {
-				members.push(parseInt(this.getAttribute('data-uid')));
+				members.push(parseInt($(this).attr('data-uid')));
 			});
 
 			if (members.indexOf(uid) === -1) {
@@ -170,7 +159,7 @@ define(function() {
 		});
 
 		groupMembersEl.on('click', 'li[data-uid]', function() {
-			var uid = this.getAttribute('data-uid'),
+			var uid = $(this).attr('data-uid'),
 				gid = detailsModal.attr('data-gid');
 
 			socket.emit('admin.groups.get', gid, function(err, groupObj){
diff --git a/public/src/forum/admin/languages.js b/public/src/forum/admin/languages.js
index 44182e3a13..207a75fedb 100644
--- a/public/src/forum/admin/languages.js
+++ b/public/src/forum/admin/languages.js
@@ -1,5 +1,5 @@
 define(['forum/admin/settings'], function(Settings) {
-	jQuery('document').ready(function() {
+	$(function() {
 		Settings.prepare();
 	});
 });
\ No newline at end of file
diff --git a/public/src/forum/admin/settings.js b/public/src/forum/admin/settings.js
index 44f0e22cc2..6f164115f4 100644
--- a/public/src/forum/admin/settings.js
+++ b/public/src/forum/admin/settings.js
@@ -15,38 +15,38 @@ define(['uploader'], function(uploader) {
 		}
 
 		// Populate the fields on the page from the config
-		var fields = document.querySelectorAll('#content [data-field]'),
+		var fields = $('#content [data-field]'),
 			numFields = fields.length,
-			saveBtn = document.getElementById('save'),
-			x, key, inputType;
+			saveBtn = $('#save'),
+			x, key, inputType, field;
 
 		for (x = 0; x < numFields; x++) {
-			key = fields[x].getAttribute('data-field');
-			inputType = fields[x].getAttribute('type');
-			if (fields[x].nodeName === 'INPUT') {
+			field = fields.eq(x);
+			key = field.attr('data-field');
+			inputType = field.attr('type');
+			if (field.is('input')) {
 				if (app.config[key]) {
 					switch (inputType) {
 						case 'text':
 						case 'password':
 						case 'textarea':
 						case 'number':
-							fields[x].value = app.config[key];
+							field.val(app.config[key]);
 							break;
 
 						case 'checkbox':
-							fields[x].checked = parseInt(app.config[key], 10) === 1;
+							field.prop('checked', parseInt(app.config[key], 10) === 1);
 							break;
 					}
 				}
-			} else if (fields[x].nodeName === 'TEXTAREA') {
-				if (app.config[key]) fields[x].value = app.config[key];
-			} else if (fields[x].nodeName === 'SELECT') {
-				if (app.config[key]) fields[x].value = app.config[key];
+			} else if (field.is('textarea')) {
+				if (app.config[key]) field.val(app.config[key]);
+			} else if (field.is('select')) {
+				if (app.config[key]) field.val(app.config[key]);
 			}
 		}
 
-		saveBtn.addEventListener('click', function(e) {
-
+		saveBtn.on('click', function(e) {
 			e.preventDefault();
 
 			for (x = 0; x < numFields; x++) {
@@ -55,27 +55,28 @@ define(['uploader'], function(uploader) {
 		});
 
 		function saveField(field) {
-			var key = field.getAttribute('data-field'),
+			field = $(field);
+			var key = field.attr('data-field'),
 				value;
 
-			if (field.nodeName === 'INPUT') {
-				inputType = field.getAttribute('type');
+			if (field.is('input')) {
+				inputType = field.attr('type');
 				switch (inputType) {
 					case 'text':
 					case 'password':
 					case 'textarea':
 					case 'number':
-						value = field.value;
+						value = field.val();
 						break;
 
 					case 'checkbox':
-						value = field.checked ? '1' : '0';
+						value = field.prop('checked') ? '1' : '0';
 						break;
 				}
-			} else if (field.nodeName === 'TEXTAREA') {
-				value = field.value;
-			} else if (field.nodeName === 'SELECT') {
-				value = field.value;
+			} else if (field.is('textarea')) {
+				value = field.val();
+			} else if (field.is('select')) {
+				value = field.val();
 			}
 
 			socket.emit('admin.config.set', {
diff --git a/public/src/forum/admin/themes.js b/public/src/forum/admin/themes.js
index e448fb072c..d5873a7164 100644
--- a/public/src/forum/admin/themes.js
+++ b/public/src/forum/admin/themes.js
@@ -2,17 +2,21 @@ define(['forum/admin/settings'], function(Settings) {
 	var Themes = {};
 
 	Themes.init = function() {
-		var scriptEl = document.createElement('script');
-		scriptEl.src = 'http://api.bootswatch.com/3/?callback=bootswatchListener';
-		document.body.appendChild(scriptEl);
+		var scriptEl = $('<script />');
+		scriptEl.attr('src', 'http://api.bootswatch.com/3/?callback=bootswatchListener');
+		$('body').append(scriptEl);
+
+		var bootstrapThemeContainer = $('#bootstrap_themes'),
+			installedThemeContainer = $('#installed_themes'),
 
-		var bootstrapThemeContainer = document.querySelector('#bootstrap_themes'),
-			installedThemeContainer = document.querySelector('#installed_themes'),
 			themeEvent = function(e) {
-				if (e.target.hasAttribute('data-action')) {
-					switch (e.target.getAttribute('data-action')) {
+				var target = $(e.target),
+					action = target.attr('data-action');
+
+				if (action) {
+					switch (action) {
 						case 'use':
-							var parentEl = $(e.target).parents('li'),
+							var parentEl = target.parents('li'),
 								themeType = parentEl.attr('data-type'),
 								cssSrc = parentEl.attr('data-css'),
 								themeId = parentEl.attr('data-theme');
@@ -30,16 +34,15 @@ define(['forum/admin/settings'], function(Settings) {
 									timeout: 3500
 								});
 							});
-						break;
+							break;
 					}
 				}
 			};
 
-		bootstrapThemeContainer.addEventListener('click', themeEvent);
-		installedThemeContainer.addEventListener('click', themeEvent);
+		bootstrapThemeContainer.on('click', themeEvent);
+		installedThemeContainer.on('click', themeEvent);
 
-		var revertEl = document.getElementById('revert_theme');
-		revertEl.addEventListener('click', function() {
+		$('#revert_theme').on('click', function() {
 			bootbox.confirm('Are you sure you wish to remove the custom theme and restore the NodeBB default theme?', function(confirm) {
 				if (confirm) {
 					socket.emit('admin.themes.set', {
@@ -64,37 +67,32 @@ define(['forum/admin/settings'], function(Settings) {
 				return app.alertError(err.message);
 			}
 
-			var instListEl = document.getElementById('installed_themes'),
-				themeFrag = document.createDocumentFragment(),
-				liEl = document.createElement('li');
-				liEl.setAttribute('data-type', 'local');
+			var instListEl = $('#installed_themes').empty(), liEl;
 
 			if (themes.length > 0) {
 				for (var x = 0, numThemes = themes.length; x < numThemes; x++) {
-					liEl.setAttribute('data-theme', themes[x].id);
-					liEl.innerHTML = '<img src="' + (themes[x].screenshot ? '/css/previews/' + themes[x].id : RELATIVE_PATH + '/images/themes/default.png') + '" />' +
-						'<div>' +
-						'<div class="pull-right">' +
-						'<button class="btn btn-primary" data-action="use">Use</button> ' +
-						'</div>' +
-						'<h4>' + themes[x].name + '</h4>' +
-						'<p>' +
-						themes[x].description +
-						(themes[x].url ? ' (<a href="' + themes[x].url + '">Homepage</a>)' : '') +
-						'</p>' +
-						'</div>' +
-						'<div class="clear">';
-					themeFrag.appendChild(liEl.cloneNode(true));
+					liEl = $('<li/ >').attr({
+						'data-type': 'local',
+						'data-theme': themes[x].id
+					}).html('<img src="' + (themes[x].screenshot ? '/css/previews/' + themes[x].id : RELATIVE_PATH + '/images/themes/default.png') + '" />' +
+							'<div>' +
+							'<div class="pull-right">' +
+							'<button class="btn btn-primary" data-action="use">Use</button> ' +
+							'</div>' +
+							'<h4>' + themes[x].name + '</h4>' +
+							'<p>' +
+							themes[x].description +
+							(themes[x].url ? ' (<a href="' + themes[x].url + '">Homepage</a>)' : '') +
+							'</p>' +
+							'</div>' +
+							'<div class="clear">');
+
+					instListEl.append(liEl);
 				}
 			} else {
 				// No themes found
-				liEl.className = 'no-themes';
-				liEl.innerHTML = 'No installed themes found';
-				themeFrag.appendChild(liEl);
+				instListEl.append($('<li/ >').addClass('no-themes').html('No installed themes found'));
 			}
-
-			instListEl.innerHTML = '';
-			instListEl.appendChild(themeFrag);
 		});
 
 		// Proper tabbing for "Custom CSS" field
@@ -105,34 +103,30 @@ define(['forum/admin/settings'], function(Settings) {
 		Themes.prepareWidgets();
 
 		Settings.prepare();
-	}
+	};
 
 	Themes.render = function(bootswatch) {
-		var themeFrag = document.createDocumentFragment(),
-			themeEl = document.createElement('li'),
-			themeContainer = document.querySelector('#bootstrap_themes'),
-			numThemes = bootswatch.themes.length;
-
-		themeEl.setAttribute('data-type', 'bootswatch');
+		var themeContainer = $('#bootstrap_themes').empty(),
+			numThemes = bootswatch.themes.length, themeEl, theme;
 
 		for (var x = 0; x < numThemes; x++) {
-			var theme = bootswatch.themes[x];
-			themeEl.setAttribute('data-css', theme.cssCdn);
-			themeEl.setAttribute('data-theme', theme.name);
-			themeEl.innerHTML = '<img src="' + theme.thumbnail + '" />' +
-				'<div>' +
-				'<div class="pull-right">' +
-				'<button class="btn btn-primary" data-action="use">Use</button> ' +
-				'</div>' +
-				'<h4>' + theme.name + '</h4>' +
-				'<p>' + theme.description + '</p>' +
-				'</div>' +
-				'<div class="clear">';
-			themeFrag.appendChild(themeEl.cloneNode(true));
+			theme = bootswatch.themes[x];
+			themeEl = $('<li />').attr({
+				'data-type': 'bootswatch',
+				'data-css': theme.cssCdn,
+				'data-theme': theme.name
+			}).html('<img src="' + theme.thumbnail + '" />' +
+					'<div>' +
+					'<div class="pull-right">' +
+					'<button class="btn btn-primary" data-action="use">Use</button> ' +
+					'</div>' +
+					'<h4>' + theme.name + '</h4>' +
+					'<p>' + theme.description + '</p>' +
+					'</div>' +
+					'<div class="clear">');
+			themeContainer.append(themeEl);
 		}
-		themeContainer.innerHTML = '';
-		themeContainer.appendChild(themeFrag);
-	}
+	};
 
 	Themes.prepareWidgets = function() {
 		$('#widgets .available-widgets .panel').draggable({
@@ -167,8 +161,8 @@ define(['forum/admin/settings'], function(Settings) {
 						hoverClass: "panel-info"
 					})
 					.children('.panel-heading')
-						.append('<div class="pull-right pointer"><span class="delete-widget"><i class="fa fa-times-circle"></i></span></div><div class="pull-left pointer"><span class="toggle-widget"><i class="fa fa-chevron-circle-down"></i></span>&nbsp;</div>')
-						.children('small').html('');
+					.append('<div class="pull-right pointer"><span class="delete-widget"><i class="fa fa-times-circle"></i></span></div><div class="pull-left pointer"><span class="toggle-widget"><i class="fa fa-chevron-circle-down"></i></span>&nbsp;</div>')
+					.children('small').html('');
 			}
 		}
 
@@ -178,18 +172,18 @@ define(['forum/admin/settings'], function(Settings) {
 			},
 			connectWith: "div"
 		}).on('click', '.toggle-widget', function() {
-			$(this).parents('.panel').children('.panel-body').toggleClass('hidden');
-		}).on('click', '.delete-widget', function() {
-			var panel = $(this).parents('.panel');
+				$(this).parents('.panel').children('.panel-body').toggleClass('hidden');
+			}).on('click', '.delete-widget', function() {
+				var panel = $(this).parents('.panel');
 
-			bootbox.confirm('Are you sure you wish to delete this widget?', function(confirm) {
-				if (confirm) {
-					panel.remove();
-				}
+				bootbox.confirm('Are you sure you wish to delete this widget?', function(confirm) {
+					if (confirm) {
+						panel.remove();
+					}
+				});
+			}).on('dblclick', '.panel-heading', function() {
+				$(this).parents('.panel').children('.panel-body').toggleClass('hidden');
 			});
-		}).on('dblclick', '.panel-heading', function() {
-			$(this).parents('.panel').children('.panel-body').toggleClass('hidden');
-		});
 
 		$('#widgets .btn[data-template]').on('click', function() {
 			var btn = $(this),
@@ -205,13 +199,13 @@ define(['forum/admin/settings'], function(Settings) {
 				for (var d in data) {
 					if (data.hasOwnProperty(d)) {
 						if (data[d].name) {
-							widgetData[data[d].name] = data[d].value;	
+							widgetData[data[d].name] = data[d].value;
 						}
 					}
 				}
 
 				widgets.push({
-					widget: this.getAttribute('data-widget'),
+					widget: $(this).attr('data-widget'),
 					data: widgetData
 				});
 			});
@@ -245,7 +239,7 @@ define(['forum/admin/settings'], function(Settings) {
 
 			return widget;
 		}
-		
+
 		$.get(RELATIVE_PATH + '/api/admin/themes', function(data) {
 			var areas = data.areas;
 
diff --git a/public/src/forum/admin/topics.js b/public/src/forum/admin/topics.js
index 1d112ddca4..545fc6b2a3 100644
--- a/public/src/forum/admin/topics.js
+++ b/public/src/forum/admin/topics.js
@@ -2,14 +2,14 @@ define(function() {
 	var	Topics = {};
 
 	Topics.init = function() {
-		var topicsListEl = document.querySelector('.topics'),
-			loadMoreEl = document.getElementById('topics_loadmore');
+		var topicsListEl = $('.topics'),
+			loadMoreEl = $('#topics_loadmore');
 
 		this.resolveButtonStates();
 
-		$(topicsListEl).on('click', '[data-action]', function() {
+		topicsListEl.on('click', '[data-action]', function() {
 			var $this = $(this),
-				action = this.getAttribute('data-action'),
+				action = $this.attr('data-action'),
 				tid = $this.parents('[data-tid]').attr('data-tid');
 
 			switch (action) {
@@ -40,17 +40,17 @@ define(function() {
 			}
 		});
 
-		loadMoreEl.addEventListener('click', function() {
-			if (this.className.indexOf('disabled') === -1) {
-				var topics = document.querySelectorAll('.topics li[data-tid]');
+		loadMoreEl.on('click', function() {
+			if (!$(this).hasClass('disabled')) {
+				var topics = $('.topics li[data-tid]');
 
 				if(!topics.length) {
 					return;
 				}
 
-				var lastTid = parseInt(topics[topics.length - 1].getAttribute('data-tid'));
+				var lastTid = parseInt(topics.eq(topics.length - 1).attr('data-tid'));
 
-				this.innerHTML = '<i class="fa fa-refresh fa-spin"></i> Retrieving topics';
+				$(this).html('<i class="fa fa-refresh fa-spin"></i> Retrieving topics');
 				socket.emit('admin.topics.getMore', {
 					limit: 10,
 					after: lastTid
@@ -59,27 +59,27 @@ define(function() {
 						return app.alertError(err.message);
 					}
 
-					var btnEl = document.getElementById('topics_loadmore');
+					var btnEl = $('#topics_loadmore');
 
 					if (topics.length > 0) {
 						var html = templates.prepare(templates['admin/topics'].blocks['topics']).parse({
 								topics: topics
 							}),
-							topicsListEl = document.querySelector('.topics');
+							topicsListEl = $('.topics');
 
 						// Fix relative paths
 						html = html.replace(/\{relative_path\}/g, RELATIVE_PATH);
 
-						topicsListEl.innerHTML += html;
+						topicsListEl.html(topicsListEl.html() + html);
 
 						Topics.resolveButtonStates();
 
-						btnEl.innerHTML = 'Load More Topics';
+						btnEl.html('Load More Topics');
 						$('span.timeago').timeago();
 					} else {
 						// Exhausted all topics
-						btnEl.className += ' disabled';
-						btnEl.innerHTML = 'No more topics';
+						btnEl.addClass('disabled');
+						btnEl.html('No more topics');
 					}
 				});
 			}
@@ -88,24 +88,26 @@ define(function() {
 
 	Topics.resolveButtonStates = function() {
 		// Resolve proper button state for all topics
-		var topicsListEl = document.querySelector('.topics'),
-			topicEls = topicsListEl.querySelectorAll('li'),
+		var topicsListEl = $('.topics'),
+			topicEls = topicsListEl.find('li'),
 			numTopics = topicEls.length;
+
 		for (var x = 0; x < numTopics; x++) {
-			if (topicEls[x].getAttribute('data-pinned') === '1') {
-				topicEls[x].querySelector('[data-action="pin"]').className += ' active';
-				topicEls[x].removeAttribute('data-pinned');
+			var topic = topicEls.eq(x);
+			if (topic.attr('data-pinned') === '1') {
+				topic.find('[data-action="pin"]').addClass('active');
+				topic.removeAttr('data-pinned');
 			}
-			if (topicEls[x].getAttribute('data-locked') === '1') {
-				topicEls[x].querySelector('[data-action="lock"]').className += ' active';
-				topicEls[x].removeAttribute('data-locked');
+			if (topic.attr('data-locked') === '1') {
+				topic.find('[data-action="lock"]').addClass('active');
+				topic.removeAttr('data-locked');
 			}
-			if (topicEls[x].getAttribute('data-deleted') === '1') {
-				topicEls[x].querySelector('[data-action="delete"]').className += ' active';
-				topicEls[x].removeAttribute('data-deleted');
+			if (topic.attr('data-deleted') === '1') {
+				topic.find('[data-action="delete"]').addClass('active');
+				topic.removeAttr('data-deleted');
 			}
 		}
-	}
+	};
 
 	Topics.setDeleted = function(err, response) {
 		if(err) {
@@ -113,10 +115,9 @@ define(function() {
 		}
 
 		if (response && response.tid) {
-			var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="delete"]');
-
-			$(btnEl).addClass('active');
-			$(btnEl).siblings('[data-action="lock"]').addClass('active');
+			var btnEl = $('li[data-tid="' + response.tid + '"] button[data-action="delete"]');
+			btnEl.addClass('active');
+			btnEl.siblings('[data-action="lock"]').addClass('active');
 		}
 	};
 
@@ -126,10 +127,10 @@ define(function() {
 		}
 
 		if (response && response.tid) {
-			var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="delete"]');
+			var btnEl = $('li[data-tid="' + response.tid + '"] button[data-action="delete"]');
 
-			$(btnEl).removeClass('active');
-			$(btnEl).siblings('[data-action="lock"]').removeClass('active');
+			btnEl.removeClass('active');
+			btnEl.siblings('[data-action="lock"]').removeClass('active');
 		}
 	};
 
@@ -139,9 +140,9 @@ define(function() {
 		}
 
 		if (response && response.tid) {
-			var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="lock"]');
+			var btnEl = $('li[data-tid="' + response.tid + '"] button[data-action="lock"]');
 
-			$(btnEl).addClass('active');
+			btnEl.addClass('active');
 		}
 	};
 
@@ -151,9 +152,9 @@ define(function() {
 		}
 
 		if (response && response.tid) {
-			var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="lock"]');
+			var btnEl = $('li[data-tid="' + response.tid + '"] button[data-action="lock"]');
 
-			$(btnEl).removeClass('active');
+			btnEl.removeClass('active');
 		}
 	};
 
@@ -164,9 +165,9 @@ define(function() {
 		}
 
 		if (response && response.tid) {
-			var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="pin"]');
+			var btnEl = $('li[data-tid="' + response.tid + '"] button[data-action="pin"]');
 
-			$(btnEl).removeClass('active');
+			btnEl.removeClass('active');
 		}
 	};
 
@@ -176,9 +177,9 @@ define(function() {
 		}
 
 		if (response && response.tid) {
-			var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="pin"]');
+			var btnEl = $('li[data-tid="' + response.tid + '"] button[data-action="pin"]');
 
-			$(btnEl).addClass('active');
+			btnEl.addClass('active');
 		}
 	};
 
diff --git a/public/src/forum/admin/users.js b/public/src/forum/admin/users.js
index 42e466534d..25e6aee335 100644
--- a/public/src/forum/admin/users.js
+++ b/public/src/forum/admin/users.js
@@ -156,7 +156,7 @@ define(function() {
 		}
 
 
-		jQuery('document').ready(function() {
+		$('document').ready(function() {
 
 			var timeoutId = 0,
 				loadingMoreUsers = false;
@@ -165,15 +165,16 @@ define(function() {
 				parts = url.split('/'),
 				active = parts[parts.length - 1];
 
-			jQuery('.nav-pills li').removeClass('active');
-			jQuery('.nav-pills li a').each(function() {
-				if (this.getAttribute('href').match(active)) {
-					jQuery(this.parentNode).addClass('active');
+			$('.nav-pills li').removeClass('active');
+			$('.nav-pills li a').each(function() {
+				var $this = $(this);
+				if ($this.attr('href').match(active)) {
+					$this.parent().addClass('active');
 					return false;
 				}
 			});
 
-			jQuery('#search-user').on('keyup', function() {
+			$('#search-user').on('keyup', function() {
 				if (timeoutId !== 0) {
 					clearTimeout(timeoutId);
 					timeoutId = 0;
@@ -182,7 +183,7 @@ define(function() {
 				timeoutId = setTimeout(function() {
 					var username = $('#search-user').val();
 
-					jQuery('.fa-spinner').removeClass('none');
+					$('.fa-spinner').removeClass('none');
 
 					socket.emit('admin.user.search', username, function(err, data) {
 						if(err) {
@@ -195,7 +196,7 @@ define(function() {
 							userListEl = document.querySelector('.users');
 
 						userListEl.innerHTML = html;
-						jQuery('.fa-spinner').addClass('none');
+						$('.fa-spinner').addClass('none');
 
 						if (data && data.users.length === 0) {
 							$('#user-notfound-notify').html('User not found!')
diff --git a/public/src/forum/category.js b/public/src/forum/category.js
index 09eacd8c55..1a27392a4d 100644
--- a/public/src/forum/category.js
+++ b/public/src/forum/category.js
@@ -65,8 +65,8 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				topics = $('#topics-container').children('.category-item'),
 				numTopics = topics.length;
 
-			jQuery('#topics-container, .category-sidebar').removeClass('hidden');
-			jQuery('#category-no-topics').remove();
+			$('#topics-container, .category-sidebar').removeClass('hidden');
+			$('#category-no-topics').remove();
 
 			if (numTopics > 0) {
 				for (var x = 0; x < numTopics; x++) {
@@ -104,8 +104,8 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		translator.translate(html, function(translatedHTML) {
 			var container = $('#topics-container');
 
-			jQuery('#topics-container, .category-sidebar').removeClass('hidden');
-			jQuery('#category-no-topics').remove();
+			$('#topics-container, .category-sidebar').removeClass('hidden');
+			$('#category-no-topics').remove();
 
 			html = $(translatedHTML);
 
diff --git a/public/src/forum/footer.js b/public/src/forum/footer.js
index d5eac0bf4f..e866b70cf9 100644
--- a/public/src/forum/footer.js
+++ b/public/src/forum/footer.js
@@ -9,13 +9,17 @@ define(['notifications', 'chat'], function(Notifications, Chat) {
 	translator.prepareDOM();
 
 	function updateUnreadCount(err, tids) {
-		var count = 0;
-		if(tids && tids.length) {
+		var count = 0, unreadEl = $('#unread-count');
+
+		if (err) {
+			console.warn('Error updating unread count', err);
+		} else if(tids && tids.length) {
 			count = tids.length;
 		}
 
-		$('#unread-count').toggleClass('unread-count', count > 0);
-		$('#unread-count').attr('data-content', count > 20 ? '20+' : count);
+		unreadEl
+			.toggleClass('unread-count', count > 0)
+			.attr('data-content', count > 20 ? '20+' : count);
 	}
 
 
diff --git a/public/src/forum/login.js b/public/src/forum/login.js
index 1f4df45ea6..11afd12cdc 100644
--- a/public/src/forum/login.js
+++ b/public/src/forum/login.js
@@ -52,7 +52,7 @@ define(function() {
 			return false;
 		});
 
-		document.querySelector('#content input').focus();
+		$('#content input').focus();
 	};
 
 	return Login;
diff --git a/public/src/forum/recent.js b/public/src/forum/recent.js
index 8837c41367..8c7d6c3b32 100644
--- a/public/src/forum/recent.js
+++ b/public/src/forum/recent.js
@@ -38,10 +38,11 @@ define(function() {
 	Recent.selectActivePill = function() {
 		var active = getActiveSection();
 
-		jQuery('.nav-pills li').removeClass('active');
-		jQuery('.nav-pills li a').each(function() {
-			if (this.getAttribute('href').match(active)) {
-				jQuery(this.parentNode).addClass('active');
+		$('.nav-pills li').removeClass('active');
+		$('.nav-pills li a').each(function() {
+			var $this = $(this);
+			if ($this.attr('href').match(active)) {
+				$this.parent().addClass('active');
 				return false;
 			}
 		});
diff --git a/public/src/forum/register.js b/public/src/forum/register.js
index 4f0acb2a0d..674f53af6e 100644
--- a/public/src/forum/register.js
+++ b/public/src/forum/register.js
@@ -93,7 +93,7 @@ define(function() {
 		}
 
 		username.on('keyup', function() {
-			jQuery('#yourUsername').html(this.value.length > 0 ? this.value : 'username');
+			$('#yourUsername').html(this.value.length > 0 ? this.value : 'username');
 		});
 
 		username.on('blur', function() {
diff --git a/public/src/forum/reset.js b/public/src/forum/reset.js
index a04b3d31e5..f67e1a28b8 100644
--- a/public/src/forum/reset.js
+++ b/public/src/forum/reset.js
@@ -2,30 +2,29 @@ define(function() {
 	var	ResetPassword = {};
 
 	ResetPassword.init = function() {
-		var inputEl = document.getElementById('email'),
-			errorEl = document.getElementById('error'),
-			errorTextEl = errorEl.querySelector('p');
+		var inputEl = $('#email'),
+			errorEl = $('#error'),
+			successEl = $('#success'),
+			errorTextEl = errorEl.find('p');
 
-		document.getElementById('reset').onclick = function() {
-			if (inputEl.value.length > 0 && inputEl.value.indexOf('@') !== -1) {
+		$('#reset').onclick = function() {
+			if (inputEl.val() && inputEl.val().indexOf('@') !== -1) {
 				socket.emit('user.reset.send', {
-					email: inputEl.value
+					email: inputEl.val()
 				}, function(err, data) {
 					if(err) {
 						return app.alertError(err.message);
 					}
 
-					var submitEl = document.getElementById('reset');
-
-					jQuery('#error').hide();
-					jQuery('#success').show();
-					jQuery('#success p').html('An email has been dispatched to "' + inputEl.value + '" with instructions on setting a new password.');
-					inputEl.value = '';
+					errorEl.addClass('hide').hide();
+					successEl.removeClass('hide').show();
+					successEl.find('p').html('An email has been dispatched to "' + inputEl.val() + '" with instructions on setting a new password.');
+					inputEl.val('');
 				});
 			} else {
-				jQuery('#success').hide();
-				jQuery(errorEl).show();
-				errorTextEl.innerHTML = 'Please enter a valid email';
+				successEl.addClass('hide').hide();
+				errorEl.removeClass('hide').show();
+				errorTextEl.html('Please enter a valid email');
 			}
 		};
 	};
diff --git a/public/src/forum/reset_code.js b/public/src/forum/reset_code.js
index 2eb4b49f28..0afd3fc266 100644
--- a/public/src/forum/reset_code.js
+++ b/public/src/forum/reset_code.js
@@ -4,34 +4,33 @@ define(function() {
 	ResetCode.init = function() {
 		var reset_code = templates.get('reset_code');
 
-		var resetEl = document.getElementById('reset'),
-			password = document.getElementById('password'),
-			repeat = document.getElementById('repeat'),
-			noticeEl = document.getElementById('notice');
+		var resetEl = $('#reset'),
+			password = $('#password'),
+			repeat = $('#repeat'),
+			noticeEl = $('#notice');
 
-		resetEl.addEventListener('click', function() {
-			if (password.value.length < 6) {
-				$('#error').hide();
-				noticeEl.querySelector('strong').innerHTML = 'Invalid Password';
-				noticeEl.querySelector('p').innerHTML = 'The password entered is too short, please pick a different password.';
-				noticeEl.style.display = 'block';
+		resetEl.on('click', function() {
+			if (password.val().length < 6) {
+				$('#error').addClass('hide').hide();
+				noticeEl.find('strong').html('Invalid Password');
+				noticeEl.find('p').html('The password entered is too short, please pick a different password.');
+				noticeEl.removeClass('hide').css({display: 'block'});
 			} else if (password.value !== repeat.value) {
 				$('#error').hide();
-				noticeEl.querySelector('strong').innerHTML = 'Invalid Password';
-				noticeEl.querySelector('p').innerHTML = 'The two passwords you\'ve entered do not match.';
-				noticeEl.style.display = 'block';
+				noticeEl.find('strong').html('Invalid Password');
+				noticeEl.find('p').html('The two passwords you\'ve entered do not match.');
+				noticeEl.removeClass('hide').css({display: 'block'});
 			} else {
 				socket.emit('user.reset.commit', {
 					code: reset_code,
-					password: password.value
+					password: password.val()
 				}, function(err) {
 					if(err) {
 						return app.alert(err.message);
 					}
-
-					$('#error').hide();
-					$('#notice').hide();
-					$('#success').show();
+					$('#error').addClass('hide').hide();
+					$('#notice').addClass('hide').hide();
+					$('#success').removeClass('hide').addClass('show').show();
 				});
 			}
 		}, false);
@@ -47,10 +46,10 @@ define(function() {
 			if (valid) {
 				resetEl.disabled = false;
 			} else {
-				var formEl = document.getElementById('reset-form');
+				var formEl = $('#reset-form');
 				// Show error message
 				$('#error').show();
-				formEl.parentNode.removeChild(formEl);
+				formEl.remove();
 			}
 		});
 	};
diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 68c6a12471..70d0661e78 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -17,7 +17,6 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 	});
 
 	Topic.init = function() {
-
 		var expose_tools = templates.get('expose_tools'),
 			tid = templates.get('topic_id'),
 			thread_state = {
@@ -34,17 +33,16 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		$(window).trigger('action:topic.loading');
 
 		function fixDeleteStateForPosts() {
-			var postEls = document.querySelectorAll('#post-container li[data-deleted]');
+			var postEls = $('#post-container li[data-deleted]');
 			for (var x = 0, numPosts = postEls.length; x < numPosts; x++) {
-				if (postEls[x].getAttribute('data-deleted') === '1') {
-					toggle_post_delete_state(postEls[x].getAttribute('data-pid'));
+				if (postEls.eq(x).attr('data-deleted') === '1') {
+					toggle_post_delete_state(postEls.eq(x).attr('data-pid'));
 				}
-				postEls[x].removeAttribute('data-deleted');
+				postEls.eq(x).removeAttr('data-deleted');
 			}
 		}
 
-		jQuery('document').ready(function() {
-
+		$(function() {
 			app.addCommasToNumbers();
 
 			app.enterRoom('topic_' + tid);
@@ -109,54 +107,54 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 						btn.parents('.thread-tools.open').find('.dropdown-toggle').trigger('click');
 					});
 					return false;
-				})
+				});
 
 				moveThreadModal.on('shown.bs.modal', function() {
 
-					var loadingEl = document.getElementById('categories-loading');
-					if (loadingEl) {
+					var loadingEl = $('#categories-loading');
+					if (loadingEl.length) {
 						socket.emit('categories.get', function(err, data) {
+
 							// Render categories
-							var categoriesFrag = document.createDocumentFragment(),
-								categoryEl = document.createElement('li'),
+							var categoryEl,
 								numCategories = data.categories.length,
 								modalBody = moveThreadModal.find('.modal-body'),
-								categoriesEl = modalBody[0].getElementsByTagName('ul')[0],
-								confirmDiv = document.getElementById('move-confirm'),
-								confirmCat = confirmDiv.getElementsByTagName('span')[0],
-								commitEl = document.getElementById('move_thread_commit'),
-								cancelEl = document.getElementById('move_thread_cancel'),
+								categoriesEl = modalBody.find('ul').eq(0).addClass('categories-list'),
+								confirmDiv = $('#move-confirm'),
+								confirmCat = confirmDiv.find('span').eq(0),
+								commitEl = $('#move_thread_commit'),
+								cancelEl = $('#move_thread_cancel'),
 								x, info, targetCid, targetCatLabel;
 
-							categoriesEl.className = 'category-list';
 							for (x = 0; x < numCategories; x++) {
 								info = data.categories[x];
-								categoryEl.style.background = info.bgColor;
-								categoryEl.style.color = info.color || '#fff';
-								categoryEl.className = info.disabled === '1' ? ' disabled' : '';
-								categoryEl.innerHTML = '<i class="fa ' + info.icon + '"></i> ' + info.name;
-								categoryEl.setAttribute('data-cid', info.cid);
-								categoriesFrag.appendChild(categoryEl.cloneNode(true));
+								categoryEl = $('<li />');
+								categoryEl.css({background: info.bgColor, color: info.color || '#fff'})
+									.addClass(info.disabled === '1' ? ' disabled' : '')
+									.attr('data-cid', info.cid)
+									.html('<i class="fa ' + info.icon + '"></i> ' + info.name);
+
+								categoriesEl.append(categoryEl);
 							}
-							categoriesEl.appendChild(categoriesFrag);
-							modalBody[0].removeChild(loadingEl);
-
-							categoriesEl.addEventListener('click', function(e) {
-								if (e.target.nodeName === 'LI') {
-									confirmCat.innerHTML = e.target.innerHTML;
-									confirmDiv.style.display = 'block';
-									targetCid = e.target.getAttribute('data-cid');
-									targetCatLabel = e.target.innerHTML;
-									commitEl.disabled = false;
+							loadingEl.remove();
+
+							categoriesEl.on('click', function(e) {
+								var el = $(e.target);
+								if (el.is('li')) {
+									confirmCat.html(e.target.innerHTML);
+									confirmDiv.css({display: 'block'});
+									targetCid = el.attr('data-cid');
+									targetCatLabel = e.html();
+									commitEl.prop('disabled', false);
 								}
 							}, false);
 
-							commitEl.addEventListener('click', function() {
-								if (!commitEl.disabled && targetCid) {
-									commitEl.disabled = true;
-									$(cancelEl).fadeOut(250);
-									$(moveThreadModal).find('.modal-header button').fadeOut(250);
-									commitEl.innerHTML = 'Moving <i class="fa-spin fa-refresh"></i>';
+							commitEl.on('click', function() {
+								if (!commitEl.prop('disabled') && targetCid) {
+									commitEl.prop('disabled', true);
+									cancelEl.fadeOut(250);
+									moveThreadModal.find('.modal-header button').fadeOut(250);
+									commitEl.html('Moving <i class="fa-spin fa-refresh"></i>');
 
 									socket.emit('topics.move', {
 										tid: tid,
@@ -192,10 +190,10 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					var forkModal = $('#fork-thread-modal'),
 						forkCommit = forkModal.find('#fork_thread_commit');
 					forkModal.removeClass('hide');
-					forkModal.css("position", "fixed")
-						.css("left", Math.max(0, (($(window).width() - $(forkModal).outerWidth()) / 2) + $(window).scrollLeft()) + "px")
-						.css("top", "0px")
-						.css("z-index", "2000");
+					forkModal.css('position', 'fixed')
+						.css('left', Math.max(0, (($(window).width() - $(forkModal).outerWidth()) / 2) + $(window).scrollLeft()) + 'px')
+						.css('top', '0px')
+						.css('z-index', '2000');
 
 					showNoPostsSelected();
 
@@ -256,7 +254,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 
 					function closeForkModal() {
 						for(var i=0; i<pids.length; ++i) {
-							$('#post-container li[data-pid="' + pids[i] + '"]').css('opacity', 1.0);
+							$('#post-container li[data-pid="' + pids[i] + '"]').css('opacity', 1);
 						}
 						forkModal.addClass('hide');
 						$('#post-container').off('click', 'li');
@@ -366,7 +364,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 
 		$('.topic').on('click', '.post_reply', function() {
 			var selectionText = '',
-				selection = window.getSelection() || document.getSelection();
+				selection = window.getSelection ? window.getSelection() : document.selection.createRange();
 
 			if ($(selection.baseNode).parents('.post-content').length > 0) {
 				var snippet = selection.toString();
@@ -718,7 +716,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			for (var p in posts) {
 				if (posts.hasOwnProperty(p)) {
 					var post = posts[p],
-						postcount = jQuery('.user_postcount_' + post.uid),
+						postcount = $('.user_postcount_' + post.uid),
 						ptotal = parseInt(postcount.html(), 10);
 
 					ptotal += 1;
@@ -1030,11 +1028,15 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					if (!scrollingToPost) {
 						if (history.replaceState) {
 							history.replaceState({
-								url: window.location.pathname.slice(1) + '#' + el.attr('data-pid')
-							}, null,
+									url: window.location.pathname.slice(1) + '#' + el.attr('data-pid')
+								},
+								null,
 								window.location.protocol + '//' + window.location.host + window.location.pathname + '#' + el.attr('data-pid'));
 						} else {
-							location.hash = '#' + el.attr('data-pid');
+							// this is very slugish on IE8/9, it causes the browser to adjust its scroll on its own,
+							// it can be fixed, but too much work for a very little return just so ie8/9 users can have the hash updated
+							// commenting it out, sorry
+							// location.hash = '#' + el.attr('data-pid');
 						}
 					}
 				}
@@ -1121,7 +1123,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				}
 			}
 		}
-	}
+	};
 
 	function onNewPostPagination(data) {
 		var posts = data.posts;
diff --git a/public/src/forum/users.js b/public/src/forum/users.js
index e0af14faf7..e64b29ec00 100644
--- a/public/src/forum/users.js
+++ b/public/src/forum/users.js
@@ -18,15 +18,16 @@ define(function() {
 
 		app.addCommasToNumbers();
 
-		jQuery('.nav-pills li').removeClass('active');
-		jQuery('.nav-pills li a').each(function() {
-			if (this.getAttribute('href').match(active)) {
-				jQuery(this.parentNode).addClass('active');
+		$('.nav-pills li').removeClass('active');
+		$('.nav-pills li a').each(function() {
+			var $this = $(this);
+			if ($this.attr('href').match(active)) {
+				$this.parent().addClass('active');
 				return false;
 			}
 		});
 
-		jQuery('#search-user').on('keyup', function() {
+		$('#search-user').on('keyup', function() {
 			if (timeoutId !== 0) {
 				clearTimeout(timeoutId);
 				timeoutId = 0;
@@ -36,15 +37,15 @@ define(function() {
 				var username = $('#search-user').val();
 
 				if (username == '') {
-					jQuery('#user-notfound-notify').html('<i class="fa fa-circle-o"></i>');
-					jQuery('#user-notfound-notify').parent().removeClass('btn-warning label-warning btn-success label-success');
+					$('#user-notfound-notify').html('<i class="fa fa-circle-o"></i>');
+					$('#user-notfound-notify').parent().removeClass('btn-warning label-warning btn-success label-success');
 					return;
 				}
 
 				if (lastSearch === username) return;
 				lastSearch = username;
 
-				jQuery('#user-notfound-notify').html('<i class="fa fa-spinner fa-spin"></i>');
+				$('#user-notfound-notify').html('<i class="fa fa-spinner fa-spin"></i>');
 
 				setTimeout(function() {
 					socket.emit('user.search', username, function(err, data) {
diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js
index 230f4d4ad3..a79e213b48 100644
--- a/public/src/modules/chat.js
+++ b/public/src/modules/chat.js
@@ -14,30 +14,32 @@ define(['taskbar', 'string'], function(taskbar, S) {
 			}
 
 			socket.emit('modules.chats.list', function(err, chats) {
-				var	chatsFrag = document.createDocumentFragment(),
-					chatEl = document.createElement('li'),
-					numChats = chats.length,
-					x, userObj;
-
+				var	numChats = chats.length,
+					chatEl, x, userObj;
+				chatsListEl.empty();
 				if (!err && numChats > 0) {
-					for(x=0;x<numChats;x++) {
-						userObj = chats[x];
-						chatEl.setAttribute('data-uid', userObj.uid);
-						chatEl.innerHTML = '<a href="javascript:app.openChat(\'' + userObj.username + '\', ' + userObj.uid + ');"><img src="' + userObj.picture + '" title="' + userObj.username + '" />' + userObj.username + '</a>';
 
-						chatsFrag.appendChild(chatEl.cloneNode(true));
+					for(x = 0;x < numChats; x++) {
+						userObj = chats[x];
+						chatEl = $('<li />')
+							.attr('data-uid', userObj.uid)
+							.html('<a href="javascript:app.openChat(\''
+								+ userObj.username
+								+ '\', ' + userObj.uid
+								+ ');"><img src="'
+								+ userObj.picture
+								+ '" title="'
+								+ userObj.username
+								+ '" />' + userObj.username + '</a>');
+
+						chatsListEl.append(chatEl);
 					}
-
-					chatsListEl.empty();
-					chatsListEl.html(chatsFrag);
 				} else {
 					translator.get('modules:chat.no_active', function(str) {
-						chatEl.className = 'no_active';
-						chatEl.innerHTML = '<a href="#">' + str + '</a>';
-						chatsFrag.appendChild(chatEl.cloneNode(true));
-
-						chatsListEl.empty();
-						chatsListEl.html(chatsFrag);
+						chatEl = $('<li />')
+							.addClass('no_active')
+							.html('<a href="#">' + str + '</a>');
+						chatsListEl.append(chatEl);
 					});
 				}
 			});
@@ -79,15 +81,15 @@ define(['taskbar', 'string'], function(taskbar, S) {
 			}
 		});
 		chatModal.css('zIndex', topZ + 1);
-	}
+	};
 
 	module.getModal = function(touid) {
 		return $('#chat-modal-' + touid);
-	}
+	};
 
 	module.modalExists = function(touid) {
 		return $('#chat-modal-' + touid).length !== 0;
-	}
+	};
 
 	function checkStatus(chatModal) {
 		socket.emit('user.isOnline', chatModal.touid, function(err, data) {
@@ -162,7 +164,7 @@ define(['taskbar', 'string'], function(taskbar, S) {
 				callback(chatModal);
 			});
 		});
-	}
+	};
 
 	module.center = function(chatModal) {
 		chatModal.css("left", Math.max(0, (($(window).width() - $(chatModal).outerWidth()) / 2) + $(window).scrollLeft()) + "px");
@@ -170,7 +172,7 @@ define(['taskbar', 'string'], function(taskbar, S) {
 		chatModal.css("zIndex", 2000);
 		chatModal.find('#chat-message-input').focus();
 		return chatModal;
-	}
+	};
 
 	module.load = function(uuid) {
 		var chatModal = $('div[UUID="'+uuid+'"]');
@@ -180,7 +182,7 @@ define(['taskbar', 'string'], function(taskbar, S) {
 		scrollToBottom(chatModal.find('#chat-content'));
 		module.center(chatModal);
 		module.bringModalToTop(chatModal);
-	}
+	};
 
 	module.minimize = function(uuid) {
 		var chatModal = $('div[UUID="'+uuid+'"]');
@@ -188,7 +190,7 @@ define(['taskbar', 'string'], function(taskbar, S) {
 		taskbar.minimize('chat', uuid);
 		clearInterval(chatModal.intervalId);
 		chatModal.intervalId = 0;
-	}
+	};
 
 	function getChatMessages(chatModal, callback) {
 		socket.emit('modules.chats.get', {touid:chatModal.touid}, function(err, messages) {
diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js
index 45b8c2b974..7cc896bcd5 100644
--- a/public/src/modules/composer.js
+++ b/public/src/modules/composer.js
@@ -4,6 +4,18 @@ define(['taskbar'], function(taskbar) {
 		posts: {}
 	};
 
+	function maybeParse(response) {
+		if (typeof response == 'string')  {
+			try {
+				return $.parseJSON(response);
+			} catch (e) {
+				return {status: 500, message: 'Something went wrong while parsing server response'}
+			}
+		}
+		return response;
+
+	}
+
 	function resetInputFile($el) {
 		$el.wrap('<form />').closest('form').get(0).reset();
 		$el.unwrap();
@@ -44,9 +56,9 @@ define(['taskbar'], function(taskbar) {
 
 	//http://stackoverflow.com/questions/14441456/how-to-detect-which-device-view-youre-on-using-twitter-bootstrap-api
 	function findBootstrapEnvironment() {
-		var envs = ['xs', 'sm', 'md', 'lg'];
+		var envs = ['xs', 'sm', 'md', 'lg'],
+			$el = $('<div>');
 
-		$el = $('<div>');
 		$el.appendTo($('body'));
 
 		for (var i = envs.length - 1; i >= 0; i--) {
@@ -73,8 +85,8 @@ define(['taskbar'], function(taskbar) {
 
 	function initializeDragAndDrop(post_uuid) {
 
-		if(jQuery.event.props.indexOf('dataTransfer') === -1) {
-			jQuery.event.props.push('dataTransfer');
+		if($.event.props.indexOf('dataTransfer') === -1) {
+			$.event.props.push('dataTransfer');
 		}
 
 		var draggingDocument = false;
@@ -88,7 +100,7 @@ define(['taskbar'], function(taskbar) {
 		$(document).off('dragstart').on('dragstart', function(e) {
 			draggingDocument = true;
 		}).off('dragend').on('dragend', function(e) {
-				draggingDocument = false;
+			draggingDocument = false;
 		});
 
 		textarea.on('dragenter', function(e) {
@@ -116,17 +128,24 @@ define(['taskbar'], function(taskbar) {
 
 		drop.on('drop', function(e) {
 			e.preventDefault();
-			var dt = e.dataTransfer,
-				files = dt.files;
+			var files = e.files || (e.dataTransfer || {}).files || (e.target.value ? [e.target.value] : []),
+				fd;
 
 			if(files.length) {
-				var fd = new FormData();
-				for (var i = 0, file; file = dt.files[i]; i++) {
-					fd.append('files[]', file, file.name);
+				if (window.FormData) {
+					fd = new FormData();
+					for (var i = 0, file; file = files[i]; i++) {
+						fd.append('files[]', file, file.name);
+					}
 				}
 
-				fileForm[0].reset();
-				uploadContentFiles({files: files, post_uuid: post_uuid, route: '/api/post/upload', formData: fd});
+				// fileForm[0].reset();
+				uploadContentFiles({
+					files: files,
+					post_uuid: post_uuid,
+					route: '/api/post/upload',
+					formData: fd
+				});
 			}
 
 			drop.hide();
@@ -135,7 +154,8 @@ define(['taskbar'], function(taskbar) {
 
 		$(window).off('paste').on('paste', function(event) {
 
-			var items = (event.clipboardData || event.originalEvent.clipboardData).items;
+			var items = (event.clipboardData || event.originalEvent.clipboardData || {}).items,
+				fd;
 
 			if(items && items.length) {
 
@@ -143,11 +163,18 @@ define(['taskbar'], function(taskbar) {
 				if(blob) {
 					blob.name = 'upload-'+ utils.generateUUID();
 
-					var fd = new FormData();
-					fd.append('files[]', blob, blob.name);
+					if (window.FormData) {
+						fd = new FormData();
+						fd.append('files[]', blob, blob.name);
+					}
 
-					fileForm[0].reset();
-					uploadContentFiles({files: [blob], post_uuid: post_uuid, route: '/api/post/upload', formData: fd});
+					// fileForm[0].reset();
+					uploadContentFiles({
+						files: [blob],
+						post_uuid: post_uuid,
+						route: '/api/post/upload',
+						formData: fd
+					});
 				}
 			}
 		});
@@ -167,7 +194,7 @@ define(['taskbar'], function(taskbar) {
 		uploadForm.attr('action', route);
 
 		for(var i = 0; i < files.length; ++i) {
-			var isImage = files[i].type.match('image.*');
+			var isImage = files[i].type.match(/image./);
 			text += (isImage ? '!' : '') + '[' + files[i].name + '](uploading...) ';
 
 			if(files[i].size > parseInt(config.maximumFileSize, 10) * 1024) {
@@ -181,6 +208,7 @@ define(['taskbar'], function(taskbar) {
 		uploadForm.off('submit').submit(function() {
 
 			$(this).find('#postUploadCsrf').val($('#csrf_token').val());
+
 			if(formData) {
 				formData.append('_csrf', $('#csrf_token').val());
 			}
@@ -191,19 +219,25 @@ define(['taskbar'], function(taskbar) {
 				resetForm: true,
 				clearForm: true,
 				formData: formData,
+
 				error: function(xhr) {
+					xhr = maybeParse(xhr);
+
 					app.alertError('Error uploading file!\nStatus : ' + xhr.status + '\nMessage : ' + xhr.responseText);
 					if (typeof callback == 'function')
 						callback(xhr);
 				},
+
 				uploadProgress: function(event, position, total, percent) {
 					var current = textarea.val();
-					for(var i=0; i<files.length; ++i) {
+					for(var i=0; i < files.length; ++i) {
 						var re = new RegExp(files[i].name + "]\\([^)]+\\)", 'g');
 						textarea.val(current.replace(re, files[i].name+'](uploading ' + percent + '%)'));
 					}
 				},
+
 				success: function(uploads) {
+					uploads = maybeParse(uploads);
 
 					if(uploads && uploads.length) {
 						for(var i=0; i<uploads.length; ++i) {
@@ -218,7 +252,7 @@ define(['taskbar'], function(taskbar) {
 						callback(null, uploads);
 				},
 
-				complete: function(xhr, status) {
+				complete: function() {
 					uploadForm[0].reset();
 					composer.posts[post_uuid].uploadsInProgress.pop();
 				}
@@ -255,11 +289,15 @@ define(['taskbar'], function(taskbar) {
 			$(this).ajaxSubmit({
 				formData: formData,
 				error: function(xhr) {
+					xhr = maybeParse(xhr);
+
 					app.alertError('Error uploading file!\nStatus : ' + xhr.status + '\nMessage : ' + xhr.responseText);
 					if (typeof callback == 'function')
 						callback(xhr);
 				},
 				success: function(uploads) {
+					uploads = maybeParse(uploads);
+
 					postContainer.find('#topic-thumb-url').val((uploads[0] || {}).url || '').trigger('change');
 					if (typeof callback == 'function')
 						callback(null, uploads);
@@ -530,19 +568,23 @@ define(['taskbar'], function(taskbar) {
 					$('#files').click();
 				});
 
-				$('#files').on('change', function(e) {
-					var files = e.target.files;
+				postContainer.find('#files').on('change', function(e) {
+					var files = (e.target || {}).files || ($(this).val() ? [{name: $(this).val(), type: utils.fileMimeType($(this).val())}] : null);
 					if(files) {
 						uploadContentFiles({files: files, post_uuid: post_uuid, route: '/api/post/upload'});
 					}
 				});
 
 				postContainer.find('#topic-thumb-file').on('change', function(e) {
-					var files = e.target.files;
+					var files = (e.target || {}).files || ($(this).val() ? [{name: $(this).val(), type: utils.fileMimeType($(this).val())}] : null),
+						fd;
+
 					if(files) {
-						var fd = new FormData();
-						for (var i = 0, file; file = files[i]; i++) {
-							fd.append('files[]', file, file.name);
+						if (window.FormData) {
+							fd = new FormData();
+							for (var i = 0, file; file = files[i]; i++) {
+								fd.append('files[]', file, file.name);
+							}
 						}
 						uploadTopicThumb({files: files, post_uuid: post_uuid, route: '/api/topic/thumb/upload', formData: fd});
 					}
@@ -573,21 +615,21 @@ define(['taskbar'], function(taskbar) {
 					resizeCenterY = 0,
 					resizeOffset = 0,
 					resizeStart = function(e) {
-						resizeRect = resizeEl.getBoundingClientRect();
+						resizeRect = resizeEl[0].getBoundingClientRect();
 						resizeCenterY = resizeRect.top + (resizeRect.height/2);
 						resizeOffset = resizeCenterY - e.clientY;
 						resizeActive = true;
 
 						$(window).on('mousemove', resizeAction);
 						$(window).on('mouseup', resizeStop);
-						document.body.addEventListener('touchmove', resizeTouchAction);
+						$('body').on('touchmove', resizeTouchAction);
 					},
 					resizeStop = function() {
 						resizeActive = false;
 						bodyEl.focus();
 						$(window).off('mousemove', resizeAction);
 						$(window).off('mouseup', resizeStop);
-						document.body.removeEventListener('touchmove', resizeTouchAction);
+						$('body').off('touchmove', resizeTouchAction);
 					},
 					resizeTouchAction = function(e) {
 						e.preventDefault();
@@ -617,22 +659,20 @@ define(['taskbar'], function(taskbar) {
 					},
 					resizeRect;
 
-				var resizeEl = postContainer.find('.resizer')[0];
+				var resizeEl = postContainer.find('.resizer');
 
-				resizeEl.addEventListener('mousedown', resizeStart);
+				resizeEl.on('mousedown', resizeStart);
 
-				resizeEl.addEventListener('touchstart', function(e) {
+				resizeEl.on('touchstart', function(e) {
 					e.preventDefault();
 					resizeStart(e.touches[0]);
 				});
-				resizeEl.addEventListener('touchend', function(e) {
+				resizeEl.on('touchend', function(e) {
 					e.preventDefault();
 					resizeStop();
 				});
-					// .on('mousedown touchstart', resizeStart)
-					// .on('mouseup touchend', resizeStop)
 
-				window.addEventListener('resize', function() {
+				$(window).on('resize', function() {
 					if (composer.active !== undefined) {
 						composer.activateReposition(composer.active);
 					}
@@ -652,7 +692,6 @@ define(['taskbar'], function(taskbar) {
 		}
 
 		var	percentage = localStorage.getItem('composer:resizePercentage'),
-			bodyRect = document.body.getBoundingClientRect(),
 			postContainer = $('#cmp-uuid-' + post_uuid);
 
 		composer.active = post_uuid;
@@ -673,10 +712,12 @@ define(['taskbar'], function(taskbar) {
 				postContainer.find('.upload-instructions').removeClass('hide');
 			}
 			postContainer.find('.img-upload-btn').removeClass('hide');
+			postContainer.find('#files.lt-ie9').removeClass('hide');
 		}
 
 		if(config.allowFileUploads) {
 			postContainer.find('.file-upload-btn').removeClass('hide');
+			postContainer.find('#files.lt-ie9').removeClass('hide');
 		}
 
 		postContainer.css('visibility', 'visible')
@@ -775,155 +816,6 @@ define(['taskbar'], function(taskbar) {
 		taskbar.minimize('composer', post_uuid);
 	};
 
-	function initializeDragAndDrop(post_uuid) {
-
-		if(jQuery.event.props.indexOf('dataTransfer') === -1) {
-			jQuery.event.props.push('dataTransfer');
-		}
-
-		var draggingDocument = false;
-
-		var postContainer = $('#cmp-uuid-' + post_uuid),
-			fileForm = postContainer.find('#fileForm');
-			drop = postContainer.find('.imagedrop'),
-			tabContent = postContainer.find('.tab-content'),
-			textarea = postContainer.find('textarea');
-
-		$(document).off('dragstart').on('dragstart', function(e) {
-			draggingDocument = true;
-		}).off('dragend').on('dragend', function(e) {
-			draggingDocument = false;
-		});
-
-		textarea.on('dragenter', function(e) {
-			if(draggingDocument) {
-				return;
-			}
-			drop.css('top', tabContent.position().top + 'px');
-			drop.css('height', textarea.height());
-			drop.css('line-height', textarea.height() + 'px');
-			drop.show();
-
-			drop.on('dragleave', function(ev) {
-				drop.hide();
-				drop.off('dragleave');
-			});
-		});
-
-		function cancel(e) {
-			e.preventDefault();
-			return false;
-		}
-
-		drop.on('dragover', cancel);
-		drop.on('dragenter', cancel);
-
-		drop.on('drop', function(e) {
-			e.preventDefault();
-			var dt = e.dataTransfer,
-				files = dt.files;
-
-			if(files.length) {
-				var fd = new FormData();
-				for (var i = 0, file; file = dt.files[i]; i++) {
-					fd.append('files[]', file, file.name);
-				}
-
-				fileForm[0].reset();
-				uploadSubmit(files, post_uuid, '/api/post/upload', fd);
-			}
-
-			drop.hide();
-			return false;
-		});
-
-		$(window).off('paste').on('paste', function(event) {
-
-			var items = (event.clipboardData || event.originalEvent.clipboardData).items;
-
-			if(items && items.length) {
-
-				var blob = items[0].getAsFile();
-				if(blob) {
-					blob.name = 'upload-'+ utils.generateUUID();
-
-					var fd = new FormData();
-					fd.append('files[]', blob, blob.name);
-
-					fileForm[0].reset();
-					uploadSubmit([blob], post_uuid, '/api/post/upload', fd);
-				}
-			}
-		});
-	}
-
-	function uploadSubmit(files, post_uuid, route, formData, callback) {
-		var postContainer = $('#cmp-uuid-' + post_uuid),
-			textarea = postContainer.find('textarea'),
-			text = textarea.val(),
-			uploadForm = postContainer.find('#fileForm');
-
-		uploadForm.attr('action', route);
-
-		for(var i=0; i<files.length; ++i) {
-			var isImage = files[i].type.match('image.*');
-			text += (isImage ? '!' : '') + '[' + files[i].name + '](uploading...) ';
-
-			if(files[i].size > parseInt(config.maximumFileSize, 10) * 1024) {
-				uploadForm[0].reset();
-				return composerAlert('File too big', 'Maximum allowed file size is ' + config.maximumFileSize + 'kbs');
-			}
-		}
-
-		textarea.val(text);
-
-		uploadForm.off('submit').submit(function() {
-
-			$(this).find('#postUploadCsrf').val($('#csrf_token').val());
-			if(formData) {
-				formData.append('_csrf', $('#csrf_token').val());
-			}
-
-			composer.posts[post_uuid].uploadsInProgress.push(1);
-
-			$(this).ajaxSubmit({
-				resetForm: true,
-				clearForm: true,
-				formData: formData,
-				error: function(xhr) {
-					app.alertError('Error uploading file!\nStatus : ' + xhr.status + '\nMessage : ' + xhr.responseText);
-				},
-				uploadProgress: function(event, position, total, percent) {
-					var current = textarea.val();
-					for(var i=0; i<files.length; ++i) {
-						var re = new RegExp(files[i].name + "]\\([^)]+\\)", 'g');
-						textarea.val(current.replace(re, files[i].name+'](uploading ' + percent + '%)'));
-					}
-				},
-				success: function(uploads) {
-
-					if(uploads && uploads.length) {
-						for(var i=0; i<uploads.length; ++i) {
-							var current = textarea.val();
-							var re = new RegExp(uploads[i].name + "]\\([^)]+\\)", 'g');
-							textarea.val(current.replace(re, uploads[i].name + '](' + uploads[i].url + ')'));
-						}
-					}
-
-					textarea.focus();
-				},
-				complete: function(xhr, status) {
-					uploadForm[0].reset();
-					composer.posts[post_uuid].uploadsInProgress.pop();
-				}
-			});
-
-			return false;
-		});
-
-		uploadForm.submit();
-	}
-
 	return {
 		newTopic: composer.newTopic,
 		newReply: composer.newReply,
diff --git a/public/src/modules/taskbar.js b/public/src/modules/taskbar.js
index 96ba98ac38..b5f4aaa5bb 100644
--- a/public/src/modules/taskbar.js
+++ b/public/src/modules/taskbar.js
@@ -1,100 +1,105 @@
 define(function() {
 	var taskbar = {
 		initialized: false,
-		taskbar: undefined,
-		tasklist: undefined,
 		init: function() {
-			var	footerEl = document.getElementById('footer');
+			var	footerEl = $('#footer');
 
-			taskbar.taskbar = document.createElement('div');
-			var jTaskbar = $(taskbar.taskbar);
-			taskbar.taskbar.innerHTML = '<div class="navbar-inner"><ul class="nav navbar-nav pull-right"></ul></div>';
-			taskbar.taskbar.className = 'taskbar navbar navbar-default navbar-fixed-bottom';
-			taskbar.taskbar.id = 'taskbar';
+			this.taskbar = $('<div />')
+				.html('<div class="navbar-inner"><ul class="nav navbar-nav pull-right"></ul></div>')
+				.addClass('taskbar navbar navbar-default navbar-fixed-bottom')
+				.attr('id', 'taskbar');
 
-			taskbar.tasklist = taskbar.taskbar.querySelector('ul');
-			document.body.insertBefore(taskbar.taskbar, footerEl.nextSibling);
+			this.tasklist = this.taskbar.find('ul');
+			this.taskbar.insertBefore(footerEl.next());
 
 			// Posts bar events
-			jTaskbar.on('click', 'li', function() {
-				var	_btn = this,
-					module = this.getAttribute('data-module'),
-					uuid = this.getAttribute('data-uuid');
+			this.taskbar.on('click', 'li', function() {
+				var	$btn = $(this),
+					module = $btn.attr('data-module'),
+					uuid = $btn.attr('data-uuid');
 
 				require([module], function(module) {
-					if (_btn.className.indexOf('active') === -1) {
+					if (!$btn.hasClass('active')) {
 						taskbar.minimizeAll();
 						module.load(uuid);
 						taskbar.toggleNew(uuid, false);
 						app.alternatingTitle('');
 
 						// Highlight the button
-						$(taskbar.tasklist).removeClass('active');
-						_btn.className += ' active';
+						taskbar.tasklist.removeClass('active');
+						$btn.addClass('active');
 					} else {
 						module.minimize(uuid);
 					}
 				});
 			});
 
-			jTaskbar.on('click', 'li a', function(e) {
+			this.taskbar.on('click', 'li a', function(e) {
 				e.preventDefault();
 			});
 
 			taskbar.initialized = true;
 		},
+
 		update: function() {
-			var	tasks = taskbar.tasklist.querySelectorAll('li');
+			var	tasks = taskbar.tasklist.find('li');
 
 			if (tasks.length > 0) {
-				taskbar.taskbar.setAttribute('data-active', '1');
+				taskbar.taskbar.attr('data-active', '1');
 			} else {
-				taskbar.taskbar.removeAttribute('data-active');
+				taskbar.taskbar.removeAttr('data-active');
 			}
 		},
+
 		discard: function(module, uuid) {
 			// Commit
-			var btnEl = taskbar.tasklist.querySelector('[data-module="' + module + '"][data-uuid="' + uuid + '"]');
-			btnEl.parentNode.removeChild(btnEl);
+			var btnEl = taskbar.tasklist.find('[data-module="' + module + '"][data-uuid="' + uuid + '"]');
+			btnEl.remove();
 			taskbar.update();
 		},
+
 		push: function(module, uuid, options) {
-			var element = $(taskbar.tasklist).find('li[data-uuid="'+uuid+'"]');
+			var element = taskbar.tasklist.find('li[data-uuid="'+uuid+'"]');
 			if(element.length)
 				return;
 
-			var	btnEl = document.createElement('li');
-
-			btnEl.innerHTML =	'<a href="#">' +
-									(options.icon ? '<img src="' + options.icon + '" />' : '') +
-									'<span>' + (options.title || 'NodeBB Task') + '</span>' +
-								'</a>';
-			btnEl.setAttribute('data-module', module);
-			btnEl.setAttribute('data-uuid', uuid);
-			btnEl.className = options.state !== undefined ? options.state : 'active';
+			var	btnEl = $('<li />')
+				.html('<a href="#">' +
+					(options.icon ? '<img src="' + options.icon + '" />' : '') +
+					'<span>' + (options.title || 'NodeBB Task') + '</span>' +
+					'</a>')
+				.attr({
+					'data-module': module,
+					'data-uuid': uuid
+				})
+				.addClass(options.state !== undefined ? options.state : 'active');
 
 			if (!options.state || options.state === 'active') taskbar.minimizeAll();
-			taskbar.tasklist.appendChild(btnEl);
+			taskbar.tasklist.append(btnEl);
 
 			taskbar.update();
 		},
+
 		minimize: function(module, uuid) {
-			var btnEl = taskbar.tasklist.querySelector('[data-module="' + module + '"][data-uuid="' + uuid + '"]');
-			$(btnEl).removeClass('active');
+			var btnEl = taskbar.tasklist.find('[data-module="' + module + '"][data-uuid="' + uuid + '"]');
+			btnEl.removeClass('active');
 		},
+
 		minimizeAll: function() {
-			$(taskbar.tasklist.querySelectorAll('.active')).removeClass('active');
+			taskbar.tasklist.find('.active').removeClass('active');
 		},
+
 		toggleNew: function(uuid, state) {
-			var btnEl = $(taskbar.tasklist.querySelector('[data-uuid="' + uuid + '"]'));
+			var btnEl = taskbar.tasklist.find('[data-uuid="' + uuid + '"]');
 			btnEl.toggleClass('new', state);
 		},
+
 		updateActive: function(uuid) {
-			var	tasks = $(taskbar.tasklist).find('li');
+			var	tasks = taskbar.tasklist.find('li');
 			tasks.removeClass('active');
 			tasks.filter('[data-uuid="' + uuid + '"]').addClass('active');
 		}
-	}
+	};
 
 	if (!taskbar.initialized) {
 		taskbar.init();
diff --git a/public/src/modules/uploader.js b/public/src/modules/uploader.js
index c82073c17a..fe9715e4d0 100644
--- a/public/src/modules/uploader.js
+++ b/public/src/modules/uploader.js
@@ -1,6 +1,16 @@
 define(function() {
 
-	var module = {};
+	var module = {},
+		maybeParse = function(response) {
+			if (typeof response == 'string')  {
+				try {
+					return $.parseJSON(response);
+				} catch (e) {
+					return {error: 'Something went wrong while parsing server response'};
+				}
+			}
+			return response;
+		};
 
 	module.open = function(route, params, fileSize, callback) {
 		$('#upload-picture-modal').modal('show').removeClass('hide');
@@ -21,7 +31,7 @@ define(function() {
 			$('#uploadForm').submit();
 		});
 
-		$('#uploadForm').off('submit').submit(function() {
+		uploadForm.off('submit').submit(function() {
 
 			function status(message) {
 				module.hideAlerts();
@@ -52,8 +62,8 @@ define(function() {
 
 
 			$(this).ajaxSubmit({
-
 				error: function(xhr) {
+					xhr = maybeParse(xhr);
 					error('Error: ' + xhr.status);
 				},
 
@@ -62,6 +72,8 @@ define(function() {
 				},
 
 				success: function(response) {
+					response = maybeParse(response);
+
 					if (response.error) {
 						error(response.error);
 						return;
@@ -78,14 +90,14 @@ define(function() {
 
 			return false;
 		});
-	}
+	};
 
 	module.hideAlerts = function() {
 		$('#alert-status').addClass('hide');
 		$('#alert-success').addClass('hide');
 		$('#alert-error').addClass('hide');
 		$('#upload-progress-box').addClass('hide');
-	}
+	};
 
 	return module;
 });
\ No newline at end of file
diff --git a/public/src/templates.js b/public/src/templates.js
index 243bc59d68..016110fe00 100644
--- a/public/src/templates.js
+++ b/public/src/templates.js
@@ -32,7 +32,7 @@
 	}
 
 	templates.is_available = function (tpl) {
-		return jQuery.inArray(tpl, available_templates) !== -1;
+		return $.inArray(tpl, available_templates) !== -1;
 	};
 
 	templates.ready = function (callback) {
@@ -111,7 +111,7 @@
 		}
 
 		function loadClient() {
-			jQuery.when(jQuery.getJSON(RELATIVE_PATH + '/templates/config.json'), jQuery.getJSON(RELATIVE_PATH + '/api/get_templates_listing')).done(function (config_data, templates_data) {
+			$.when($.getJSON(RELATIVE_PATH + '/templates/config.json'), $.getJSON(RELATIVE_PATH + '/api/get_templates_listing')).done(function (config_data, templates_data) {
 				config = config_data[0];
 				available_templates = templates_data[0];
 				templates.ready();
@@ -148,7 +148,7 @@
 		// should be named something else
 		// TODO: The "Date.now()" in the line below is only there for development purposes.
 		// It should be removed at some point.
-		jQuery.get(RELATIVE_PATH + '/templates/' + tpl_name + '.tpl?v=' + Date.now(), function (html) {
+		$.get(RELATIVE_PATH + '/templates/' + tpl_name + '.tpl?v=' + Date.now(), function (html) {
 			var template = function () {
 				this.toString = function () {
 					return this.html;
@@ -222,7 +222,7 @@
 
 				$('#content').html(translatedTemplate);
 
-				jQuery('#content [template-variable]').each(function (index, element) {
+				$('#content [template-variable]').each(function (index, element) {
 					var value = null;
 
 					switch ($(element).attr('template-type')) {
diff --git a/public/src/translator.js b/public/src/translator.js
index 10c511fc21..265365ce5e 100644
--- a/public/src/translator.js
+++ b/public/src/translator.js
@@ -105,7 +105,7 @@
 			if (value) {
 				for (var i = 1, ii = variables.length; i < ii; i++) {
 					var variable = variables[i].replace(']]', '');
-					value = value.replace('%' + i, variable);
+					value = ('' + value).replace('%' + i, variable);
 				}
 
 				text = text.replace(key, value);
@@ -119,6 +119,7 @@
 
 		for (var key in keys) {
 			if (keys.hasOwnProperty(key)) {
+				keys[key] = '' + keys[key];
 				var variables = keys[key].split(/[,][?\s+]/);
 
 				var parsedKey = keys[key].replace('[[', '').replace(']]', '').split(':');
@@ -127,7 +128,7 @@
 				}
 
 				var languageFile = parsedKey[0];
-				parsedKey = parsedKey[1].split(',')[0];
+				parsedKey = ('' + parsedKey[1]).split(',')[0];
 
 				if (files.loaded[languageFile]) {
 					data = insertLanguage(data, keys[key], files.loaded[languageFile][parsedKey], variables);
@@ -178,7 +179,7 @@
 
 			files.loading[filename] = true;
 
-			jQuery.getJSON(RELATIVE_PATH + '/language/' + config.defaultLang + '/' + filename + '.json?v=' + timestamp, function (language) {
+			$.getJSON(RELATIVE_PATH + '/language/' + config.defaultLang + '/' + filename + '.json?v=' + timestamp, function (language) {
 				files.loaded[filename] = language;
 
 				if (callback) {
diff --git a/public/src/utils.js b/public/src/utils.js
index 2e4064d1f8..1ebd7e475b 100644
--- a/public/src/utils.js
+++ b/public/src/utils.js
@@ -99,15 +99,14 @@
 			return difference + (min ? 'y' : ' year') + (difference !== 1 && !min ? 's' : '');
 		},
 
-		invalidUnicodeChars : XRegExp('[^\\p{L}\\s\\d\\-_]', 'g'),
-		invalidLatinChars : /[^\w\s\d\-_]/g,
-
-		trimRegex : /^\s+|\s+$/g,
-		collapseWhitespace : /\s+/g,
-		collapseDash : /-+/g,
-		trimTrailingDash : /-$/g,
-		trimLeadingDash : /^-/g,
-		isLatin : /^[\w]+$/,
+		invalidUnicodeChars: XRegExp('[^\\p{L}\\s\\d\\-_]', 'g'),
+		invalidLatinChars: /[^\w\s\d\-_]/g,
+		trimRegex: /^\s+|\s+$/g,
+		collapseWhitespace: /\s+/g,
+		collapseDash: /-+/g,
+		trimTrailingDash: /-$/g,
+		trimLeadingDash: /^-/g,
+		isLatin: /^[\w]+$/,
 
 		//http://dense13.com/blog/2009/05/03/converting-string-to-slug-javascript/
 		slugify: function(str) {
@@ -138,9 +137,11 @@
 		isPasswordValid: function(password) {
 			return password && password.indexOf(' ') === -1;
 		},
+
 		isNumber: function(n) {
 			return !isNaN(parseFloat(n)) && isFinite(n);
 		},
+
 		// shallow objects merge
 		merge: function() {
 			var result = {}, obj, keys;
@@ -154,6 +155,43 @@
 			return result;
 		},
 
+		fileExtension: function (path) {
+			return ('' + path).split('.').pop();
+		},
+
+		fileMimeType: (function () {
+			// we only care about images, for now
+			var map = {
+				"bmp": "image/bmp",
+				"cmx": "image/x-cmx",
+				"cod": "image/cis-cod",
+				"gif": "image/gif",
+				"ico": "image/x-icon",
+				"ief": "image/ief",
+				"jfif": "image/pipeg",
+				"jpe": "image/jpeg",
+				"jpeg": "image/jpeg",
+				"jpg": "image/jpeg",
+				"pbm": "image/x-portable-bitmap",
+				"pgm": "image/x-portable-graymap",
+				"pnm": "image/x-portable-anymap",
+				"ppm": "image/x-portable-pixmap",
+				"ras": "image/x-cmu-raster",
+				"rgb": "image/x-rgb",
+				"svg": "image/svg+xml",
+				"tif": "image/tiff",
+				"tiff": "image/tiff",
+				"xbm": "image/x-xbitmap",
+				"xpm": "image/x-xpixmap",
+				"xwd": "image/x-xwindowdump"
+			};
+
+			return function (path) {
+				var extension = utils.fileExtension(path);
+				return map[extension] || '*';
+			}
+		})(),
+
 		isRelativeUrl: function(url) {
 			var firstChar = url.slice(0, 1);
 			return (firstChar === '.' || firstChar === '/');
@@ -200,64 +238,8 @@
 		}
 	};
 
-
-	if (!String.prototype.trim) {
-		String.prototype.trim = function() {
-			return this.replace(utils.trimRegex, '');
-		};
-	}
-
 	if ('undefined' !== typeof window) {
 		window.utils = module.exports;
-
-		(function ($, undefined) {
-			$.fn.getCursorPosition = function() {
-				var el = $(this).get(0);
-				var pos = 0;
-				if('selectionStart' in el) {
-					pos = el.selectionStart;
-				} else if('selection' in document) {
-					el.focus();
-					var Sel = document.selection.createRange();
-					var SelLength = document.selection.createRange().text.length;
-					Sel.moveStart('character', -el.value.length);
-					pos = Sel.text.length - SelLength;
-				}
-				return pos;
-			}
-
-			$.fn.selectRange = function(start, end) {
-				if(!end) end = start;
-				return this.each(function() {
-					if (this.setSelectionRange) {
-						this.focus();
-						this.setSelectionRange(start, end);
-					} else if (this.createTextRange) {
-						var range = this.createTextRange();
-						range.collapse(true);
-						range.moveEnd('character', end);
-						range.moveStart('character', start);
-						range.select();
-					}
-				});
-			};
-
-			//http://stackoverflow.com/questions/511088/use-javascript-to-place-cursor-at-end-of-text-in-text-input-element
-			$.fn.putCursorAtEnd = function() {
-				return this.each(function() {
-					$(this).focus();
-
-					if (this.setSelectionRange) {
-						var len = $(this).val().length * 2;
-						this.setSelectionRange(len, len);
-					} else {
-						$(this).val($(this).val());
-					}
-					this.scrollTop = 999999;
-				});
-			};
-
-		})(jQuery);
 	}
 
 })('undefined' === typeof module ? {
diff --git a/public/templates/admin/header.tpl b/public/templates/admin/header.tpl
index ba4ec0425c..0c5990c7b2 100644
--- a/public/templates/admin/header.tpl
+++ b/public/templates/admin/header.tpl
@@ -3,13 +3,24 @@
 <head>
 	<title>NodeBB Administration Panel</title>
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+	<link rel="stylesheet" href="{relative_path}/vendor/fontawesome/css/font-awesome.min.css">
+	<link rel="stylesheet" type="text/css" href="{relative_path}/vendor/colorpicker/colorpicker.css">
+	<link rel="stylesheet" type="text/css" href="{relative_path}/stylesheet.css?{cache-buster}" />
+
 	<script>
 		var RELATIVE_PATH = "{relative_path}";
 	</script>
-	<link rel="stylesheet" href="{relative_path}/vendor/fontawesome/css/font-awesome.min.css">
+
+	<!--[if lt IE 9]>
+  		<script src="//cdnjs.cloudflare.com/ajax/libs/es5-shim/2.3.0/es5-shim.min.js"></script>
+  		<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7/html5shiv.js"></script>
+  		<script src="//cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.js"></script>
+	    <script>__lt_ie_9__ = 1;</script>
+	<![endif]-->
+
 	<script src="{relative_path}/vendor/jquery/js/jquery.js"></script>
 	<script src="{relative_path}/vendor/bootstrap/js/bootstrap.min.js"></script>
-	<link rel="stylesheet" type="text/css" href="{relative_path}/vendor/colorpicker/colorpicker.css">
 	<script src="{relative_path}/socket.io/socket.io.js"></script>
 	<script src="{relative_path}/src/app.js?{cache-buster}"></script>
 	<script src="{relative_path}/src/templates.js?{cache-buster}"></script>
diff --git a/public/templates/composer.tpl b/public/templates/composer.tpl
index 584ba48153..e600021dc0 100644
--- a/public/templates/composer.tpl
+++ b/public/templates/composer.tpl
@@ -1,14 +1,5 @@
 <div class="composer">
 
-	<style>
-			/* todo: move this to base theme */
-			.topic-thumb-container { width: 95%; margin-top: 5px; background: rgb(255, 255, 255); background: rgba(255, 255, 255, 0.6); padding: 10px; }
-			.topic-thumb-btn { cursor: hand; cursor: pointer; }
-			.topic-thumb-toggle-btn { margin: -25px 3% 0 0; }
-			.topic-thumb-preview { width: auto; height: auto; max-width: 100px; max-height: 100px }
-			.topic-thumb-ctrl.form-group { display: inline-block; vertical-align: -50% !important; }
-	</style>
-
 	<div class="composer-container">
 		<input class="title form-control" type="text" tabIndex="1" placeholder="[[topic:composer.title_placeholder]]" />
 
@@ -22,7 +13,6 @@
 					<input type="text" id="topic-thumb-url" class="form-control" placeholder="[[topic:composer.thumb_url_placeholder]]" />
 				</div>
 				<div class="form-group">
-					<!-- todo: drag and drop? -->
 					<label for="topic-thumb-file">[[topic:composer.thumb_file_label]]</label>
 					<input type="file" id="topic-thumb-file" class="form-control" />
 				</div>
@@ -41,16 +31,26 @@
 				<span class="btn btn-link" tabindex="-1"><i class="fa fa-italic"></i></span>
 				<span class="btn btn-link" tabindex="-1"><i class="fa fa-list"></i></span>
 				<span class="btn btn-link" tabindex="-1"><i class="fa fa-link"></i></span>
-				<span class="btn btn-link img-upload-btn hide" tabindex="-1">
-					<i class="fa fa-picture-o"></i>
-				</span>
-				<span class="btn btn-link file-upload-btn hide" tabindex="-1">
-					<i class="fa fa-upload"></i>
-				</span>
+
+				<!--[if gte IE 9]><!-->
+					<span class="btn btn-link img-upload-btn hide" tabindex="-1">
+						<i class="fa fa-picture-o"></i>
+					</span>
+					<span class="btn btn-link file-upload-btn hide" tabindex="-1">
+						<i class="fa fa-upload"></i>
+					</span>
+				<!--<![endif]-->
 
 				<form id="fileForm" method="post" enctype="multipart/form-data">
-					<input type="file" id="files" name="files[]" multiple class="hide"/>
 					<input id="postUploadCsrf" type="hidden" name="_csrf">
+
+					<!--[if gte IE 9]><!-->
+					     <input type="file" id="files" name="files[]" multiple class="gte-ie9 hide"/>
+                    <!--<![endif]-->
+					<!--[if lt IE 9]>
+					     <input type="file" id="files" name="files[]" class="lt-ie9 hide" value="Upload"/>
+					<![endif]-->
+
 				</form>
 			</div>
 		</div>
diff --git a/public/templates/header.tpl b/public/templates/header.tpl
index 4a5aa70794..decbae43da 100644
--- a/public/templates/header.tpl
+++ b/public/templates/header.tpl
@@ -18,6 +18,13 @@
 	<style type="text/css">{customCSS}</style>
 	<!-- ENDIF useCustomCSS -->
 
+	<!--[if lt IE 9]>
+  		<script src="//cdnjs.cloudflare.com/ajax/libs/es5-shim/2.3.0/es5-shim.min.js"></script>
+  		<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7/html5shiv.js"></script>
+  		<script src="//cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.js"></script>
+  		<script>__lt_ie_9__ = 1;</script>
+	<![endif]-->
+
 	<script>
 		var RELATIVE_PATH = "{relative_path}";
 	</script>
diff --git a/src/routes/admin.js b/src/routes/admin.js
index d04ac78b80..1741f62c6f 100644
--- a/src/routes/admin.js
+++ b/src/routes/admin.js
@@ -109,20 +109,22 @@ var nconf = require('nconf'),
 					return res.redirect('/403');
 				}
 
-				var allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'];
-				var params = null;
+				var allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'],
+					params = null, er;
 				try {
 					params = JSON.parse(req.body.params);
 				} catch (e) {
-					return res.send({
+					er = {
 						error: 'Error uploading file! Error :' + e.message
-					});
+					};
+					return res.send(req.xhr ? er : JSON.stringify(er));
 				}
 
 				if (allowedTypes.indexOf(req.files.userPhoto.type) === -1) {
-					res.send({
+					er = {
 						error: 'Allowed image types are png, jpg and gif!'
-					});
+					};
+					res.send(req.xhr ? er : JSON.stringify(er));
 					return;
 				}
 
@@ -136,12 +138,12 @@ var nconf = require('nconf'),
 					return res.redirect('/403');
 				}
 
-				var allowedTypes = ['image/x-icon', 'image/vnd.microsoft.icon'];
+				var allowedTypes = ['image/x-icon', 'image/vnd.microsoft.icon'],
+					er;
 
 				if (allowedTypes.indexOf(req.files.userPhoto.type) === -1) {
-					res.send({
-						error: 'You can only upload icon file type!'
-					});
+					er = {error: 'You can only upload icon file type!'};
+					res.send(req.xhr ? er : JSON.stringify(er));
 					return;
 				}
 
@@ -149,14 +151,12 @@ var nconf = require('nconf'),
 					fs.unlink(req.files.userPhoto.path);
 
 					if(err) {
-						return res.send({
-							error: err.message
-						});
+						er = {error: err.message};
+						return res.send(req.xhr ? er : JSON.stringify(er));
 					}
 
-					res.json({
-						path: image.url
-					});
+					var rs = {path: image.url};
+					res.send(req.xhr ? rs : JSON.stringify(rs));
 				});
 			});
 
@@ -166,12 +166,12 @@ var nconf = require('nconf'),
 					return res.redirect('/403');
 				}
 
-				var allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'];
+				var allowedTypes = ['image/png', 'image/jpeg', 'image/pjpeg', 'image/jpg', 'image/gif'],
+					er;
 
 				if (allowedTypes.indexOf(req.files.userPhoto.type) === -1) {
-					res.send({
-						error: 'Allowed image types are png, jpg and gif!'
-					});
+					er = {error: 'Allowed image types are png, jpg and gif!'};
+					res.send(req.xhr ? er : JSON.stringify(er));
 					return;
 				}
 
@@ -191,17 +191,16 @@ var nconf = require('nconf'),
 
 		function uploadImage(filename, req, res) {
 			function done(err, image) {
+				var er, rs;
 				fs.unlink(req.files.userPhoto.path);
 
 				if(err) {
-					return res.send({
-						error: err.message
-					});
+					er = {error: err.message};
+					return res.send(req.xhr ? er : JSON.stringify(er));
 				}
 
-				res.json({
-					path: image.url
-				});
+				rs = {path: image.url};
+				res.send(req.xhr ? rs : JSON.stringify(rs));
 			}
 
 			if(plugins.hasListeners('filter:uploadImage')) {
diff --git a/src/routes/api.js b/src/routes/api.js
index 7cdd7a8589..df7cd85488 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -455,16 +455,25 @@ var path = require('path'),
 
 				async.map(files, filesIterator, function(err, images) {
 					deleteTempFiles();
+
 					if(err) {
-						return res.json(500, err.message);
+						return res.send(500, err.message);
 					}
-					res.json(200, images);
+
+					// if this was not a XMLHttpRequest (hence the req.xhr check http://expressjs.com/api.html#req.xhr)
+					// then most likely it's submit via the iFrame workaround, via the jquery.form plugin's ajaxSubmit()
+					// we need to send it as text/html so IE8 won't trigger a file download for the json response
+					// malsup.com/jquery/form/#file-upload
+
+					// Also, req.send is safe for both types, if the response was an object, res.send will automatically submit as application/json
+					// expressjs.com/api.html#res.send
+					res.send(200, req.xhr ? images : JSON.stringify(images));
 				});
 			}
 
 			app.post('/post/upload', function(req, res, next) {
 				upload(req, res, function(file, next) {
-					if(file.type.match('image.*')) {
+					if(file.type.match(/image./)) {
 						posts.uploadPostImage(file, next);
 					} else {
 						posts.uploadPostFile(file, next);
@@ -474,7 +483,7 @@ var path = require('path'),
 
 			app.post('/topic/thumb/upload', function(req, res, next) {
 				upload(req, res, function(file, next) {
-					if(file.type.match('image.*')) {
+					if(file.type.match(/image./)) {
 						topics.uploadTopicThumb(file, next);
 					} else {
 		            	res.json(500, {message: 'Invalid File'});

From d87034b131f9cca080a14a7b71b144e1330c6b60 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 26 Feb 2014 21:58:04 -0500
Subject: [PATCH 091/193] use alert for bookmark

---
 public/language/en_GB/topic.json |  2 +
 public/src/app.js                | 63 ++++++++++++++++++--------------
 public/src/forum/topic.js        | 21 ++++++++---
 3 files changed, 53 insertions(+), 33 deletions(-)

diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json
index e03bada393..085f8df821 100644
--- a/public/language/en_GB/topic.json
+++ b/public/language/en_GB/topic.json
@@ -22,6 +22,8 @@
 	"tools": "Tools",
 	"flag": "Flag",
 
+	"bookmark_instructions" : "Click here to return to your last position or close to discard.",
+
 	"flag_title": "Flag this post for moderation",
 	"deleted_message": "This thread has been deleted. Only users with thread management privileges can see it.",
 
diff --git a/public/src/app.js b/public/src/app.js
index 39cfc8e7b3..9e7b1af06a 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -175,23 +175,27 @@ var socket,
 		var alert = $('#' + alert_id);
 		var title = params.title || '';
 
-		function startTimeout(div, timeout) {
+		function fadeOut() {
+			alert.fadeOut(500, function () {
+				$(this).remove();
+			});
+		}
+
+		function startTimeout(timeout) {
 			var timeoutId = setTimeout(function () {
-				$(div).fadeOut(1000, function () {
-					$(this).remove();
-				});
+				fadeOut();
 			}, timeout);
 
-			$(div).attr('timeoutId', timeoutId);
+			alert.attr('timeoutId', timeoutId);
 		}
 
 		if (alert.length > 0) {
 			alert.find('strong').html(title);
 			alert.find('p').html(params.message);
-			alert.attr('class', "alert alert-dismissable alert-" + params.type);
+			alert.attr('class', 'alert alert-dismissable alert-' + params.type);
 
 			clearTimeout(alert.attr('timeoutId'));
-			startTimeout(alert, params.timeout);
+			startTimeout(params.timeout);
 
 			alert.children().fadeOut('100');
 			translator.translate(alert.html(), function(translatedHTML) {
@@ -199,42 +203,45 @@ var socket,
 				alert.html(translatedHTML);
 			});
 		} else {
-			var div = $('<div id="' + alert_id + '" class="alert alert-dismissable alert-' + params.type +'"></div>'),
-				button = $('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>'),
-				strong = $('<strong>' + title + '</strong>'),
-				p = $('<p>' + params.message + '</p>');
+			alert = $('<div id="' + alert_id + '" class="alert alert-dismissable alert-' + params.type +'"></div>');
 
-			div.append(button)
-				.append(strong)
-				.append(p);
+			alert.append($('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>'))
+				.append($('<strong>' + title + '</strong>'))
+				.append($('<p>' + params.message + '</p>'));
 
-			button.on('click', function () {
-				div.remove();
-			});
-
-			if (params.location == null)
+			if (params.location == null) {
 				params.location = 'alert_window';
+			}
 
-			translator.translate(div.html(), function(translatedHTML) {
-				div.html(translatedHTML);
-				$('#' + params.location).prepend(div.fadeIn('100'));
+			translator.translate(alert.html(), function(translatedHTML) {
+				alert.html(translatedHTML);
+				$('#' + params.location).prepend(alert.fadeIn('100'));
+
+				if(typeof params.closefn === 'function') {
+					alert.find('button').on('click', function () {
+						params.closefn();
+						fadeOut();
+					});
+				}
 			});
 
 			if (params.timeout) {
-				startTimeout(div, params.timeout);
+				startTimeout(params.timeout);
 			}
 
-			if (params.clickfn) {
-				div.on('click', function () {
+			if (typeof params.clickfn === 'function') {
+				alert.on('click', function () {
 					params.clickfn();
-					div.fadeOut(500, function () {
-						$(this).remove();
-					});
+					fadeOut();
 				});
 			}
 		}
 	};
 
+	app.removeAlert = function(id) {
+		$('#' + 'alert_button_' + id).remove();
+	}
+
 	app.alertSuccess = function (message, timeout) {
 		if (!timeout)
 			timeout = 2000;
diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index f10839ec0e..d220494b86 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -14,6 +14,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		if(data.url.indexOf('topic') !== 0) {
 			$('.pagination-block').addClass('hide');
 			$('#header-topic-title').html('').hide();
+			app.removeAlert('bookmark');
 		}
 	});
 
@@ -310,7 +311,18 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			if (window.location.hash) {
 				Topic.scrollToPost(window.location.hash.substr(1), true);
 			} else if (bookmark) {
-				Topic.scrollToPost(parseInt(bookmark, 10), true);
+				app.alert({
+					alert_id: 'bookmark',
+					message: '[[topic:bookmark_instructions]]',
+					timeout: 0,
+					type: 'info',
+					clickfn : function() {
+						Topic.scrollToPost(parseInt(bookmark, 10), true);
+					},
+					closefn : function() {
+						localStorage.removeItem('topic:' + tid + ':bookmark');
+					}
+				});
 			} else {
 				updateHeader();
 			}
@@ -1023,11 +1035,10 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			var el = $(this);
 
 			if (elementInView(el)) {
-				var index = parseInt(el.attr('data-index'), 10) + 1;
-				if(index === 0) {
-					localStorage.removeItem("topic:" + templates.get('topic_id') + ":bookmark");
+				if(!parseInt(el.attr('data-index'), 10)) {
+					localStorage.removeItem('topic:' + templates.get('topic_id') + ':bookmark');
 				} else {
-					localStorage.setItem("topic:" + templates.get('topic_id') + ":bookmark", el.attr('data-pid'));
+					localStorage.setItem('topic:' + templates.get('topic_id') + ':bookmark', el.attr('data-pid'));
 
 					if (!scrollingToPost) {
 						var newUrl = window.location.protocol + '//' + window.location.host + window.location.pathname + '#' + el.attr('data-pid')

From 72aa22d8243d6403cc3706c2e4d8017be4345079 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 26 Feb 2014 22:19:01 -0500
Subject: [PATCH 092/193] removed unused timestamp

---
 public/src/templates.js | 2 --
 1 file changed, 2 deletions(-)

diff --git a/public/src/templates.js b/public/src/templates.js
index 2ac8f0b7fb..f3b239866d 100644
--- a/public/src/templates.js
+++ b/public/src/templates.js
@@ -199,8 +199,6 @@
 
 		var template_data = null;
 
-		var timestamp = new Date().getTime(); //debug
-
 		if (!templates[tpl_url]) {
 			templates.preload_template(tpl_url, function() {
 				parse_template();

From 0070e1158e0539d20da19ad965ce6bd251da9718 Mon Sep 17 00:00:00 2001
From: akhoury <bentael@gmail.com>
Date: Wed, 26 Feb 2014 22:32:09 -0500
Subject: [PATCH 093/193] removing a boolean left out from the addEventListener
 definition

---
 public/src/forum/admin/groups.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/src/forum/admin/groups.js b/public/src/forum/admin/groups.js
index 2e68c929f6..c5b69e8743 100644
--- a/public/src/forum/admin/groups.js
+++ b/public/src/forum/admin/groups.js
@@ -20,7 +20,7 @@ define(function() {
 			setTimeout(function() {
 				createNameEl.focus();
 			}, 250);
-		}, false);
+		});
 
 		createSubmitBtn.on('click', function() {
 			var submitObj = {

From 2209a55afc1dd6666759ce929ef062070260cbef Mon Sep 17 00:00:00 2001
From: akhoury <bentael@gmail.com>
Date: Wed, 26 Feb 2014 23:34:03 -0500
Subject: [PATCH 094/193] - changes per PR conversation

---
 public/src/ajaxify.js     | 1 +
 public/src/forum/topic.js | 5 -----
 2 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
index 03ea7f1a1d..8681ceec1b 100644
--- a/public/src/ajaxify.js
+++ b/public/src/ajaxify.js
@@ -125,6 +125,7 @@ var ajaxify = {};
 							if (!renderedWidgets.length) {
 								$('body [no-widget-class]').each(function() {
 									var $this = $(this);
+									$this.removeClass();
 									$this.addClass($this.attr('no-widget-class'));
 								});
 							}
diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index d92e5ba58f..5c59f20ffa 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -1034,11 +1034,6 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 								history.replaceState({
 									url: window.location.pathname.slice(1) + '#' + el.attr('data-pid')
 								}, null, newUrl);
-							} else {
-								// this is very slugish on IE8/9, it causes the browser to adjust its scroll on its own,
-								// it can be fixed, but too much work for a very little return just so ie8/9 users can have the hash updated
-								// commenting it out, sorry
-								// location.hash = '#' + el.attr('data-pid');
 							}
 							currentUrl = newUrl;
 						}

From dcd3975933a66df6862e3a2593926a7541866f6c Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Thu, 27 Feb 2014 00:45:29 -0500
Subject: [PATCH 095/193] closes #1135

---
 public/templates/admin/header.tpl | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/public/templates/admin/header.tpl b/public/templates/admin/header.tpl
index 6e04b32dc5..66a6f87187 100644
--- a/public/templates/admin/header.tpl
+++ b/public/templates/admin/header.tpl
@@ -7,7 +7,9 @@
 		var RELATIVE_PATH = "{relative_path}";
 	</script>
 	<link rel="stylesheet" href="{relative_path}/vendor/fontawesome/css/font-awesome.min.css">
+
 	<script src="{relative_path}/vendor/jquery/js/jquery.js"></script>
+	<script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
 	<script src="{relative_path}/vendor/bootstrap/js/bootstrap.min.js"></script>
 	<link rel="stylesheet" type="text/css" href="{relative_path}/vendor/colorpicker/colorpicker.css">
 	<script src="{relative_path}/socket.io/socket.io.js"></script>
@@ -34,7 +36,7 @@
 			}
 		});
 	</script>
-	<script src="//code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
+
 	<script src="{relative_path}/src/utils.js"></script>
 
 	<link rel="stylesheet" type="text/css" href="{relative_path}/stylesheet.css?{cache-buster}" />

From fb1313ec90ff093dd28aed205f9b7882ee82bd06 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Thu, 27 Feb 2014 01:32:20 -0500
Subject: [PATCH 096/193] load config change

---
 app.js | 28 +++++++++++++---------------
 1 file changed, 13 insertions(+), 15 deletions(-)

diff --git a/app.js b/app.js
index 8ad89c8ade..665944644b 100644
--- a/app.js
+++ b/app.js
@@ -82,8 +82,7 @@ if (!nconf.get('help') && !nconf.get('setup') && !nconf.get('install') && !nconf
 	displayHelp();
 };
 
-
-function start() {
+function loadConfig() {
 	nconf.file({
 		file: configFile
 	});
@@ -92,13 +91,18 @@ function start() {
 		themes_path: path.join(__dirname, 'node_modules')
 	});
 
+	// Ensure themes_path is a full filepath
+	nconf.set('themes_path', path.resolve(__dirname, nconf.get('themes_path')));
+}
+
+
+function start() {
+	loadConfig();
+
 	nconf.set('url', nconf.get('base_url') + (nconf.get('use_port') ? ':' + nconf.get('port') : '') + nconf.get('relative_path'));
 	nconf.set('upload_url', path.join(path.sep, nconf.get('relative_path'), 'uploads', path.sep));
 	nconf.set('base_dir', __dirname);
 
-	// Ensure themes_path is a full filepath
-	nconf.set('themes_path', path.resolve(__dirname, nconf.get('themes_path')));
-
 	winston.info('Time: ' + new Date());
 	winston.info('Initializing NodeBB v' + pkg.version);
 	winston.info('* using configuration stored in: ' + configFile);
@@ -157,16 +161,14 @@ function start() {
 }
 
 function setup() {
+	loadConfig();
+
 	if (nconf.get('setup')) {
 		winston.info('NodeBB Setup Triggered via Command Line');
 	} else {
 		winston.warn('Configuration not found, starting NodeBB setup');
 	}
 
-	nconf.file({
-		file: __dirname + '/config.json'
-	});
-
 	var install = require('./src/install');
 
 	winston.info('Welcome to NodeBB!');
@@ -185,9 +187,7 @@ function setup() {
 }
 
 function upgrade() {
-	nconf.file({
-		file: __dirname + '/config.json'
-	});
+	loadConfig();
 
 	var meta = require('./src/meta');
 
@@ -199,9 +199,7 @@ function upgrade() {
 }
 
 function reset() {
-	nconf.file({
-		file: __dirname + '/config.json'
-	});
+	loadConfig();
 
 	var meta = require('./src/meta'),
 		db = require('./src/database'),

From b8c089cfaa33a94dcd29bc33217fe58ab601c8b9 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Thu, 27 Feb 2014 01:43:24 -0500
Subject: [PATCH 097/193] added check for invalid tags

---
 src/webserver.js | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/webserver.js b/src/webserver.js
index 58ea6ae479..f4869b4cbf 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -148,6 +148,11 @@ process.on('uncaughtException', function(err) {
 
 			// Meta Tags
 			templateValues.metaTags = defaultMetaTags.concat(options.metaTags || []).map(function(tag) {
+				if(!tag || !tag.content) {
+					winston.warn('Invalid meta tag. ' + tag);
+					return tag;
+				}
+
 				tag.content = tag.content.replace(/[&<>'"]/g, function(tag) {
 					return escapeList[tag] || tag;
 				});

From 38e4a6c8b068dbcc9126c92636ec04e09838f02b Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Thu, 27 Feb 2014 01:51:33 -0500
Subject: [PATCH 098/193] better check

---
 src/webserver.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/webserver.js b/src/webserver.js
index f4869b4cbf..b7028efc5c 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -148,8 +148,8 @@ process.on('uncaughtException', function(err) {
 
 			// Meta Tags
 			templateValues.metaTags = defaultMetaTags.concat(options.metaTags || []).map(function(tag) {
-				if(!tag || !tag.content) {
-					winston.warn('Invalid meta tag. ' + tag);
+				if(!tag || typeof tag.content !== 'string') {
+					winston.warn('Invalid meta tag. ', tag);
 					return tag;
 				}
 

From 5b301772bb0a1b6c9cc4fd5ba72371a23a9b5cf8 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Thu, 27 Feb 2014 10:06:31 -0500
Subject: [PATCH 099/193] added daemon capability to ./nodebb start, npm
 start/stop scripts

---
 loader.js        | 75 ++++++++++++++++++++++++++++++++++++------------
 logs/.gitignore  |  1 +
 nodebb           | 14 ++++++++-
 package.json     |  5 +++-
 src/webserver.js |  2 +-
 5 files changed, 76 insertions(+), 21 deletions(-)
 create mode 100644 logs/.gitignore

diff --git a/loader.js b/loader.js
index 2263955ef9..3bb103d417 100644
--- a/loader.js
+++ b/loader.js
@@ -1,26 +1,65 @@
-var	fork = require('child_process').fork,
+"use strict";
+
+var	nconf = require('nconf'),
+	fs = require('fs'),
+	pidFilePath = __dirname + '/pidfile',
 	start = function() {
-		nbb = fork('./app', process.argv.slice(2), {
-				env: {
-					'NODE_ENV': process.env.NODE_ENV
-				}
-			});
+		var	fork = require('child_process').fork,
+			nbb_start = function() {
+				nbb = fork('./app', process.argv.slice(2), {
+						env: {
+							'NODE_ENV': process.env.NODE_ENV
+						}
+					});
 
-		nbb.on('message', function(cmd) {
-			if (cmd === 'nodebb:restart') {
-				nbb.on('exit', function() {
-					start();
+				nbb.on('message', function(cmd) {
+					if (cmd === 'nodebb:restart') {
+						nbb.on('exit', function() {
+							nbb_start();
+						});
+						nbb.kill();
+					}
 				});
+			},
+			nbb_stop = function() {
 				nbb.kill();
-			}
-		});
-	},
-	stop = function() {
-		nbb.kill();
+				if (fs.existsSync(pidFilePath)) {
+					var	pid = parseInt(fs.readFileSync(pidFilePath, { encoding: 'utf-8' }), 10);
+					if (process.pid === pid) {
+						fs.unlinkSync(pidFilePath);
+					}
+				}
+			};
+
+		process.on('SIGINT', nbb_stop);
+		process.on('SIGTERM', nbb_stop);
+
+		nbb_start();
 	},
 	nbb;
 
-process.on('SIGINT', stop);
-process.on('SIGTERM', stop);
+nconf.argv();
+
+if (nconf.get('d')) {
+	// Check for a still-active NodeBB process
+	if (fs.existsSync(pidFilePath)) {
+		console.log('\n  Error: Another NodeBB is already running!');
+		process.exit();
+	}
+
+	// Initialise logging streams
+	var	outputStream = fs.createWriteStream(__dirname + '/logs/output.log');
+	outputStream.on('open', function(fd) {
+		// Daemonize
+		require('daemon')({
+			stdout: fd
+		});
+
+		// Write its pid to a pidfile
+		fs.writeFile(__dirname + '/pidfile', process.pid);
 
-start();
\ No newline at end of file
+		start();
+	});
+} else {
+	start();
+}
\ No newline at end of file
diff --git a/logs/.gitignore b/logs/.gitignore
new file mode 100644
index 0000000000..397b4a7624
--- /dev/null
+++ b/logs/.gitignore
@@ -0,0 +1 @@
+*.log
diff --git a/nodebb b/nodebb
index 0ea67b0a69..3e4d0e20a7 100755
--- a/nodebb
+++ b/nodebb
@@ -6,7 +6,19 @@
 
 case "$1" in
 	start)
-		node loader "$@"
+		echo "Starting NodeBB";
+		echo "  \"./nodebb stop\" to stop the NodeBB server";
+		echo "  \"./nodebb log\" to view server output";
+		node loader -d "$@"
+		;;
+
+	stop)
+		echo "Stopping NodeBB. Goodbye!";
+		kill `cat pidfile`;
+		;;
+
+	log)
+		tail -F ./logs/output.log;
 		;;
 
 	upgrade)
diff --git a/package.json b/package.json
index 221a6a781a..fec5bc2bab 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,8 @@
   },
   "main": "app.js",
   "scripts": {
+    "start": "./nodebb start",
+    "stop": "./nodebb stop",
     "test": "mocha ./tests"
   },
   "dependencies": {
@@ -43,7 +45,8 @@
     "nodebb-theme-vanilla": "~0.0.14",
     "nodebb-theme-cerulean": "~0.0.13",
     "nodebb-theme-lavender": "~0.0.22",
-    "less": "^1.6.3"
+    "less": "^1.6.3",
+    "daemon": "~1.1.0"
   },
   "optionalDependencies": {
     "redis": "0.8.3",
diff --git a/src/webserver.js b/src/webserver.js
index b7028efc5c..b4d5afe7b6 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -52,7 +52,7 @@ var	shutdown = function(code) {
 		db.close();
 		winston.info('[app] Database connection closed.');
 
-		winston.info('[app] Goodbye!');
+		winston.info('[app] Shutdown complete.');
 		process.exit();
 	},
 	restart = function() {

From 4567e5fbd00c816d3f9b0ac1496b49e96e6f1211 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Thu, 27 Feb 2014 10:13:36 -0500
Subject: [PATCH 100/193] updated help blurb in executable

---
 nodebb | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/nodebb b/nodebb
index 3e4d0e20a7..324594356e 100755
--- a/nodebb
+++ b/nodebb
@@ -54,13 +54,15 @@ case "$1" in
 
 	*)
 		echo "Welcome to NodeBB"
-		echo $"Usage: $0 {start|dev|watch|upgrade}"
+		echo $"Usage: $0 {start|stop|log|upgrade|dev|watch}"
 		echo ''
 		column -s '	' -t <<< '
-		start	Start NodeBB in production mode
-		dev	Start NodeBB in development mode
-		watch	Start NodeBB in development mode and watch for changes
+		start	Start the NodeBB server
+		stop	Stops the NodeBB server
+		log	Opens the logging interface (useful for debugging)
 		upgrade	Run NodeBB upgrade scripts, ensure packages are up-to-date
+		dev	Start NodeBB in interactive development mode
+		watch	Start NodeBB in development mode and watch for changes
 		'
 		exit 1
 esac

From c7274e11d02f4a6a8aa57c80796b33349ae771d0 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Thu, 27 Feb 2014 10:28:49 -0500
Subject: [PATCH 101/193] removing the bit of code that disables plugins if the
 minver does not satisfy (too annoying imo)... now that we have ./nodebb
 reset, this is moot

---
 src/plugins.js | 15 +--------------
 1 file changed, 1 insertion(+), 14 deletions(-)

diff --git a/src/plugins.js b/src/plugins.js
index 5dad12632a..30731712e8 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -114,20 +114,7 @@ var fs = require('fs'),
 			if (pluginData.minver && semver.validRange(pluginData.minver)) {
 				if (!semver.satisfies(pkg.version, pluginData.minver)) {
 					// If NodeBB is not new enough to run this plugin
-					if (process.env.NODE_ENV === 'development') {
-						// Throw a warning in development, but do nothing else
-						winston.warn('[plugins/' + pluginData.id + '] This plugin may not be compatible with your version of NodeBB. This may cause unintended behaviour or crashing.');
-					} else {
-						if (nconf.get('check-plugins') !== false) {
-							// Refuse to load this plugin...
-							winston.error('[plugins/' + pluginData.id + '] This plugin is reportedly incompatible with your version of NodeBB, so it has been disabled.');
-							winston.error('[plugins/' + pluginData.id + '] Use `--no-check-plugins` if you wish to live dangerously.');
-
-							// ... and disable it, too
-							Plugins.toggleActive(pluginData.id);
-							return callback();
-						}
-					}
+					winston.warn('[plugins/' + pluginData.id + '] This plugin may not be compatible with your version of NodeBB. This may cause unintended behaviour or crashing.');
 				}
 			}
 

From a7c53519b65fcc2448ea92e84158b5ddf1fc8157 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Thu, 27 Feb 2014 10:30:09 -0500
Subject: [PATCH 102/193] updated executable help

---
 nodebb | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/nodebb b/nodebb
index 324594356e..107d96259e 100755
--- a/nodebb
+++ b/nodebb
@@ -54,12 +54,14 @@ case "$1" in
 
 	*)
 		echo "Welcome to NodeBB"
-		echo $"Usage: $0 {start|stop|log|upgrade|dev|watch}"
+		echo $"Usage: $0 {start|stop|log|setup|reset|upgrade|dev|watch}"
 		echo ''
 		column -s '	' -t <<< '
 		start	Start the NodeBB server
 		stop	Stops the NodeBB server
 		log	Opens the logging interface (useful for debugging)
+		setup	Runs the NodeBB setup script
+		reset	Disables all plugins, restores the default theme.
 		upgrade	Run NodeBB upgrade scripts, ensure packages are up-to-date
 		dev	Start NodeBB in interactive development mode
 		watch	Start NodeBB in development mode and watch for changes

From 1a85d455672fcd9f9206a18ac155272a04885b7a Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Thu, 27 Feb 2014 10:41:59 -0500
Subject: [PATCH 103/193] portuguese and slovak translations

---
 .gitignore                               |  2 +
 public/language/pt_BR/global.json        | 10 +++--
 public/language/pt_BR/notifications.json |  4 +-
 public/language/pt_BR/pages.json         |  2 +-
 public/language/pt_BR/recent.json        |  2 +-
 public/language/pt_BR/topic.json         | 54 ++++++++++++++----------
 public/language/pt_BR/user.json          | 20 ++++-----
 public/language/sk/global.json           |  2 +
 public/language/sk/pages.json            |  2 +-
 public/language/sk/topic.json            | 10 ++++-
 10 files changed, 65 insertions(+), 43 deletions(-)

diff --git a/.gitignore b/.gitignore
index e743a14466..58bdac0c9d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,5 @@ feeds/recent.rss
 # winston?
 error.log
 events.log
+
+pidfile
diff --git a/public/language/pt_BR/global.json b/public/language/pt_BR/global.json
index 26d5525a81..efb6b78f23 100644
--- a/public/language/pt_BR/global.json
+++ b/public/language/pt_BR/global.json
@@ -10,14 +10,16 @@
     "500.message": "Oops! deu algo errado!",
     "register": "Cadastrar",
     "login": "Logar",
-    "welcome_back": "Welcome Back ",
-    "you_have_successfully_logged_in": "You have successfully logged in",
+    "please_log_in": "Por favor efetue o login",
+    "posting_restriction_info": "Postagens esta restritas para membros registrados. clique aqui para logar",
+    "welcome_back": "Bem vindo de volta",
+    "you_have_successfully_logged_in": "Você logou com sucesso",
     "logout": "Logout",
     "logout.title": "Logout com sucesso.",
     "logout.message": "Logado com Sucesso!",
     "save_changes": "Salvar Alterações",
     "close": "Fechar",
-    "pagination": "Pagination",
+    "pagination": "Paginação",
     "header.admin": "Admin",
     "header.recent": "Recente",
     "header.unread": "Não Lido",
@@ -52,5 +54,5 @@
     "dnd": "Não Perturbe",
     "invisible": "Invisível",
     "offline": "Offline",
-    "privacy": "Privacy"
+    "privacy": "Privacidade"
 }
\ No newline at end of file
diff --git a/public/language/pt_BR/notifications.json b/public/language/pt_BR/notifications.json
index fa7441b08d..253ec3cfdd 100644
--- a/public/language/pt_BR/notifications.json
+++ b/public/language/pt_BR/notifications.json
@@ -1,7 +1,7 @@
 {
     "title": "Notificações",
-    "no_notifs": "You have no new notifications",
-    "see_all": "See all Notifications",
+    "no_notifs": "Você não tem nenhuma notificação nova",
+    "see_all": "Visualizar todas as Notificações",
     "back_to_home": "voltar para home",
     "outgoing_link": "Link Externo",
     "outgoing_link_message": "Você está; saindo para um link externo",
diff --git a/public/language/pt_BR/pages.json b/public/language/pt_BR/pages.json
index 8f6d6f08e8..313eee0d2c 100644
--- a/public/language/pt_BR/pages.json
+++ b/public/language/pt_BR/pages.json
@@ -1,7 +1,7 @@
 {
     "home": "Home",
     "unread": "Tópicos Não Lidos",
-    "popular": "Popular Topics",
+    "popular": "Tópicos Populares",
     "recent": "Tópicos Recentes",
     "users": "Usuários Registrados",
     "notifications": "Notificações",
diff --git a/public/language/pt_BR/recent.json b/public/language/pt_BR/recent.json
index b67b2e88b4..9adf191ed6 100644
--- a/public/language/pt_BR/recent.json
+++ b/public/language/pt_BR/recent.json
@@ -3,5 +3,5 @@
     "day": "Dia",
     "week": "Semana",
     "month": "Mês",
-    "no_recent_topics": "There are no recent topics."
+    "no_recent_topics": "Nenhum tópico recente."
 }
\ No newline at end of file
diff --git a/public/language/pt_BR/topic.json b/public/language/pt_BR/topic.json
index a20ea66672..0e01e879f4 100644
--- a/public/language/pt_BR/topic.json
+++ b/public/language/pt_BR/topic.json
@@ -2,7 +2,7 @@
     "topic": "Tópico",
     "topics": "Tópicos",
     "no_topics_found": "Nenhum tópico encontrado!",
-    "no_posts_found": "No posts found!",
+    "no_posts_found": "Nenhum post encontrado!",
     "profile": "Profile",
     "posted_by": "Postado por",
     "chat": "Bate Papo",
@@ -19,19 +19,24 @@
     "tools": "Ferramentas",
     "flag": "Marcar",
     "flag_title": "Marcar este post para moderação",
-    "deleted_message": "This thread has been deleted. Only users with thread management privileges can see it.",
-    "watch": "Watch",
-    "share_this_post": "Share this Post",
+    "deleted_message": "Esta Thread foi deletada. Somente usuários com privilégios administrativos podem ver.",
+    "following_topic.title": "Seguir Tópico",
+    "following_topic.message": "Você receberá notificações quando alguém responder este tópico.",
+    "not_following_topic.title": "Não está seguindo Tópico",
+    "not_following_topic.message": "Você não irá mais receber notificações deste tópico.",
+    "login_to_subscribe": "Por favor registre ou logue para poder assinar este tópico",
+    "watch": "Acompanhar",
+    "share_this_post": "Compartilhar este Post",
     "thread_tools.title": "Ferramentas da Thread",
     "thread_tools.markAsUnreadForAll": "Marcar como não lido",
-    "thread_tools.pin": "Pin Topic",
-    "thread_tools.unpin": "Unpin Topic",
-    "thread_tools.lock": "Lock Topic",
-    "thread_tools.unlock": "Unlock Topic",
-    "thread_tools.move": "Move Topic",
-    "thread_tools.fork": "Fork Topic",
-    "thread_tools.delete": "Delete Topic",
-    "thread_tools.restore": "Restore Topic",
+    "thread_tools.pin": "Fixar Tópico",
+    "thread_tools.unpin": "Remover Fixação do Tópico",
+    "thread_tools.lock": "Trancar Tópico",
+    "thread_tools.unlock": "Destrancar Tópico",
+    "thread_tools.move": "Mover Tópico",
+    "thread_tools.fork": "Fork Tópico",
+    "thread_tools.delete": "Deletar Tópico",
+    "thread_tools.restore": "Restaurar Tópico",
     "load_categories": "Carregando Categorias",
     "disabled_categories_note": "Categorias desabilitadas estão em cinza",
     "confirm_move": "Mover",
@@ -41,10 +46,10 @@
     "favourites.not_logged_in.title": "Não Logado",
     "favourites.not_logged_in.message": "Por Favor logar para favoritar o tópico",
     "favourites.has_no_favourites": "Você não tem nenhum item favoritado!",
-    "vote.not_logged_in.title": "Not Logged In",
-    "vote.not_logged_in.message": "Please log in in order to vote",
-    "vote.cant_vote_self.title": "Invalid Vote",
-    "vote.cant_vote_self.message": "You cannot vote for your own post",
+    "vote.not_logged_in.title": "Não está logado",
+    "vote.not_logged_in.message": "Por favor efetuar login para votar",
+    "vote.cant_vote_self.title": "Voto inválido",
+    "vote.cant_vote_self.message": "Você não pode votar o seu próprio post",
     "loading_more_posts": "Carregando mais posts",
     "move_topic": "Mover Tó;pico",
     "move_post": "Mover Post",
@@ -55,11 +60,14 @@
     "fork_success": "Fork realizado com sucesso!",
     "reputation": "Reputação",
     "posts": "Posts",
-    "composer.title_placeholder": "Enter your topic title here...",
-    "composer.write": "Write",
-    "composer.preview": "Preview",
-    "composer.discard": "Discard",
-    "composer.submit": "Submit",
-    "composer.replying_to": "Replying to",
-    "composer.new_topic": "New Topic"
+    "composer.title_placeholder": "Digite seu tópico aqui...",
+    "composer.write": "Escreva",
+    "composer.preview": "Pré Visualização",
+    "composer.discard": "Descartar",
+    "composer.submit": "Enviar",
+    "composer.replying_to": "Respondendo para",
+    "composer.new_topic": "Novo Tópico",
+    "composer.drag_and_drop_images": "Clique e arraste imagens aqui",
+    "composer.content_is_parsed_with": "Conteúdo está separado com",
+    "composer.upload_instructions": "Mande suas imagens arrastando e soltando."
 }
\ No newline at end of file
diff --git a/public/language/pt_BR/user.json b/public/language/pt_BR/user.json
index 09270bb6d8..6991971504 100644
--- a/public/language/pt_BR/user.json
+++ b/public/language/pt_BR/user.json
@@ -9,7 +9,7 @@
     "age": "Idade",
     "joined": "Cadastrou",
     "lastonline": "Última vez online",
-    "profile": "Profile",
+    "profile": "Perfil",
     "profile_views": "Visualizações de Profile",
     "reputation": "Reputação",
     "posts": "Posts",
@@ -19,29 +19,29 @@
     "signature": "Assinatura",
     "gravatar": "Gravatar",
     "birthday": "Aniversário",
-    "chat": "Chat",
-    "follow": "Follow",
-    "unfollow": "Unfollow",
+    "chat": "Bate Papo",
+    "follow": "Seguir",
+    "unfollow": "Deixar de Seguir",
     "change_picture": "Alterar Foto",
     "edit": "Editar",
     "uploaded_picture": "Foto Carregada",
     "upload_new_picture": "Carregar novo Foto",
-    "current_password": "Current Password",
+    "current_password": "Senha Atual",
     "change_password": "Alterar Senha",
     "confirm_password": "Confirmar Senha",
     "password": "Senha",
     "upload_picture": "Carregar Foto",
     "upload_a_picture": "Carregar Foto",
-    "image_spec": "You may only upload PNG, JPG, or GIF files",
+    "image_spec": "Você pode usar somente arquivos PNG, JPG ou GIF",
     "max": "max.",
     "settings": "Configurações",
     "show_email": "Mostrar meu email",
     "has_no_follower": "Ninguém está seguindo esse usuário :(",
     "follows_no_one": "Este usuário não está seguindo ninguém :(",
-    "has_no_posts": "This user didn't post anything yet.",
+    "has_no_posts": "Este usuário não postou nada ainda.",
     "email_hidden": "Email Escondido",
     "hidden": "Escondido",
-    "paginate_description": "Paginate topics and posts instead of using infinite scroll.",
-    "topics_per_page": "Topics per Page",
-    "posts_per_page": "Posts per Page"
+    "paginate_description": "Paginação de tópicos e posts ao invés de usar \"scroll infinito\"",
+    "topics_per_page": "Tópicos por Página",
+    "posts_per_page": "Posts por Página"
 }
\ No newline at end of file
diff --git a/public/language/sk/global.json b/public/language/sk/global.json
index 6fa7361dca..cf17eb234d 100644
--- a/public/language/sk/global.json
+++ b/public/language/sk/global.json
@@ -10,6 +10,8 @@
     "500.message": "Jejda, vyzerá, že sa niečo pokazilo.",
     "register": "Registrovať",
     "login": "Prihlásiť sa",
+    "please_log_in": "Prosím Prihláste sa",
+    "posting_restriction_info": "Prispievanie je obmedzené len pre registrovaných, kliknite pre Prihlásenie sa ",
     "welcome_back": "Vitaj naspäť",
     "you_have_successfully_logged_in": "Úspešne si sa prihlásil",
     "logout": "Odhlásiť sa",
diff --git a/public/language/sk/pages.json b/public/language/sk/pages.json
index d60e0a0a9b..4fa2659252 100644
--- a/public/language/sk/pages.json
+++ b/public/language/sk/pages.json
@@ -1,7 +1,7 @@
 {
     "home": "Home",
     "unread": "Unread Topics",
-    "popular": "Popular Topics",
+    "popular": "Populárne Témy",
     "recent": "Recent Topics",
     "users": "Registered Users",
     "notifications": "Notifications",
diff --git a/public/language/sk/topic.json b/public/language/sk/topic.json
index 4bf7ffe6cd..b7a2bac591 100644
--- a/public/language/sk/topic.json
+++ b/public/language/sk/topic.json
@@ -20,6 +20,11 @@
     "flag": "Označiť",
     "flag_title": "Označiť príspevok pre moderáciu",
     "deleted_message": "Toto vlákno bolo vymazané. Iba užívatelia s privilégiami ho môžu vidieť.",
+    "following_topic.title": "Sledovať Tému",
+    "following_topic.message": "Budete teraz príjimať notifikácie, ked niekto prispeje do témy.",
+    "not_following_topic.title": "Nesledujete Tému",
+    "not_following_topic.message": "Nebudete už dostávať notifikácie z tejto Témy",
+    "login_to_subscribe": "Prosím Zaregistrujte sa alebo sa Prihláste, aby ste mohli odoberať túto Tému",
     "watch": "Sledovať",
     "share_this_post": "Zdielaj tento príspevok",
     "thread_tools.title": "Nástroje",
@@ -61,5 +66,8 @@
     "composer.discard": "Zahodiť",
     "composer.submit": "Poslať",
     "composer.replying_to": "Odpovedáš ",
-    "composer.new_topic": "Nová téma"
+    "composer.new_topic": "Nová téma",
+    "composer.drag_and_drop_images": "Pretiahni a Pusť Obrázky Sem",
+    "composer.content_is_parsed_with": "Obsah je vybraný s",
+    "composer.upload_instructions": "Nahraj obrázky pretiahnutím a pustením ich."
 }
\ No newline at end of file

From 0ca6c58ded6b61a872a7a2e8a49837b77b834806 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Thu, 27 Feb 2014 12:15:26 -0500
Subject: [PATCH 104/193] closes #1137

---
 public/language/en_GB/topic.json | 4 +++-
 public/src/forum/topic.js        | 2 ++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json
index 085f8df821..dc47458923 100644
--- a/public/language/en_GB/topic.json
+++ b/public/language/en_GB/topic.json
@@ -32,7 +32,9 @@
 	"not_following_topic.title": "Not Following Topic",
 	"not_following_topic.message": "You will no longer receive notifications from this topic.",
 
-	"login_to_subscribe": "Please register or log in in order to subscribe to this topic",
+	"login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+
+	"markAsUnreadForAll.success" : "Topic marked as unread for all.",
 
 	"watch": "Watch",
 	"share_this_post": "Share this Post",
diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index d220494b86..f603ca4aba 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -108,6 +108,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 						if(err) {
 							return app.alertError(err.message);
 						}
+						app.alertSuccess('[[topic:markAsUnreadForAll.success]]');
 						btn.parents('.thread-tools.open').find('.dropdown-toggle').trigger('click');
 					});
 					return false;
@@ -323,6 +324,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 						localStorage.removeItem('topic:' + tid + ':bookmark');
 					}
 				});
+				updateHeader();
 			} else {
 				updateHeader();
 			}

From 44ac7ec2627b5698b535c197bd4cd3ebe74238ef Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Thu, 27 Feb 2014 14:05:31 -0500
Subject: [PATCH 105/193] added new hooks for rendering help messages in
 composer, removed markdown text from translation

---
 package.json                     | 2 +-
 public/language/en_GB/topic.json | 1 -
 public/src/modules/composer.js   | 7 +++++++
 public/templates/composer.tpl    | 5 ++++-
 src/socket.io/modules.js         | 6 +++++-
 5 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/package.json b/package.json
index fec5bc2bab..9024cc068b 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,7 @@
     "socket.io-wildcard": "~0.1.1",
     "bcryptjs": "~0.7.10",
     "nodebb-plugin-mentions": "~0.4",
-    "nodebb-plugin-markdown": "~0.3",
+    "nodebb-plugin-markdown": "~0.4",
     "nodebb-widget-essentials": "~0.0",
     "nodebb-theme-vanilla": "~0.0.14",
     "nodebb-theme-cerulean": "~0.0.13",
diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json
index 085f8df821..d92093720d 100644
--- a/public/language/en_GB/topic.json
+++ b/public/language/en_GB/topic.json
@@ -91,6 +91,5 @@
 	"composer.thumb_file_label": "Or upload a file",
 	"composer.thumb_remove": "Clear fields",
 	"composer.drag_and_drop_images": "Drag and Drop Images Here",
-	"composer.content_is_parsed_with": "Content is parsed with",
 	"composer.upload_instructions": "Upload images by dragging & dropping them."
 }
diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js
index 45b8c2b974..1862092834 100644
--- a/public/src/modules/composer.js
+++ b/public/src/modules/composer.js
@@ -638,6 +638,13 @@ define(['taskbar'], function(taskbar) {
 					}
 				});
 
+				socket.emit('modules.composer.renderHelp', function(err, html) {
+					if (html && html.length > 0) {
+						postContainer.find('.help').html(html);
+						postContainer.find('[data-pane=".tab-help"]').parent().removeClass('hidden');
+					}
+				});
+
 				$(window).trigger('action:composer.loaded', {
 					post_uuid: post_uuid
 				});
diff --git a/public/templates/composer.tpl b/public/templates/composer.tpl
index 584ba48153..7302c9a040 100644
--- a/public/templates/composer.tpl
+++ b/public/templates/composer.tpl
@@ -58,6 +58,7 @@
 		<ul class="nav nav-tabs">
 			<li class="active"><a data-pane=".tab-write" data-toggle="tab">[[topic:composer.write]]</a></li>
 			<li><a data-pane=".tab-preview" data-toggle="tab">[[topic:composer.preview]]</a></li>
+			<li class="hidden"><a data-pane=".tab-help" data-toggle="tab">[[topic:composer.help]]</a></li>
 			<li class="btn-group pull-right action-bar">
 				<button class="btn btn-default" data-action="discard" tabIndex="5"><i class="fa fa-times"></i> [[topic:composer.discard]]</button>
 				<button data-action="post" class="btn btn-default btn-primary" tabIndex="3"><i class="fa fa-check"></i> [[topic:composer.submit]]</button>
@@ -71,13 +72,15 @@
 			<div class="tab-pane tab-preview">
 				<div class="preview well"></div>
 			</div>
+			<div class="tab-pane tab-help">
+				<div class="help well"></div>
+			</div>
 		</div>
 
 		<div class="imagedrop"><div>[[topic:composer.drag_and_drop_images]]</div></div>
 
 		<div class="text-center instructions">
 			<span>
-				<small>[[topic:composer.content_is_parsed_with]] <a href="http://daringfireball.net/projects/markdown/syntax">Markdown</a>. </small>
 				<span class="upload-instructions hide"><small>[[topic:composer.upload_instructions]]</small></span>
 			</span>
 
diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js
index c5e23dc92b..a9d9d659be 100644
--- a/src/socket.io/modules.js
+++ b/src/socket.io/modules.js
@@ -68,7 +68,11 @@ SocketModules.composer.editCheck = function(socket, pid, callback) {
 
 SocketModules.composer.renderPreview = function(socket, content, callback) {
 	plugins.fireHook('filter:post.parse', content, callback);
-}
+};
+
+SocketModules.composer.renderHelp = function(socket, data, callback) {
+	plugins.fireHook('filter:composer.help', '', callback);
+};
 
 /* Chat */
 

From e2fb3dacca6265af98fd3e7176b5aa90d5a0b244 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Thu, 27 Feb 2014 14:10:13 -0500
Subject: [PATCH 106/193] pushing new language fallbacks

---
 public/language/ar/category.json    |  3 ---
 public/language/ar/global.json      |  3 +++
 public/language/ar/pages.json       |  1 +
 public/language/ar/topic.json       | 19 ++++++++++++++++++-
 public/language/cs/category.json    |  3 ---
 public/language/cs/global.json      |  3 +++
 public/language/cs/pages.json       |  1 +
 public/language/cs/topic.json       | 19 ++++++++++++++++++-
 public/language/de/category.json    |  3 ---
 public/language/de/global.json      |  3 +++
 public/language/de/pages.json       |  3 ++-
 public/language/de/topic.json       | 19 ++++++++++++++++++-
 public/language/en_GB/topic.json    |  1 +
 public/language/es/category.json    |  3 ---
 public/language/es/global.json      |  5 ++++-
 public/language/es/pages.json       |  1 +
 public/language/es/topic.json       | 19 ++++++++++++++++++-
 public/language/fi/category.json    |  3 ---
 public/language/fi/global.json      |  1 +
 public/language/fi/pages.json       |  1 +
 public/language/fi/topic.json       | 13 +++++++++++--
 public/language/fr/category.json    |  3 ---
 public/language/fr/global.json      |  3 +++
 public/language/fr/pages.json       |  3 ++-
 public/language/fr/topic.json       | 19 ++++++++++++++++++-
 public/language/he/category.json    |  3 ---
 public/language/he/global.json      |  3 +++
 public/language/he/pages.json       |  1 +
 public/language/he/topic.json       | 19 ++++++++++++++++++-
 public/language/hu/category.json    |  3 ---
 public/language/hu/global.json      |  3 +++
 public/language/hu/pages.json       |  1 +
 public/language/hu/topic.json       | 13 +++++++++++--
 public/language/it/category.json    |  3 ---
 public/language/it/global.json      |  1 +
 public/language/it/pages.json       |  1 +
 public/language/it/topic.json       | 21 +++++++++++++++------
 public/language/nb/category.json    |  3 ---
 public/language/nb/global.json      |  3 +++
 public/language/nb/pages.json       |  3 ++-
 public/language/nb/topic.json       | 19 ++++++++++++++++++-
 public/language/nl/category.json    |  3 ---
 public/language/nl/global.json      |  1 +
 public/language/nl/pages.json       |  1 +
 public/language/nl/topic.json       | 13 +++++++++++--
 public/language/pl/category.json    |  3 ---
 public/language/pl/global.json      |  1 +
 public/language/pl/pages.json       |  1 +
 public/language/pl/topic.json       | 13 +++++++++++--
 public/language/pt_BR/category.json |  3 ---
 public/language/pt_BR/global.json   |  1 +
 public/language/pt_BR/pages.json    |  1 +
 public/language/pt_BR/topic.json    | 13 +++++++++++--
 public/language/ru/category.json    |  3 ---
 public/language/ru/global.json      |  3 +++
 public/language/ru/pages.json       |  1 +
 public/language/ru/topic.json       | 19 ++++++++++++++++++-
 public/language/sk/category.json    |  3 ---
 public/language/sk/global.json      |  1 +
 public/language/sk/pages.json       |  1 +
 public/language/sk/topic.json       | 13 +++++++++++--
 public/language/sv/category.json    |  3 ---
 public/language/sv/global.json      |  3 +++
 public/language/sv/pages.json       |  1 +
 public/language/sv/topic.json       | 19 ++++++++++++++++++-
 public/language/tr/category.json    |  3 ---
 public/language/tr/global.json      |  3 +++
 public/language/tr/pages.json       |  1 +
 public/language/tr/topic.json       | 19 ++++++++++++++++++-
 public/language/zh_CN/category.json |  3 ---
 public/language/zh_CN/global.json   |  3 ++-
 public/language/zh_CN/pages.json    |  1 +
 public/language/zh_CN/topic.json    | 13 +++++++++++--
 public/language/zh_TW/category.json |  3 ---
 public/language/zh_TW/global.json   |  3 +++
 public/language/zh_TW/pages.json    |  1 +
 public/language/zh_TW/topic.json    | 19 ++++++++++++++++++-
 77 files changed, 358 insertions(+), 93 deletions(-)

diff --git a/public/language/ar/category.json b/public/language/ar/category.json
index 53f8e469b3..382c60fd40 100644
--- a/public/language/ar/category.json
+++ b/public/language/ar/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "موضوع جديد",
     "no_topics": "<strong>لا توجد مواضيع في هذه الفئة</strong>لماذا لا تحاول نشر واحد؟<br />",
-    "sidebar.recent_replies": "الردود مؤخرا",
-    "sidebar.active_participants": "المشاركون النشطة",
-    "sidebar.moderators": "المشرفين",
     "posts": "مشاركات",
     "views": "مشاهدات",
     "posted": "نشر",
diff --git a/public/language/ar/global.json b/public/language/ar/global.json
index 6a8eb3ed8c..ffe888a143 100644
--- a/public/language/ar/global.json
+++ b/public/language/ar/global.json
@@ -10,6 +10,8 @@
     "500.message": "عفوا! يبدو وكأنه شيء ذهب على نحو خاطئ!",
     "register": "تسجيل",
     "login": "دخول",
+    "please_log_in": "Please Log In",
+    "posting_restriction_info": "Posting is currently restricted to registered members only, click here to log in.",
     "welcome_back": "Welcome Back ",
     "you_have_successfully_logged_in": "You have successfully logged in",
     "logout": "تسجيل الخروج",
@@ -47,6 +49,7 @@
     "posted": "posted",
     "in": "in",
     "recentposts": "Recent Posts",
+    "recentips": "Recently Logged In IPs",
     "online": "Online",
     "away": "Away",
     "dnd": "Do not Disturb",
diff --git a/public/language/ar/pages.json b/public/language/ar/pages.json
index d60e0a0a9b..6b41654688 100644
--- a/public/language/ar/pages.json
+++ b/public/language/ar/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "Editing \"%1\"",
     "user.following": "People %1 Follows",
     "user.followers": "People who Follow %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "%1's Favourite Posts",
     "user.settings": "User Settings"
 }
\ No newline at end of file
diff --git a/public/language/ar/topic.json b/public/language/ar/topic.json
index 81f8163fdf..f243cabfc7 100644
--- a/public/language/ar/topic.json
+++ b/public/language/ar/topic.json
@@ -11,6 +11,7 @@
     "reply": "رد",
     "edit": "صحح",
     "delete": "حذف",
+    "restore": "Restore",
     "move": "انقل",
     "fork": "فرع",
     "banned": "محظور",
@@ -18,8 +19,15 @@
     "share": "شارك",
     "tools": "أدوات",
     "flag": "Flag",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Flag this post for moderation",
     "deleted_message": "This thread has been deleted. Only users with thread management privileges can see it.",
+    "following_topic.title": "Following Topic",
+    "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
+    "not_following_topic.title": "Not Following Topic",
+    "not_following_topic.message": "You will no longer receive notifications from this topic.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Watch",
     "share_this_post": "Share this Post",
     "thread_tools.title": "أدوات الموضوع",
@@ -58,8 +66,17 @@
     "composer.title_placeholder": "Enter your topic title here...",
     "composer.write": "Write",
     "composer.preview": "Preview",
+    "composer.help": "Help",
     "composer.discard": "Discard",
     "composer.submit": "Submit",
     "composer.replying_to": "Replying to",
-    "composer.new_topic": "New Topic"
+    "composer.new_topic": "New Topic",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
+    "composer.drag_and_drop_images": "Drag and Drop Images Here",
+    "composer.upload_instructions": "Upload images by dragging & dropping them."
 }
\ No newline at end of file
diff --git a/public/language/cs/category.json b/public/language/cs/category.json
index 9c2f2981a3..61fc76f497 100644
--- a/public/language/cs/category.json
+++ b/public/language/cs/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Nové téma",
     "no_topics": "<strong>V této kategorii zatím nejsou žádné příspěvky.</strong><br />Můžeš být první!",
-    "sidebar.recent_replies": "Poslední příspěvky",
-    "sidebar.active_participants": "Aktivní účastníci",
-    "sidebar.moderators": "Moderátoři",
     "posts": "příspěvky",
     "views": "zobrazení",
     "posted": "odesláno",
diff --git a/public/language/cs/global.json b/public/language/cs/global.json
index db2aaee987..602ebc5dcd 100644
--- a/public/language/cs/global.json
+++ b/public/language/cs/global.json
@@ -10,6 +10,8 @@
     "500.message": "Jejda, vypadá to, že se něco pokazilo.",
     "register": "Registrovat",
     "login": "Přihlásit se",
+    "please_log_in": "Please Log In",
+    "posting_restriction_info": "Posting is currently restricted to registered members only, click here to log in.",
     "welcome_back": "Welcome Back ",
     "you_have_successfully_logged_in": "You have successfully logged in",
     "logout": "Odhlásit se",
@@ -47,6 +49,7 @@
     "posted": "odesláno",
     "in": "v",
     "recentposts": "Nedávné příspěvky",
+    "recentips": "Recently Logged In IPs",
     "online": "Online",
     "away": "Pryč",
     "dnd": "Nerušit",
diff --git a/public/language/cs/pages.json b/public/language/cs/pages.json
index d60e0a0a9b..6b41654688 100644
--- a/public/language/cs/pages.json
+++ b/public/language/cs/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "Editing \"%1\"",
     "user.following": "People %1 Follows",
     "user.followers": "People who Follow %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "%1's Favourite Posts",
     "user.settings": "User Settings"
 }
\ No newline at end of file
diff --git a/public/language/cs/topic.json b/public/language/cs/topic.json
index f24a403223..f259a117c7 100644
--- a/public/language/cs/topic.json
+++ b/public/language/cs/topic.json
@@ -11,6 +11,7 @@
     "reply": "Odpovědět",
     "edit": "Upravit",
     "delete": "Smazat",
+    "restore": "Restore",
     "move": "Přesunout",
     "fork": "Rozdělit",
     "banned": "banned",
@@ -18,8 +19,15 @@
     "share": "Sdílet",
     "tools": "Nástroje",
     "flag": "Flag",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Flag this post for moderation",
     "deleted_message": "This thread has been deleted. Only users with thread management privileges can see it.",
+    "following_topic.title": "Following Topic",
+    "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
+    "not_following_topic.title": "Not Following Topic",
+    "not_following_topic.message": "You will no longer receive notifications from this topic.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Watch",
     "share_this_post": "Share this Post",
     "thread_tools.title": "Nástroje",
@@ -58,8 +66,17 @@
     "composer.title_placeholder": "Enter your topic title here...",
     "composer.write": "Write",
     "composer.preview": "Preview",
+    "composer.help": "Help",
     "composer.discard": "Discard",
     "composer.submit": "Submit",
     "composer.replying_to": "Replying to",
-    "composer.new_topic": "New Topic"
+    "composer.new_topic": "New Topic",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
+    "composer.drag_and_drop_images": "Drag and Drop Images Here",
+    "composer.upload_instructions": "Upload images by dragging & dropping them."
 }
\ No newline at end of file
diff --git a/public/language/de/category.json b/public/language/de/category.json
index 72ad87d4e9..62e1993357 100644
--- a/public/language/de/category.json
+++ b/public/language/de/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Neues Thema",
     "no_topics": "<strong>Es gibt noch keine Threads in dieser Kategorie.</strong><br />Warum beginnst du nicht den ersten?",
-    "sidebar.recent_replies": "Neuste Antworten",
-    "sidebar.active_participants": "Aktive Teilnehmer",
-    "sidebar.moderators": "Moderatoren",
     "posts": "Posts",
     "views": "Aufrufe",
     "posted": "Geposted",
diff --git a/public/language/de/global.json b/public/language/de/global.json
index 7a6910df88..a577429cff 100644
--- a/public/language/de/global.json
+++ b/public/language/de/global.json
@@ -10,6 +10,8 @@
     "500.message": "Ooops! Looks like something went wrong!",
     "register": "Registrierung",
     "login": "Login",
+    "please_log_in": "Bitte einloggen",
+    "posting_restriction_info": "Nur registrierte Mitglieder dürfen Beiträge verfassen. Hier klicken zum Einloggen.",
     "welcome_back": "Willkommen zurück",
     "you_have_successfully_logged_in": "Du hast dich erfolgreich eingeloggt",
     "logout": "Logout",
@@ -47,6 +49,7 @@
     "posted": "geposted",
     "in": "in",
     "recentposts": "Aktuelle Beiträge",
+    "recentips": "Recently Logged In IPs",
     "online": "Online",
     "away": "Abwesend",
     "dnd": "Nicht stören",
diff --git a/public/language/de/pages.json b/public/language/de/pages.json
index d60e0a0a9b..7a755df1f5 100644
--- a/public/language/de/pages.json
+++ b/public/language/de/pages.json
@@ -1,13 +1,14 @@
 {
     "home": "Home",
     "unread": "Unread Topics",
-    "popular": "Popular Topics",
+    "popular": "Beliebte Themen",
     "recent": "Recent Topics",
     "users": "Registered Users",
     "notifications": "Notifications",
     "user.edit": "Editing \"%1\"",
     "user.following": "People %1 Follows",
     "user.followers": "People who Follow %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "%1's Favourite Posts",
     "user.settings": "User Settings"
 }
\ No newline at end of file
diff --git a/public/language/de/topic.json b/public/language/de/topic.json
index 2c073da3db..c0901d1700 100644
--- a/public/language/de/topic.json
+++ b/public/language/de/topic.json
@@ -11,6 +11,7 @@
     "reply": "antworten",
     "edit": "bearbeiten",
     "delete": "löschen",
+    "restore": "Restore",
     "move": "Verschieben",
     "fork": "Aufspalten",
     "banned": "gesperrt",
@@ -18,8 +19,15 @@
     "share": "Teilen",
     "tools": "Tools",
     "flag": "Markieren",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Diesen Beitrag zur Moderation markieren",
     "deleted_message": "Dieser Thread wurde gelöscht. Nur Nutzer mit Thread-Management Rechten können ihn sehen.",
+    "following_topic.title": "Thema wird gefolgt",
+    "following_topic.message": "Du erhälst nun eine Benachrichtigung, wenn jemand einen Beitrag zu diesem Thema verfasst.",
+    "not_following_topic.title": "Thema nicht gefolgt",
+    "not_following_topic.message": "Du erhälst keine weiteren Benachrichtigungen zu diesem Thema.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Beobachten",
     "share_this_post": "Diesen Beitrag teilen",
     "thread_tools.title": "Thread Tools",
@@ -58,8 +66,17 @@
     "composer.title_placeholder": "Hier den Titel des Themas eingeben...",
     "composer.write": "Schreiben",
     "composer.preview": "Vorschau",
+    "composer.help": "Help",
     "composer.discard": "Verwerfen",
     "composer.submit": "Absenden",
     "composer.replying_to": "Als Antwort auf",
-    "composer.new_topic": "Neues Thema"
+    "composer.new_topic": "Neues Thema",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
+    "composer.drag_and_drop_images": "Bilder hier reinziehen",
+    "composer.upload_instructions": "Zum Hochladen Bilder hier reinziehen."
 }
\ No newline at end of file
diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json
index 1955bd7e36..00f2358c56 100644
--- a/public/language/en_GB/topic.json
+++ b/public/language/en_GB/topic.json
@@ -81,6 +81,7 @@
 	"composer.title_placeholder": "Enter your topic title here...",
 	"composer.write": "Write",
 	"composer.preview": "Preview",
+	"composer.help": "Help",
 	"composer.discard": "Discard",
 	"composer.submit": "Submit",
 	"composer.replying_to": "Replying to",
diff --git a/public/language/es/category.json b/public/language/es/category.json
index d75bf5e916..ee5b39465a 100644
--- a/public/language/es/category.json
+++ b/public/language/es/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Nuevo Tema",
     "no_topics": "<strong>No hay temas en esta categoría.</strong><br />Por que no te animas y publicas uno?",
-    "sidebar.recent_replies": "Respuestas recientes",
-    "sidebar.active_participants": "Miembros más activos",
-    "sidebar.moderators": "Moderadores",
     "posts": "respuestas",
     "views": "visitas",
     "posted": "posted",
diff --git a/public/language/es/global.json b/public/language/es/global.json
index 8fa7474ad2..7bb6ec6d41 100644
--- a/public/language/es/global.json
+++ b/public/language/es/global.json
@@ -10,6 +10,8 @@
     "500.message": "Ooops! Algo salio mal!, No te alarmes. Nuestros simios hiperinteligentes lo solucionarán",
     "register": "Registrarse",
     "login": "Conectarse",
+    "please_log_in": "Por favor conectate.",
+    "posting_restriction_info": "Para publicar debes ser miembro, registrate o conectate.",
     "welcome_back": "Bienvenido de nuevo!",
     "you_have_successfully_logged_in": "Te has conectado!",
     "logout": "Salir",
@@ -47,10 +49,11 @@
     "posted": "publicado",
     "in": "en",
     "recentposts": "Publicaciones Recientes",
+    "recentips": "Recently Logged In IPs",
     "online": "Conectado",
     "away": "No disponible",
     "dnd": "No molestar",
     "invisible": "Invisible",
     "offline": "Desconectado",
     "privacy": "Privacidad"
-}
+}
\ No newline at end of file
diff --git a/public/language/es/pages.json b/public/language/es/pages.json
index 6d91d53482..603c8ace7a 100644
--- a/public/language/es/pages.json
+++ b/public/language/es/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "Editando \"%1\"",
     "user.following": "Gente que sigue %1 ",
     "user.followers": "Seguidores de %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "Publicaciones favoritas de %1 ",
     "user.settings": "Preferencias del Usuario"
 }
\ No newline at end of file
diff --git a/public/language/es/topic.json b/public/language/es/topic.json
index d826369a84..ce34ffa2a7 100644
--- a/public/language/es/topic.json
+++ b/public/language/es/topic.json
@@ -11,6 +11,7 @@
     "reply": "Responder",
     "edit": "Editar",
     "delete": "Borrar",
+    "restore": "Restore",
     "move": "Mover",
     "fork": "Bifurcar",
     "banned": "baneado",
@@ -18,8 +19,15 @@
     "share": "Compartir",
     "tools": "Herramientas",
     "flag": "Reportar",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Reportar esta publicación a los moderadores",
     "deleted_message": "Este tema ha sido borrado. Solo los miembros con privilegios pueden verlo.",
+    "following_topic.title": "Siguendo tema",
+    "following_topic.message": "Ahora recibiras notificaciones cuando alguien publique en este tema.",
+    "not_following_topic.title": "No sigues este tema",
+    "not_following_topic.message": "No recibiras notificaciones de este tema.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Seguir",
     "share_this_post": "Compartir este post",
     "thread_tools.title": "Herramientas del Tema",
@@ -58,8 +66,17 @@
     "composer.title_placeholder": "Ingresa el titulo de tu tema",
     "composer.write": "Escribe",
     "composer.preview": "Previsualización",
+    "composer.help": "Help",
     "composer.discard": "Descartar",
     "composer.submit": "Enviar",
     "composer.replying_to": "Respondiendo a",
-    "composer.new_topic": "Nuevo Tema"
+    "composer.new_topic": "Nuevo Tema",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
+    "composer.drag_and_drop_images": "Arrastra las imagenes aqui",
+    "composer.upload_instructions": "Carga tus imagenes con solo arrastrarlas aqui."
 }
\ No newline at end of file
diff --git a/public/language/fi/category.json b/public/language/fi/category.json
index 3512fae9d5..d9ee3f3ad4 100644
--- a/public/language/fi/category.json
+++ b/public/language/fi/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Uusi aihe",
     "no_topics": "<strong>Tällä aihealueella ei ole yhtään aihetta.</strong><br />Miksi et aloittaisi uutta?",
-    "sidebar.recent_replies": "Viimeisimmät vastaukset",
-    "sidebar.active_participants": "Aktiiviset keskustelijat",
-    "sidebar.moderators": "Moderaattorit",
     "posts": "viestit",
     "views": "katsottu",
     "posted": "kirjoitettu",
diff --git a/public/language/fi/global.json b/public/language/fi/global.json
index b5d7aab994..2be70cb8a4 100644
--- a/public/language/fi/global.json
+++ b/public/language/fi/global.json
@@ -49,6 +49,7 @@
     "posted": "kirjoitettu",
     "in": "alueelle",
     "recentposts": "Viimeisimmät viestit",
+    "recentips": "Recently Logged In IPs",
     "online": "Online",
     "away": "Poissa",
     "dnd": "Älä häiritse",
diff --git a/public/language/fi/pages.json b/public/language/fi/pages.json
index 49a7dca36a..aaf5763470 100644
--- a/public/language/fi/pages.json
+++ b/public/language/fi/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "Muokataan \"%1\"",
     "user.following": "Käyttäjät, joita %1 seuraa",
     "user.followers": "Käyttäjät, jotka seuraavat käyttäjää %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "Käyttäjän %1 suosikkiviestit",
     "user.settings": "Käyttäjän asetukset"
 }
\ No newline at end of file
diff --git a/public/language/fi/topic.json b/public/language/fi/topic.json
index c7dcff3a0f..6172216102 100644
--- a/public/language/fi/topic.json
+++ b/public/language/fi/topic.json
@@ -11,6 +11,7 @@
     "reply": "Vastaa",
     "edit": "Muokkaa",
     "delete": "Poista",
+    "restore": "Restore",
     "move": "Siirrä",
     "fork": "Haaroita",
     "banned": "estetty",
@@ -18,13 +19,15 @@
     "share": "Jaa",
     "tools": "Työkalut",
     "flag": "Ilmianna",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Ilmianna tämä viesti moderaattoreille",
     "deleted_message": "Tämä viestiketju on poistettu. Vain käyttäjät, joilla on viestiketjujen hallintaoikeudet, voivat nähdä sen.",
     "following_topic.title": "Seurataan aihetta",
     "following_topic.message": "Saat nyt ilmoituksen, kun joku kirjoittaa tähän aiheeseen.",
     "not_following_topic.title": "Et seuraa aihetta",
     "not_following_topic.message": "Et saa enää ilmoituksia tästä aiheesta.",
-    "login_to_subscribe": "Ole hyvä ja rekisteröidy tai kirjaudu sisään tilataksesi tämän aiheen",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Tarkkaile",
     "share_this_post": "Jaa tämä viesti",
     "thread_tools.title": "Aiheen työkalut",
@@ -63,11 +66,17 @@
     "composer.title_placeholder": "Syötä aiheesi otsikko tähän...",
     "composer.write": "Kirjoita",
     "composer.preview": "Esikatsele",
+    "composer.help": "Help",
     "composer.discard": "Hylkää",
     "composer.submit": "Lähetä",
     "composer.replying_to": "Vastataan aiheeseen",
     "composer.new_topic": "Uusi aihe",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
     "composer.drag_and_drop_images": "Vedä ja pudota kuvat tähän",
-    "composer.content_is_parsed_with": "Sisältö jäsennetään muodossa",
     "composer.upload_instructions": "Lataa kuvia vetämällä & pudottamalla ne."
 }
\ No newline at end of file
diff --git a/public/language/fr/category.json b/public/language/fr/category.json
index 4543da7ba3..cc53bc321b 100644
--- a/public/language/fr/category.json
+++ b/public/language/fr/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Nouveau Sujet",
     "no_topics": "<strong>Il n'y a aucun topic dans cette catégorie.</strong><br />Pourquoi ne pas en créer un?",
-    "sidebar.recent_replies": "Réponses Récentes",
-    "sidebar.active_participants": "Participants Actifs",
-    "sidebar.moderators": "Modérateurs",
     "posts": "messages",
     "views": "vues",
     "posted": "posté",
diff --git a/public/language/fr/global.json b/public/language/fr/global.json
index bfe824c9e2..3a560db0a1 100644
--- a/public/language/fr/global.json
+++ b/public/language/fr/global.json
@@ -10,6 +10,8 @@
     "500.message": "Oops! Il semblerait que quelque chose se soit mal passé!",
     "register": "S'inscrire",
     "login": "Connecter",
+    "please_log_in": "Connectez vous",
+    "posting_restriction_info": "L'écriture de message est réservée aux membres enregistrés, cliquer ici pour se connecter",
     "welcome_back": "Bon retour parmis nous",
     "you_have_successfully_logged_in": "Vous vous êtes connecté avec succès.",
     "logout": "Déconnection",
@@ -47,6 +49,7 @@
     "posted": "posté",
     "in": "dans",
     "recentposts": "Messages Récents",
+    "recentips": "Recently Logged In IPs",
     "online": "En ligne",
     "away": "Absent",
     "dnd": "Occupé",
diff --git a/public/language/fr/pages.json b/public/language/fr/pages.json
index 3c61eb6cf8..e20ff0444c 100644
--- a/public/language/fr/pages.json
+++ b/public/language/fr/pages.json
@@ -1,13 +1,14 @@
 {
     "home": "Accueil",
     "unread": "Sujets non lus",
-    "popular": "Popular Topics",
+    "popular": "Sujets Populaires",
     "recent": "Sujets Récents",
     "users": "Utilisateurs enregistrés",
     "notifications": "Notifications",
     "user.edit": "Edite \"%1\"",
     "user.following": "Personnes que %1 suit",
     "user.followers": "Personnes qui suivent  %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "Messages favoris de %1",
     "user.settings": "Préférences Utilisateur"
 }
\ No newline at end of file
diff --git a/public/language/fr/topic.json b/public/language/fr/topic.json
index dd878a751f..cbe1843ca5 100644
--- a/public/language/fr/topic.json
+++ b/public/language/fr/topic.json
@@ -11,6 +11,7 @@
     "reply": "Répondre",
     "edit": "Editer",
     "delete": "Supprimer",
+    "restore": "Restore",
     "move": "Déplacer",
     "fork": "Scinder",
     "banned": "bannis",
@@ -18,8 +19,15 @@
     "share": "Partager",
     "tools": "Outils",
     "flag": "Signaler",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Signaler ce post pour modération",
     "deleted_message": "Ce sujet a été supprimé. Seuls les utilsateurs avec les droits d'administration peuvent le voir.",
+    "following_topic.title": "Sujet suivi",
+    "following_topic.message": "Vous recevrez désormais des notifications lorsque quelqu'un postera dans ce sujet.",
+    "not_following_topic.title": "Sujet non suivi",
+    "not_following_topic.message": "Vous ne recevrez plus de notifications pour ce sujet.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Suivre",
     "share_this_post": "Partager ce message",
     "thread_tools.title": "Outils du Fil",
@@ -58,8 +66,17 @@
     "composer.title_placeholder": "Entrer le titre du sujet ici...",
     "composer.write": "Ecriture",
     "composer.preview": "Aperçu",
+    "composer.help": "Help",
     "composer.discard": "Abandon",
     "composer.submit": "Envoi",
     "composer.replying_to": "Répondre à",
-    "composer.new_topic": "Nouveau Sujet"
+    "composer.new_topic": "Nouveau Sujet",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
+    "composer.drag_and_drop_images": "Glisser-déposer ici les images",
+    "composer.upload_instructions": "Uploader des images par glisser-déposer."
 }
\ No newline at end of file
diff --git a/public/language/he/category.json b/public/language/he/category.json
index cf2ecb0050..34253c2ca2 100644
--- a/public/language/he/category.json
+++ b/public/language/he/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "נושא חדש",
     "no_topics": "<strong>קטגוריה זו ריקה מנושאים.</strong><br />למה שלא תנסה להוסיף נושא חדש?",
-    "sidebar.recent_replies": "תגובות אחרונות",
-    "sidebar.active_participants": "משתתפים פעילים",
-    "sidebar.moderators": "מנהלי הפורום",
     "posts": "פוסטים",
     "views": "צפיות",
     "posted": "פורסם",
diff --git a/public/language/he/global.json b/public/language/he/global.json
index dabcda50c6..0719d1408b 100644
--- a/public/language/he/global.json
+++ b/public/language/he/global.json
@@ -10,6 +10,8 @@
     "500.message": "אופס! נראה שמשהו השתבש!",
     "register": "הרשמה",
     "login": "התחברות",
+    "please_log_in": "Please Log In",
+    "posting_restriction_info": "Posting is currently restricted to registered members only, click here to log in.",
     "welcome_back": "ברוכים השבים",
     "you_have_successfully_logged_in": "התחברת בהצלחה",
     "logout": "יציאה",
@@ -47,6 +49,7 @@
     "posted": "פורסם",
     "in": "ב",
     "recentposts": "פוסטים אחרונים",
+    "recentips": "Recently Logged In IPs",
     "online": "מחובר",
     "away": "לא נמצא",
     "dnd": "לא להפריע",
diff --git a/public/language/he/pages.json b/public/language/he/pages.json
index 0ff571585f..44b12272f4 100644
--- a/public/language/he/pages.json
+++ b/public/language/he/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "עורך את %1",
     "user.following": "אנשים ש%1 עוקב אחריהם",
     "user.followers": "אנשים שעוקבים אחרי %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "הפוסטים המועדפים על %1",
     "user.settings": "הגדרות משתמש"
 }
\ No newline at end of file
diff --git a/public/language/he/topic.json b/public/language/he/topic.json
index 4e10c34c29..866570224f 100644
--- a/public/language/he/topic.json
+++ b/public/language/he/topic.json
@@ -11,6 +11,7 @@
     "reply": "תגובה",
     "edit": "עריכה",
     "delete": "מחק",
+    "restore": "Restore",
     "move": "הזז",
     "fork": "פורק",
     "banned": "מורחק",
@@ -18,8 +19,15 @@
     "share": "Share",
     "tools": "כלים",
     "flag": "דווח",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "דווח על פוסט זה למנהל",
     "deleted_message": "הנושא הזה נמחק. רק מנהלים מורשים לראות אותו",
+    "following_topic.title": "Following Topic",
+    "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
+    "not_following_topic.title": "Not Following Topic",
+    "not_following_topic.message": "You will no longer receive notifications from this topic.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "עקוב",
     "share_this_post": "שתף פוסט זה",
     "thread_tools.title": "כלים",
@@ -58,8 +66,17 @@
     "composer.title_placeholder": "הכנס את כותרת הנושא כאן...",
     "composer.write": "כתוב",
     "composer.preview": "תצוגה מקדימה",
+    "composer.help": "Help",
     "composer.discard": "מחק",
     "composer.submit": "שלח",
     "composer.replying_to": "תגובה",
-    "composer.new_topic": "נושא חדש"
+    "composer.new_topic": "נושא חדש",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
+    "composer.drag_and_drop_images": "Drag and Drop Images Here",
+    "composer.upload_instructions": "Upload images by dragging & dropping them."
 }
\ No newline at end of file
diff --git a/public/language/hu/category.json b/public/language/hu/category.json
index b47c4d3f62..0026d7f8e7 100644
--- a/public/language/hu/category.json
+++ b/public/language/hu/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Új Topik",
     "no_topics": "<strong>Még nincs nyitva egy téma sem ebben a kategóriában.</strong>Miért nem hozol létre egyet?",
-    "sidebar.recent_replies": "Friss Válaszok",
-    "sidebar.active_participants": "Aktív Résztvevők",
-    "sidebar.moderators": "Moderátorok",
     "posts": "hozzászólások",
     "views": "megtekintések",
     "posted": "hozzászólt",
diff --git a/public/language/hu/global.json b/public/language/hu/global.json
index 8996720bfa..579ae7fd48 100644
--- a/public/language/hu/global.json
+++ b/public/language/hu/global.json
@@ -10,6 +10,8 @@
     "500.message": "Hoppá! Úgy tűnik valami hiba történt!",
     "register": "Regisztrálás",
     "login": "Belépés",
+    "please_log_in": "Please Log In",
+    "posting_restriction_info": "Posting is currently restricted to registered members only, click here to log in.",
     "welcome_back": "Welcome Back ",
     "you_have_successfully_logged_in": "You have successfully logged in",
     "logout": "Kijelentkezés",
@@ -47,6 +49,7 @@
     "posted": "hozzászólt",
     "in": "itt:",
     "recentposts": "Friss hozzászólások",
+    "recentips": "Recently Logged In IPs",
     "online": "Online",
     "away": "Távol van",
     "dnd": "Elfoglalt",
diff --git a/public/language/hu/pages.json b/public/language/hu/pages.json
index 00a3c97c1a..1ed955722c 100644
--- a/public/language/hu/pages.json
+++ b/public/language/hu/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "Szerkesztés \"%1\"",
     "user.following": "Tagok akiket %1 követ",
     "user.followers": "Tagok akik követik %1 -t",
+    "user.posts": "Posts made by %1",
     "user.favourites": "%1 Kedvenc Hozzászólásai",
     "user.settings": "Felhasználói Beállítások"
 }
\ No newline at end of file
diff --git a/public/language/hu/topic.json b/public/language/hu/topic.json
index 0e64a57877..4523c44c01 100644
--- a/public/language/hu/topic.json
+++ b/public/language/hu/topic.json
@@ -11,6 +11,7 @@
     "reply": "Válasz",
     "edit": "Szerkeszt",
     "delete": "Töröl",
+    "restore": "Restore",
     "move": "Áthelyez",
     "fork": "Szétszedés",
     "banned": "tiltva",
@@ -18,13 +19,15 @@
     "share": "Megosztás",
     "tools": "Eszközök",
     "flag": "Jelentés",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "A hozzászólás jelentése a moderátoroknál",
     "deleted_message": "Ez a topik törölve lett. Kizárólag azok a felhasználók láthatják, akiknek joga van hozzá.",
     "following_topic.title": "Following Topic",
     "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
     "not_following_topic.title": "Not Following Topic",
     "not_following_topic.message": "You will no longer receive notifications from this topic.",
-    "login_to_subscribe": "Please register or log in in order to subscribe to this topic",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Watch",
     "share_this_post": "Share this Post",
     "thread_tools.title": "Téma Eszközök",
@@ -63,11 +66,17 @@
     "composer.title_placeholder": "Írd be a témanevet...",
     "composer.write": "Ír",
     "composer.preview": "Előnézet",
+    "composer.help": "Help",
     "composer.discard": "Elvet",
     "composer.submit": "Küldés",
     "composer.replying_to": "Válasz erre:",
     "composer.new_topic": "Új Topik",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
     "composer.drag_and_drop_images": "Drag and Drop Images Here",
-    "composer.content_is_parsed_with": "Content is parsed with",
     "composer.upload_instructions": "Upload images by dragging & dropping them."
 }
\ No newline at end of file
diff --git a/public/language/it/category.json b/public/language/it/category.json
index 9712a85a6c..4957d06d56 100644
--- a/public/language/it/category.json
+++ b/public/language/it/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Nuovo Argomento",
     "no_topics": "<strong>Non ci sono discussioni in questa categoria.</strong><br />Perché non ne inizi una?",
-    "sidebar.recent_replies": "Risposte Recenti",
-    "sidebar.active_participants": "Partecipanti Attivi",
-    "sidebar.moderators": "Moderatori",
     "posts": "post",
     "views": "visualizzazioni",
     "posted": "postato",
diff --git a/public/language/it/global.json b/public/language/it/global.json
index b9c1700a5a..e8223af7e5 100644
--- a/public/language/it/global.json
+++ b/public/language/it/global.json
@@ -49,6 +49,7 @@
     "posted": "postato",
     "in": "in",
     "recentposts": "Post Recenti",
+    "recentips": "Recently Logged In IPs",
     "online": "Online",
     "away": "Non disponibile",
     "dnd": "Non disturbare",
diff --git a/public/language/it/pages.json b/public/language/it/pages.json
index 80f9ef9e72..559d80b9d2 100644
--- a/public/language/it/pages.json
+++ b/public/language/it/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "Modificando \"%1\"",
     "user.following": "%1 Persone seguono",
     "user.followers": "Persone che seguono %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "Post Favoriti di %1",
     "user.settings": "Impostazioni Utente"
 }
\ No newline at end of file
diff --git a/public/language/it/topic.json b/public/language/it/topic.json
index e8e44d9f03..5aced3efe3 100644
--- a/public/language/it/topic.json
+++ b/public/language/it/topic.json
@@ -11,6 +11,7 @@
     "reply": "Rispondi",
     "edit": "Modifica",
     "delete": "Cancella",
+    "restore": "Restore",
     "move": "Muovi",
     "fork": "Dividi",
     "banned": "bannato",
@@ -18,14 +19,16 @@
     "share": "Condividi",
     "tools": "Strumenti",
     "flag": "Segnala",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Segnala questo post per la moderazione",
     "deleted_message": "Questo argomento è stato cancellato. Solo gli utenti che possono gestire gli argomenti riescono a vederlo.",
-    "following_topic.title": "Argomento seguente",
+    "following_topic.title": "Stai seguendo questa Discussione",
     "following_topic.message": "Da ora riceverai notifiche quando qualcuno posterà in questa discussione.",
-    "not_following_topic.title": "Non stai seguendo questo argomento",
+    "not_following_topic.title": "Non stai seguendo questa Discussione",
     "not_following_topic.message": "Non riceverai più notifiche da questa discussione.",
-    "login_to_subscribe": "Per favore registrati o accedi per sottoscrivere questo argomento",
-    "watch": "Guarda",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
+    "watch": "Osserva",
     "share_this_post": "Condividi questo Post",
     "thread_tools.title": "Strumenti per il Thread",
     "thread_tools.markAsUnreadForAll": "Segna come non letto",
@@ -63,11 +66,17 @@
     "composer.title_placeholder": "Inserisci qui il titolo della discussione...",
     "composer.write": "Scrivi",
     "composer.preview": "Anteprima",
-    "composer.discard": "Scarta",
+    "composer.help": "Help",
+    "composer.discard": "Annulla",
     "composer.submit": "Invia",
     "composer.replying_to": "Rispondendo a",
     "composer.new_topic": "Nuovo Argomento",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
     "composer.drag_and_drop_images": "Trascina e rilascia le immagini qui",
-    "composer.content_is_parsed_with": "Il contenuto è analizzato con",
     "composer.upload_instructions": "Carica immagini trascinandole e rilasciandole."
 }
\ No newline at end of file
diff --git a/public/language/nb/category.json b/public/language/nb/category.json
index dffe17e88b..e9faf5ec0e 100644
--- a/public/language/nb/category.json
+++ b/public/language/nb/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Nytt emne",
     "no_topics": "<strong>Det er ingen emner i denne kategorien</strong><br />Hvorfor ikke lage ett?",
-    "sidebar.recent_replies": "Seneste svar",
-    "sidebar.active_participants": "Aktive deltakere",
-    "sidebar.moderators": "Moderatorer",
     "posts": "inlegg",
     "views": "visninger",
     "posted": "skapte",
diff --git a/public/language/nb/global.json b/public/language/nb/global.json
index 0c07cd9c51..d1773e9388 100644
--- a/public/language/nb/global.json
+++ b/public/language/nb/global.json
@@ -10,6 +10,8 @@
     "500.message": "Oops! Ser ut som noe gikk galt!",
     "register": "Registrer",
     "login": "Logg inn",
+    "please_log_in": "Vennligst logg inn",
+    "posting_restriction_info": "Posting er foreløpig begrenset til registrerte medlemmer, klikk her for å logge inn.",
     "welcome_back": "Velkommen tilbake",
     "you_have_successfully_logged_in": "Du har blitt logget inn",
     "logout": "Logg ut",
@@ -47,6 +49,7 @@
     "posted": "skapt",
     "in": "i",
     "recentposts": "Seneste innlegg",
+    "recentips": "Recently Logged In IPs",
     "online": "Online",
     "away": "Borte",
     "dnd": "Ikke forsturr",
diff --git a/public/language/nb/pages.json b/public/language/nb/pages.json
index 60940216d0..1d7a489f5e 100644
--- a/public/language/nb/pages.json
+++ b/public/language/nb/pages.json
@@ -1,13 +1,14 @@
 {
     "home": "Hjem",
     "unread": "Uleste emner",
-    "popular": "Popular Topics",
+    "popular": "Populære tråder",
     "recent": "Seneste emner",
     "users": "Registrerte brukere",
     "notifications": "Varsler",
     "user.edit": "Endrer \"%1\"",
     "user.following": "Personer %1 følger",
     "user.followers": "Personer som følger %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "%1 sine favoritt-innlegg",
     "user.settings": "Brukerinnstillinger"
 }
\ No newline at end of file
diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json
index aab44d8835..320f0b5002 100644
--- a/public/language/nb/topic.json
+++ b/public/language/nb/topic.json
@@ -11,6 +11,7 @@
     "reply": "Svar",
     "edit": "Endre",
     "delete": "Slett",
+    "restore": "Restore",
     "move": "Flytt",
     "fork": "Del",
     "banned": "utestengt",
@@ -18,8 +19,15 @@
     "share": "Del",
     "tools": "Verktøy",
     "flag": "Rapporter",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Rapporter dette innlegget for granskning",
     "deleted_message": "Denne tråden har blitt slettet. Bare brukere med trådhåndterings-privilegier kan se den.",
+    "following_topic.title": "Følger tråd",
+    "following_topic.message": "Du vil nå motta varsler når noen skriver i denne tråden.",
+    "not_following_topic.title": "Følger ikke tråd",
+    "not_following_topic.message": "Du vil ikke lenger motta varsler fra denne tråden.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Overvåk",
     "share_this_post": "Del ditt innlegg",
     "thread_tools.title": "Trådverktøy",
@@ -58,8 +66,17 @@
     "composer.title_placeholder": "Skriv din tråd-tittel her",
     "composer.write": "Skriv",
     "composer.preview": "Forhåndsvis",
+    "composer.help": "Help",
     "composer.discard": "Forkast",
     "composer.submit": "Send",
     "composer.replying_to": "Svarer til",
-    "composer.new_topic": "Ny tråd"
+    "composer.new_topic": "Ny tråd",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
+    "composer.drag_and_drop_images": "Dra og slipp bilder her",
+    "composer.upload_instructions": "Last opp bilder ved å dra og slippe dem."
 }
\ No newline at end of file
diff --git a/public/language/nl/category.json b/public/language/nl/category.json
index af6ba62510..08cb947178 100644
--- a/public/language/nl/category.json
+++ b/public/language/nl/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Nieuw onderwerp",
     "no_topics": "<strong>Er zijn geen onderwerpen in deze categorie.</strong><br />Waarom maak je er niet een aan?",
-    "sidebar.recent_replies": "Recente Reacties",
-    "sidebar.active_participants": "Actieve Deelnemers",
-    "sidebar.moderators": "Moderators",
     "posts": "berichten",
     "views": "weergaven",
     "posted": "geplaatst",
diff --git a/public/language/nl/global.json b/public/language/nl/global.json
index d9a3048fd6..cf6a683825 100644
--- a/public/language/nl/global.json
+++ b/public/language/nl/global.json
@@ -49,6 +49,7 @@
     "posted": "geplaatst",
     "in": "in",
     "recentposts": "Recente Berichten",
+    "recentips": "Recently Logged In IPs",
     "online": "Online",
     "away": "Afwezig",
     "dnd": "Niet Storen",
diff --git a/public/language/nl/pages.json b/public/language/nl/pages.json
index 07a6963b89..15deb0556f 100644
--- a/public/language/nl/pages.json
+++ b/public/language/nl/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "\"%1\" aanpassen",
     "user.following": "Mensen %1 Volgt",
     "user.followers": "Mensen die %1 Volgen",
+    "user.posts": "Posts made by %1",
     "user.favourites": "%1's Favoriete Berichten",
     "user.settings": "Gebruikersinstellingen"
 }
\ No newline at end of file
diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json
index a0d1dd9ffb..3331b7fcf1 100644
--- a/public/language/nl/topic.json
+++ b/public/language/nl/topic.json
@@ -11,6 +11,7 @@
     "reply": "Reageren",
     "edit": "Aanpassen",
     "delete": "Verwijderen",
+    "restore": "Restore",
     "move": "Verplaatsen",
     "fork": "Fork",
     "banned": "verbannen",
@@ -18,13 +19,15 @@
     "share": "Delen",
     "tools": "Gereedschap",
     "flag": "Markeren",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Dit bericht markeren voor moderatie",
     "deleted_message": "Dit onderwerp is verwijderd. Alleen gebruikers met onderwerp management privileges kunnen dit onderwerp zien.",
     "following_topic.title": "Following Topic",
     "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
     "not_following_topic.title": "Not Following Topic",
     "not_following_topic.message": "You will no longer receive notifications from this topic.",
-    "login_to_subscribe": "Please register or log in in order to subscribe to this topic",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Watch",
     "share_this_post": "Share this Post",
     "thread_tools.title": "Thread Gereedschap",
@@ -63,11 +66,17 @@
     "composer.title_placeholder": "Vul de titel voor het onderwerp hier in...",
     "composer.write": "Schrijven",
     "composer.preview": "Voorbeeld",
+    "composer.help": "Help",
     "composer.discard": "Annuleren",
     "composer.submit": "Opslaan",
     "composer.replying_to": "Reageren op",
     "composer.new_topic": "Nieuw Onderwerp",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
     "composer.drag_and_drop_images": "Drag and Drop Images Here",
-    "composer.content_is_parsed_with": "Content is parsed with",
     "composer.upload_instructions": "Upload images by dragging & dropping them."
 }
\ No newline at end of file
diff --git a/public/language/pl/category.json b/public/language/pl/category.json
index fec821a94f..c916677ef8 100644
--- a/public/language/pl/category.json
+++ b/public/language/pl/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Nowy wątek",
     "no_topics": "<strong>W tej kategorii nie ma jeszcze żadnych wątków.</strong><br />Dlaczego ty nie utworzysz jakiegoś?",
-    "sidebar.recent_replies": "Ostatnie odpowiedzi",
-    "sidebar.active_participants": "Aktywni uczestnicy",
-    "sidebar.moderators": "Moderatorzy",
     "posts": "postów",
     "views": "wyświetleń",
     "posted": "napisano",
diff --git a/public/language/pl/global.json b/public/language/pl/global.json
index 9b979e6373..b543fc38ff 100644
--- a/public/language/pl/global.json
+++ b/public/language/pl/global.json
@@ -49,6 +49,7 @@
     "posted": "napisano",
     "in": "w",
     "recentposts": "Ostatnie posty",
+    "recentips": "Recently Logged In IPs",
     "online": "Online",
     "away": "Z dala",
     "dnd": "Nie przeszkadzać",
diff --git a/public/language/pl/pages.json b/public/language/pl/pages.json
index c91a2daff9..483fb2d6ec 100644
--- a/public/language/pl/pages.json
+++ b/public/language/pl/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "Edytowanie \"%1\"",
     "user.following": "Obserwowani przez %1",
     "user.followers": "Obserwujący %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "Ulubione posty %1",
     "user.settings": "Ustawienia użytkownika"
 }
\ No newline at end of file
diff --git a/public/language/pl/topic.json b/public/language/pl/topic.json
index 8127f73075..c05f78147f 100644
--- a/public/language/pl/topic.json
+++ b/public/language/pl/topic.json
@@ -11,6 +11,7 @@
     "reply": "Odpowiedz",
     "edit": "Edytuj",
     "delete": "Usuń",
+    "restore": "Restore",
     "move": "Przenieś",
     "fork": "Skopiuj",
     "banned": "zbanowany",
@@ -18,13 +19,15 @@
     "share": "Udostępnij",
     "tools": "Narzędzia",
     "flag": "Zgłoś",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Zgłoś post do moderacji",
     "deleted_message": "Ten wątek został usunięty. Tylko użytkownicy z uprawnieniami do zarządzania wątkami mogą go widzieć.",
     "following_topic.title": "Obserwujesz wątek",
     "following_topic.message": "Będziesz otrzymywał powiadomienia, gdy ktoś odpowie w tym wątku.",
     "not_following_topic.title": "Nie obserwujesz wątku",
     "not_following_topic.message": "Nie będziesz otrzymywał więcej powiadomień z tego wątku.",
-    "login_to_subscribe": "Zaloguj się, aby subskrybować ten wątek.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Obserwuj",
     "share_this_post": "Udostępnij",
     "thread_tools.title": "Narzędzia wątków",
@@ -63,11 +66,17 @@
     "composer.title_placeholder": "Wpisz tytuł wątku tutaj",
     "composer.write": "Pisz",
     "composer.preview": "Podgląd",
+    "composer.help": "Help",
     "composer.discard": "Odrzuć",
     "composer.submit": "Wyślij",
     "composer.replying_to": "Odpowiadasz",
     "composer.new_topic": "Nowy wątek",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
     "composer.drag_and_drop_images": "Przeciągnij i upuść obrazek tutaj.",
-    "composer.content_is_parsed_with": "Tekst jest parsowany przy pomocy",
     "composer.upload_instructions": "Prześlij obrazki przeciągając i upuszczając je."
 }
\ No newline at end of file
diff --git a/public/language/pt_BR/category.json b/public/language/pt_BR/category.json
index e0f5933e5c..7b09b35907 100644
--- a/public/language/pt_BR/category.json
+++ b/public/language/pt_BR/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Novo Tópico",
     "no_topics": "<strong>Não tem nenhum tópico nesta categoria.</strong><br />Por que não tenta postar um?",
-    "sidebar.recent_replies": "Respostas Recentes",
-    "sidebar.active_participants": "Participantes Ativos",
-    "sidebar.moderators": "Moderadores",
     "posts": "posts",
     "views": "visualizações",
     "posted": "postado",
diff --git a/public/language/pt_BR/global.json b/public/language/pt_BR/global.json
index efb6b78f23..d0eec0d266 100644
--- a/public/language/pt_BR/global.json
+++ b/public/language/pt_BR/global.json
@@ -49,6 +49,7 @@
     "posted": "Postado",
     "in": "em",
     "recentposts": "Posts Recentes",
+    "recentips": "Recently Logged In IPs",
     "online": "Online",
     "away": "Ausente",
     "dnd": "Não Perturbe",
diff --git a/public/language/pt_BR/pages.json b/public/language/pt_BR/pages.json
index 313eee0d2c..6319f5b5d4 100644
--- a/public/language/pt_BR/pages.json
+++ b/public/language/pt_BR/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "Editando \"%1\"",
     "user.following": "Pessoas %1 Seguindo",
     "user.followers": "Pessoas que seguem %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "%1's Posts Favoritos",
     "user.settings": "Configurações de Usuário"
 }
\ No newline at end of file
diff --git a/public/language/pt_BR/topic.json b/public/language/pt_BR/topic.json
index 0e01e879f4..4871bf5ca9 100644
--- a/public/language/pt_BR/topic.json
+++ b/public/language/pt_BR/topic.json
@@ -11,6 +11,7 @@
     "reply": "Responder",
     "edit": "Editar",
     "delete": "Deletar",
+    "restore": "Restore",
     "move": "Mover",
     "fork": "Fork",
     "banned": "Banido",
@@ -18,13 +19,15 @@
     "share": "Compartilhar",
     "tools": "Ferramentas",
     "flag": "Marcar",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Marcar este post para moderação",
     "deleted_message": "Esta Thread foi deletada. Somente usuários com privilégios administrativos podem ver.",
     "following_topic.title": "Seguir Tópico",
     "following_topic.message": "Você receberá notificações quando alguém responder este tópico.",
     "not_following_topic.title": "Não está seguindo Tópico",
     "not_following_topic.message": "Você não irá mais receber notificações deste tópico.",
-    "login_to_subscribe": "Por favor registre ou logue para poder assinar este tópico",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Acompanhar",
     "share_this_post": "Compartilhar este Post",
     "thread_tools.title": "Ferramentas da Thread",
@@ -63,11 +66,17 @@
     "composer.title_placeholder": "Digite seu tópico aqui...",
     "composer.write": "Escreva",
     "composer.preview": "Pré Visualização",
+    "composer.help": "Help",
     "composer.discard": "Descartar",
     "composer.submit": "Enviar",
     "composer.replying_to": "Respondendo para",
     "composer.new_topic": "Novo Tópico",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
     "composer.drag_and_drop_images": "Clique e arraste imagens aqui",
-    "composer.content_is_parsed_with": "Conteúdo está separado com",
     "composer.upload_instructions": "Mande suas imagens arrastando e soltando."
 }
\ No newline at end of file
diff --git a/public/language/ru/category.json b/public/language/ru/category.json
index 875db86e42..f29163f180 100644
--- a/public/language/ru/category.json
+++ b/public/language/ru/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Создать тему",
     "no_topics": "<strong>В этой категории еще нет тем.</strong><br />Почему бы вам не создать первую?",
-    "sidebar.recent_replies": "Последние сообщения",
-    "sidebar.active_participants": "Активные участники",
-    "sidebar.moderators": "Модераторы",
     "posts": "сообщений",
     "views": "просмотров",
     "posted": "написано",
diff --git a/public/language/ru/global.json b/public/language/ru/global.json
index 518d4ba74b..a87efeb669 100644
--- a/public/language/ru/global.json
+++ b/public/language/ru/global.json
@@ -10,6 +10,8 @@
     "500.message": "Упс! Похоже, что-то пошло не так!",
     "register": "Зарегистрироваться",
     "login": "Войти",
+    "please_log_in": "Please Log In",
+    "posting_restriction_info": "Posting is currently restricted to registered members only, click here to log in.",
     "welcome_back": "Welcome Back ",
     "you_have_successfully_logged_in": "You have successfully logged in",
     "logout": "Выйти",
@@ -47,6 +49,7 @@
     "posted": "создан",
     "in": "в",
     "recentposts": "Свежие записи",
+    "recentips": "Recently Logged In IPs",
     "online": "В сети",
     "away": "Отсутствует",
     "dnd": "Не беспокоить",
diff --git a/public/language/ru/pages.json b/public/language/ru/pages.json
index 0d5f5b9ca9..48a0755e63 100644
--- a/public/language/ru/pages.json
+++ b/public/language/ru/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "Редактирование \"%1\"",
     "user.following": "%1 читает",
     "user.followers": "Читают %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "Избранные сообщения %1",
     "user.settings": "Настройки"
 }
\ No newline at end of file
diff --git a/public/language/ru/topic.json b/public/language/ru/topic.json
index 2ab3aadc57..210319b57b 100644
--- a/public/language/ru/topic.json
+++ b/public/language/ru/topic.json
@@ -11,6 +11,7 @@
     "reply": "Ответить",
     "edit": "Редактировать",
     "delete": "Удалить",
+    "restore": "Restore",
     "move": "Перенести",
     "fork": "Ответвление",
     "banned": "заблокировано",
@@ -18,8 +19,15 @@
     "share": "Поделиться",
     "tools": "Опции",
     "flag": "Отметить",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Отметить сообщение для модерирования",
     "deleted_message": "This thread has been deleted. Only users with thread management privileges can see it.",
+    "following_topic.title": "Following Topic",
+    "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
+    "not_following_topic.title": "Not Following Topic",
+    "not_following_topic.message": "You will no longer receive notifications from this topic.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Watch",
     "share_this_post": "Share this Post",
     "thread_tools.title": "Опции Темы",
@@ -58,8 +66,17 @@
     "composer.title_placeholder": "Enter your topic title here...",
     "composer.write": "Write",
     "composer.preview": "Preview",
+    "composer.help": "Help",
     "composer.discard": "Discard",
     "composer.submit": "Submit",
     "composer.replying_to": "Replying to",
-    "composer.new_topic": "New Topic"
+    "composer.new_topic": "New Topic",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
+    "composer.drag_and_drop_images": "Drag and Drop Images Here",
+    "composer.upload_instructions": "Upload images by dragging & dropping them."
 }
\ No newline at end of file
diff --git a/public/language/sk/category.json b/public/language/sk/category.json
index d689f1ccf8..134024158d 100644
--- a/public/language/sk/category.json
+++ b/public/language/sk/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Nová téma",
     "no_topics": "<strong>V tejto kategórií zatiaľ nie sú žiadne príspevky.</strong><br />Môžeš byť prvý!",
-    "sidebar.recent_replies": "Posledné príspevky",
-    "sidebar.active_participants": "Aktívni účastníci",
-    "sidebar.moderators": "Moderátori",
     "posts": "príspevkov",
     "views": "zobrazení",
     "posted": "odoslané",
diff --git a/public/language/sk/global.json b/public/language/sk/global.json
index cf17eb234d..1427672f70 100644
--- a/public/language/sk/global.json
+++ b/public/language/sk/global.json
@@ -49,6 +49,7 @@
     "posted": "príspevok",
     "in": "v",
     "recentposts": "Posledné príspevky",
+    "recentips": "Recently Logged In IPs",
     "online": "Online",
     "away": "Preč",
     "dnd": "Nevyrušovať",
diff --git a/public/language/sk/pages.json b/public/language/sk/pages.json
index 4fa2659252..22de254a9c 100644
--- a/public/language/sk/pages.json
+++ b/public/language/sk/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "Editing \"%1\"",
     "user.following": "People %1 Follows",
     "user.followers": "People who Follow %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "%1's Favourite Posts",
     "user.settings": "User Settings"
 }
\ No newline at end of file
diff --git a/public/language/sk/topic.json b/public/language/sk/topic.json
index b7a2bac591..c1d4b89bf6 100644
--- a/public/language/sk/topic.json
+++ b/public/language/sk/topic.json
@@ -11,6 +11,7 @@
     "reply": "Odpovedať",
     "edit": "Upraviť",
     "delete": "Zmazať",
+    "restore": "Restore",
     "move": "Presunúť",
     "fork": "Rozdeliť",
     "banned": "Zakázaný",
@@ -18,13 +19,15 @@
     "share": "Zdieľaj",
     "tools": "Nástroje",
     "flag": "Označiť",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Označiť príspevok pre moderáciu",
     "deleted_message": "Toto vlákno bolo vymazané. Iba užívatelia s privilégiami ho môžu vidieť.",
     "following_topic.title": "Sledovať Tému",
     "following_topic.message": "Budete teraz príjimať notifikácie, ked niekto prispeje do témy.",
     "not_following_topic.title": "Nesledujete Tému",
     "not_following_topic.message": "Nebudete už dostávať notifikácie z tejto Témy",
-    "login_to_subscribe": "Prosím Zaregistrujte sa alebo sa Prihláste, aby ste mohli odoberať túto Tému",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Sledovať",
     "share_this_post": "Zdielaj tento príspevok",
     "thread_tools.title": "Nástroje",
@@ -63,11 +66,17 @@
     "composer.title_placeholder": "Vlož nadpis témy sem...",
     "composer.write": "Píš",
     "composer.preview": "Náhľad",
+    "composer.help": "Help",
     "composer.discard": "Zahodiť",
     "composer.submit": "Poslať",
     "composer.replying_to": "Odpovedáš ",
     "composer.new_topic": "Nová téma",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
     "composer.drag_and_drop_images": "Pretiahni a Pusť Obrázky Sem",
-    "composer.content_is_parsed_with": "Obsah je vybraný s",
     "composer.upload_instructions": "Nahraj obrázky pretiahnutím a pustením ich."
 }
\ No newline at end of file
diff --git a/public/language/sv/category.json b/public/language/sv/category.json
index d982039058..629829d720 100644
--- a/public/language/sv/category.json
+++ b/public/language/sv/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Nytt ämne",
     "no_topics": "<strong>Det finns inga ämnen i denna kategori.</strong><br />Varför inte skapa ett?",
-    "sidebar.recent_replies": "Senaste svaren",
-    "sidebar.active_participants": "Aktiva deltagare",
-    "sidebar.moderators": "Moderatorer",
     "posts": "inlägg",
     "views": "tittningar",
     "posted": "skapad",
diff --git a/public/language/sv/global.json b/public/language/sv/global.json
index 83515c5b57..5896cf7d5c 100644
--- a/public/language/sv/global.json
+++ b/public/language/sv/global.json
@@ -10,6 +10,8 @@
     "500.message": "Hoppsan! Verkar som att något gått snett!",
     "register": "Registrera",
     "login": "Logga in",
+    "please_log_in": "Please Log In",
+    "posting_restriction_info": "Posting is currently restricted to registered members only, click here to log in.",
     "welcome_back": "Welcome Back ",
     "you_have_successfully_logged_in": "You have successfully logged in",
     "logout": "Logga ut",
@@ -47,6 +49,7 @@
     "posted": "svarade",
     "in": "i",
     "recentposts": "Senaste ämnena",
+    "recentips": "Recently Logged In IPs",
     "online": "Online",
     "away": "Borta",
     "dnd": "Stör ej",
diff --git a/public/language/sv/pages.json b/public/language/sv/pages.json
index dd938e334f..39899ade4d 100644
--- a/public/language/sv/pages.json
+++ b/public/language/sv/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "Ändrar \"%1\"",
     "user.following": "Personer %1 Följer",
     "user.followers": "Personer som följer %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "%1's favorit-inlägg",
     "user.settings": "Avnändarinställningar"
 }
\ No newline at end of file
diff --git a/public/language/sv/topic.json b/public/language/sv/topic.json
index 771ed23e96..67e81513be 100644
--- a/public/language/sv/topic.json
+++ b/public/language/sv/topic.json
@@ -11,6 +11,7 @@
     "reply": "Svara",
     "edit": "Ändra",
     "delete": "Ta bort",
+    "restore": "Restore",
     "move": "Flytta",
     "fork": "Grena",
     "banned": "bannad",
@@ -18,8 +19,15 @@
     "share": "Dela",
     "tools": "Verktyg",
     "flag": "Rapportera",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Rapportera detta inlägg för granskning",
     "deleted_message": "Denna tråd har tagits bort. Endast användare med administrations-rättigheter kan se den.",
+    "following_topic.title": "Following Topic",
+    "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
+    "not_following_topic.title": "Not Following Topic",
+    "not_following_topic.message": "You will no longer receive notifications from this topic.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Watch",
     "share_this_post": "Share this Post",
     "thread_tools.title": "Trådverktyg",
@@ -58,8 +66,17 @@
     "composer.title_placeholder": "Skriv in ämnets titel här...",
     "composer.write": "Skriv",
     "composer.preview": "Förhandsgranska",
+    "composer.help": "Help",
     "composer.discard": "Avbryt",
     "composer.submit": "Spara",
     "composer.replying_to": "Svarar till",
-    "composer.new_topic": "Nytt ämne"
+    "composer.new_topic": "Nytt ämne",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
+    "composer.drag_and_drop_images": "Drag and Drop Images Here",
+    "composer.upload_instructions": "Upload images by dragging & dropping them."
 }
\ No newline at end of file
diff --git a/public/language/tr/category.json b/public/language/tr/category.json
index fd41b09848..5eab2af8a4 100644
--- a/public/language/tr/category.json
+++ b/public/language/tr/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "Yeni Başlık",
     "no_topics": "<strong> Bu kategoride hiç konu yok. </strong> <br /> Yeni bir konu açmak istemez misiniz?",
-    "sidebar.recent_replies": "Güncel Cevaplar",
-    "sidebar.active_participants": "Aktif Katılımcılar",
-    "sidebar.moderators": "Moderatörler",
     "posts": "ileti",
     "views": "görüntülemeler",
     "posted": "yayımlandı",
diff --git a/public/language/tr/global.json b/public/language/tr/global.json
index b04553aaa4..492c5f6e89 100644
--- a/public/language/tr/global.json
+++ b/public/language/tr/global.json
@@ -10,6 +10,8 @@
     "500.message": "Ups! Bir şeyler ters gitti sanki!",
     "register": "Kayıt Ol",
     "login": "Giriş",
+    "please_log_in": "Please Log In",
+    "posting_restriction_info": "Posting is currently restricted to registered members only, click here to log in.",
     "welcome_back": "Welcome Back ",
     "you_have_successfully_logged_in": "You have successfully logged in",
     "logout": "Çıkış",
@@ -47,6 +49,7 @@
     "posted": "gönderildi",
     "in": "içinde",
     "recentposts": "Güncel İletiler",
+    "recentips": "Recently Logged In IPs",
     "online": "Çevrimiçi",
     "away": "Dışarıda",
     "dnd": "Rahatsız Etmeyin",
diff --git a/public/language/tr/pages.json b/public/language/tr/pages.json
index 55f625162f..a2b94f72ef 100644
--- a/public/language/tr/pages.json
+++ b/public/language/tr/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "\"% 1\" düzenleniyor",
     "user.following": "İnsanlar %1 Takip Ediyor",
     "user.followers": "%1 takip edenler",
+    "user.posts": "Posts made by %1",
     "user.favourites": "%1'in Favori İletileri",
     "user.settings": "Kullanıcı Ayarları"
 }
\ No newline at end of file
diff --git a/public/language/tr/topic.json b/public/language/tr/topic.json
index b587f45646..23288d91c0 100644
--- a/public/language/tr/topic.json
+++ b/public/language/tr/topic.json
@@ -11,6 +11,7 @@
     "reply": "Cevap",
     "edit": "Düzenle",
     "delete": "Sil",
+    "restore": "Restore",
     "move": "Taşı",
     "fork": "Fork",
     "banned": "yasaklı",
@@ -18,8 +19,15 @@
     "share": "Paylaş",
     "tools": "Araçlar",
     "flag": "Bayrak",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Bu iletiyi moderatöre haber et",
     "deleted_message": "This thread has been deleted. Only users with thread management privileges can see it.",
+    "following_topic.title": "Following Topic",
+    "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
+    "not_following_topic.title": "Not Following Topic",
+    "not_following_topic.message": "You will no longer receive notifications from this topic.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Watch",
     "share_this_post": "Share this Post",
     "thread_tools.title": "Seçenekler",
@@ -58,8 +66,17 @@
     "composer.title_placeholder": "Enter your topic title here...",
     "composer.write": "Write",
     "composer.preview": "Preview",
+    "composer.help": "Help",
     "composer.discard": "Discard",
     "composer.submit": "Submit",
     "composer.replying_to": "Replying to",
-    "composer.new_topic": "New Topic"
+    "composer.new_topic": "New Topic",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
+    "composer.drag_and_drop_images": "Drag and Drop Images Here",
+    "composer.upload_instructions": "Upload images by dragging & dropping them."
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/category.json b/public/language/zh_CN/category.json
index 7cd55cb0c3..570400f15b 100644
--- a/public/language/zh_CN/category.json
+++ b/public/language/zh_CN/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "新主题",
     "no_topics": "<strong>这个版面还没有任何内容。</strong><br />赶紧来发帖吧!",
-    "sidebar.recent_replies": "最近回复",
-    "sidebar.active_participants": "活跃用户",
-    "sidebar.moderators": "版主",
     "posts": "帖子",
     "views": "浏览",
     "posted": "发布",
diff --git a/public/language/zh_CN/global.json b/public/language/zh_CN/global.json
index 3d930991d7..174386fd13 100644
--- a/public/language/zh_CN/global.json
+++ b/public/language/zh_CN/global.json
@@ -13,7 +13,7 @@
     "please_log_in": "请登录",
     "posting_restriction_info": "发表目前仅限于注册会员,点击这里登录。",
     "welcome_back": "欢迎回来",
-    "you_have_successfully_logged_in": "你已经退出登录",
+    "you_have_successfully_logged_in": "你已成功登录!",
     "logout": "退出",
     "logout.title": "你已经退出。",
     "logout.message": "你已经成功退出登录。",
@@ -49,6 +49,7 @@
     "posted": "发布",
     "in": "在",
     "recentposts": "最新发表",
+    "recentips": "Recently Logged In IPs",
     "online": " 在线",
     "away": "离开",
     "dnd": "不打扰",
diff --git a/public/language/zh_CN/pages.json b/public/language/zh_CN/pages.json
index d4218f3f08..712bf602f1 100644
--- a/public/language/zh_CN/pages.json
+++ b/public/language/zh_CN/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "编辑 \"%1\"",
     "user.following": "%1的人关注",
     "user.followers": "%1关注的人",
+    "user.posts": "Posts made by %1",
     "user.favourites": "%1 喜爱的帖子",
     "user.settings": "用户设置"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/topic.json b/public/language/zh_CN/topic.json
index 381c310b52..4992fa4719 100644
--- a/public/language/zh_CN/topic.json
+++ b/public/language/zh_CN/topic.json
@@ -11,6 +11,7 @@
     "reply": "回复",
     "edit": "编辑",
     "delete": "删除",
+    "restore": "Restore",
     "move": "移动",
     "fork": "作为主题",
     "banned": "禁止",
@@ -18,13 +19,15 @@
     "share": "分享",
     "tools": "工具",
     "flag": "标志",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "标志受限的帖子",
     "deleted_message": "这个帖子已经删除,只有帖子的拥有者才有权限去查看。",
     "following_topic.title": "关注该主题",
     "following_topic.message": "当有回复提交的时候你将会收到通知。",
     "not_following_topic.title": "非关注主题",
     "not_following_topic.message": "你将不再接受来自该帖子的通知。",
-    "login_to_subscribe": "请注册或登录以订阅该主题",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "查看",
     "share_this_post": "分享帖子",
     "thread_tools.title": "管理工具",
@@ -63,11 +66,17 @@
     "composer.title_placeholder": "在这里输入你的主题标题...",
     "composer.write": "书写",
     "composer.preview": "预览",
+    "composer.help": "Help",
     "composer.discard": "丢弃",
     "composer.submit": "提交",
     "composer.replying_to": "回复",
     "composer.new_topic": "新主题",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
     "composer.drag_and_drop_images": "把图像拖到此处",
-    "composer.content_is_parsed_with": "内容已经被解析",
     "composer.upload_instructions": "拖拽图片以上传"
 }
\ No newline at end of file
diff --git a/public/language/zh_TW/category.json b/public/language/zh_TW/category.json
index 3d9745daac..dc50d92e39 100644
--- a/public/language/zh_TW/category.json
+++ b/public/language/zh_TW/category.json
@@ -1,9 +1,6 @@
 {
     "new_topic_button": "新主題",
     "no_topics": "<strong>這個版面還沒有任何內容。</strong><br />趕緊來發帖吧!",
-    "sidebar.recent_replies": "最近回復",
-    "sidebar.active_participants": "活躍用戶",
-    "sidebar.moderators": "版主",
     "posts": "帖子",
     "views": "瀏覽",
     "posted": "發布",
diff --git a/public/language/zh_TW/global.json b/public/language/zh_TW/global.json
index d00e8f8baf..149dda2fb1 100644
--- a/public/language/zh_TW/global.json
+++ b/public/language/zh_TW/global.json
@@ -10,6 +10,8 @@
     "500.message": "不好!看來是哪裡出錯了!",
     "register": "注冊",
     "login": "登錄",
+    "please_log_in": "Please Log In",
+    "posting_restriction_info": "Posting is currently restricted to registered members only, click here to log in.",
     "welcome_back": "Welcome Back ",
     "you_have_successfully_logged_in": "You have successfully logged in",
     "logout": "退出",
@@ -47,6 +49,7 @@
     "posted": "posted",
     "in": "in",
     "recentposts": "Recent Posts",
+    "recentips": "Recently Logged In IPs",
     "online": "Online",
     "away": "Away",
     "dnd": "Do not Disturb",
diff --git a/public/language/zh_TW/pages.json b/public/language/zh_TW/pages.json
index d60e0a0a9b..6b41654688 100644
--- a/public/language/zh_TW/pages.json
+++ b/public/language/zh_TW/pages.json
@@ -8,6 +8,7 @@
     "user.edit": "Editing \"%1\"",
     "user.following": "People %1 Follows",
     "user.followers": "People who Follow %1",
+    "user.posts": "Posts made by %1",
     "user.favourites": "%1's Favourite Posts",
     "user.settings": "User Settings"
 }
\ No newline at end of file
diff --git a/public/language/zh_TW/topic.json b/public/language/zh_TW/topic.json
index e1b54c68e5..6a76485358 100644
--- a/public/language/zh_TW/topic.json
+++ b/public/language/zh_TW/topic.json
@@ -11,6 +11,7 @@
     "reply": "回復",
     "edit": "編輯",
     "delete": "刪除",
+    "restore": "Restore",
     "move": "移動",
     "fork": "作為主題",
     "banned": "封禁",
@@ -18,8 +19,15 @@
     "share": "Share",
     "tools": "Tools",
     "flag": "Flag",
+    "bookmark_instructions": "Click here to return to your last position or close to discard.",
     "flag_title": "Flag this post for moderation",
     "deleted_message": "This thread has been deleted. Only users with thread management privileges can see it.",
+    "following_topic.title": "Following Topic",
+    "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
+    "not_following_topic.title": "Not Following Topic",
+    "not_following_topic.message": "You will no longer receive notifications from this topic.",
+    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
+    "markAsUnreadForAll.success": "Topic marked as unread for all.",
     "watch": "Watch",
     "share_this_post": "Share this Post",
     "thread_tools.title": "管理工具",
@@ -58,8 +66,17 @@
     "composer.title_placeholder": "Enter your topic title here...",
     "composer.write": "Write",
     "composer.preview": "Preview",
+    "composer.help": "Help",
     "composer.discard": "Discard",
     "composer.submit": "Submit",
     "composer.replying_to": "Replying to",
-    "composer.new_topic": "New Topic"
+    "composer.new_topic": "New Topic",
+    "composer.uploading": "uploading...",
+    "composer.thumb_url_label": "Paste a topic thumbnail URL",
+    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+    "composer.thumb_file_label": "Or upload a file",
+    "composer.thumb_remove": "Clear fields",
+    "composer.drag_and_drop_images": "Drag and Drop Images Here",
+    "composer.upload_instructions": "Upload images by dragging & dropping them."
 }
\ No newline at end of file

From c49c3e3550d9c8093397768ff076555b7d2935a7 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Thu, 27 Feb 2014 15:35:50 -0500
Subject: [PATCH 107/193] fixed jquery ui package

---
 public/src/ajaxify.js                         |    3 +-
 public/src/forum/category.js                  |    4 +
 .../jquery/js/jquery-ui-1.10.4.custom.js      | 3028 +----------------
 3 files changed, 9 insertions(+), 3026 deletions(-)

diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
index 8681ceec1b..6b88546cba 100644
--- a/public/src/ajaxify.js
+++ b/public/src/ajaxify.js
@@ -22,6 +22,7 @@ var ajaxify = {};
 	window.onpopstate = function (event) {
 		if (event !== null && event.state && event.state.url !== undefined && !ajaxify.initialLoad) {
 			ajaxify.go(event.state.url, null, true);
+			$(window).trigger('action:popstate', {url: event.state.url});
 		}
 	};
 
@@ -129,7 +130,7 @@ var ajaxify = {};
 									$this.addClass($this.attr('no-widget-class'));
 								});
 							}
-							
+
 							next(err);
 						});
 					}, function(err) {
diff --git a/public/src/forum/category.js b/public/src/forum/category.js
index 1a27392a4d..da62a01226 100644
--- a/public/src/forum/category.js
+++ b/public/src/forum/category.js
@@ -2,6 +2,10 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 	var Category = {},
 		loadingMoreTopics = false;
 
+	$(window).on('action:popstate', function(ev, data) {
+
+	});
+
 	Category.init = function() {
 		var	cid = templates.get('category_id'),
 			categoryName = templates.get('category_name'),
diff --git a/public/vendor/jquery/js/jquery-ui-1.10.4.custom.js b/public/vendor/jquery/js/jquery-ui-1.10.4.custom.js
index 7d3c11b547..1f6654ced5 100644
--- a/public/vendor/jquery/js/jquery-ui-1.10.4.custom.js
+++ b/public/vendor/jquery/js/jquery-ui-1.10.4.custom.js
@@ -1,3028 +1,6 @@
-/*! jQuery UI - v1.10.4 - 2014-01-19
+/*! jQuery UI - v1.10.4 - 2014-02-27
 * http://jqueryui.com
-* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.draggable.js, jquery.ui.menu.js
+* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.sortable.js, jquery.ui.menu.js
 * Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */
 
-(function( $, undefined ) {
-
-var uuid = 0,
-	runiqueId = /^ui-id-\d+$/;
-
-// $.ui might exist from components with no dependencies, e.g., $.ui.position
-$.ui = $.ui || {};
-
-$.extend( $.ui, {
-	version: "1.10.4",
-
-	keyCode: {
-		BACKSPACE: 8,
-		COMMA: 188,
-		DELETE: 46,
-		DOWN: 40,
-		END: 35,
-		ENTER: 13,
-		ESCAPE: 27,
-		HOME: 36,
-		LEFT: 37,
-		NUMPAD_ADD: 107,
-		NUMPAD_DECIMAL: 110,
-		NUMPAD_DIVIDE: 111,
-		NUMPAD_ENTER: 108,
-		NUMPAD_MULTIPLY: 106,
-		NUMPAD_SUBTRACT: 109,
-		PAGE_DOWN: 34,
-		PAGE_UP: 33,
-		PERIOD: 190,
-		RIGHT: 39,
-		SPACE: 32,
-		TAB: 9,
-		UP: 38
-	}
-});
-
-// plugins
-$.fn.extend({
-	focus: (function( orig ) {
-		return function( delay, fn ) {
-			return typeof delay === "number" ?
-				this.each(function() {
-					var elem = this;
-					setTimeout(function() {
-						$( elem ).focus();
-						if ( fn ) {
-							fn.call( elem );
-						}
-					}, delay );
-				}) :
-				orig.apply( this, arguments );
-		};
-	})( $.fn.focus ),
-
-	scrollParent: function() {
-		var scrollParent;
-		if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) {
-			scrollParent = this.parents().filter(function() {
-				return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
-			}).eq(0);
-		} else {
-			scrollParent = this.parents().filter(function() {
-				return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
-			}).eq(0);
-		}
-
-		return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent;
-	},
-
-	zIndex: function( zIndex ) {
-		if ( zIndex !== undefined ) {
-			return this.css( "zIndex", zIndex );
-		}
-
-		if ( this.length ) {
-			var elem = $( this[ 0 ] ), position, value;
-			while ( elem.length && elem[ 0 ] !== document ) {
-				// Ignore z-index if position is set to a value where z-index is ignored by the browser
-				// This makes behavior of this function consistent across browsers
-				// WebKit always returns auto if the element is positioned
-				position = elem.css( "position" );
-				if ( position === "absolute" || position === "relative" || position === "fixed" ) {
-					// IE returns 0 when zIndex is not specified
-					// other browsers return a string
-					// we ignore the case of nested elements with an explicit value of 0
-					// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
-					value = parseInt( elem.css( "zIndex" ), 10 );
-					if ( !isNaN( value ) && value !== 0 ) {
-						return value;
-					}
-				}
-				elem = elem.parent();
-			}
-		}
-
-		return 0;
-	},
-
-	uniqueId: function() {
-		return this.each(function() {
-			if ( !this.id ) {
-				this.id = "ui-id-" + (++uuid);
-			}
-		});
-	},
-
-	removeUniqueId: function() {
-		return this.each(function() {
-			if ( runiqueId.test( this.id ) ) {
-				$( this ).removeAttr( "id" );
-			}
-		});
-	}
-});
-
-// selectors
-function focusable( element, isTabIndexNotNaN ) {
-	var map, mapName, img,
-		nodeName = element.nodeName.toLowerCase();
-	if ( "area" === nodeName ) {
-		map = element.parentNode;
-		mapName = map.name;
-		if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
-			return false;
-		}
-		img = $( "img[usemap=#" + mapName + "]" )[0];
-		return !!img && visible( img );
-	}
-	return ( /input|select|textarea|button|object/.test( nodeName ) ?
-		!element.disabled :
-		"a" === nodeName ?
-			element.href || isTabIndexNotNaN :
-			isTabIndexNotNaN) &&
-		// the element and all of its ancestors must be visible
-		visible( element );
-}
-
-function visible( element ) {
-	return $.expr.filters.visible( element ) &&
-		!$( element ).parents().addBack().filter(function() {
-			return $.css( this, "visibility" ) === "hidden";
-		}).length;
-}
-
-$.extend( $.expr[ ":" ], {
-	data: $.expr.createPseudo ?
-		$.expr.createPseudo(function( dataName ) {
-			return function( elem ) {
-				return !!$.data( elem, dataName );
-			};
-		}) :
-		// support: jQuery <1.8
-		function( elem, i, match ) {
-			return !!$.data( elem, match[ 3 ] );
-		},
-
-	focusable: function( element ) {
-		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
-	},
-
-	tabbable: function( element ) {
-		var tabIndex = $.attr( element, "tabindex" ),
-			isTabIndexNaN = isNaN( tabIndex );
-		return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
-	}
-});
-
-// support: jQuery <1.8
-if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
-	$.each( [ "Width", "Height" ], function( i, name ) {
-		var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
-			type = name.toLowerCase(),
-			orig = {
-				innerWidth: $.fn.innerWidth,
-				innerHeight: $.fn.innerHeight,
-				outerWidth: $.fn.outerWidth,
-				outerHeight: $.fn.outerHeight
-			};
-
-		function reduce( elem, size, border, margin ) {
-			$.each( side, function() {
-				size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
-				if ( border ) {
-					size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
-				}
-				if ( margin ) {
-					size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
-				}
-			});
-			return size;
-		}
-
-		$.fn[ "inner" + name ] = function( size ) {
-			if ( size === undefined ) {
-				return orig[ "inner" + name ].call( this );
-			}
-
-			return this.each(function() {
-				$( this ).css( type, reduce( this, size ) + "px" );
-			});
-		};
-
-		$.fn[ "outer" + name] = function( size, margin ) {
-			if ( typeof size !== "number" ) {
-				return orig[ "outer" + name ].call( this, size );
-			}
-
-			return this.each(function() {
-				$( this).css( type, reduce( this, size, true, margin ) + "px" );
-			});
-		};
-	});
-}
-
-// support: jQuery <1.8
-if ( !$.fn.addBack ) {
-	$.fn.addBack = function( selector ) {
-		return this.add( selector == null ?
-			this.prevObject : this.prevObject.filter( selector )
-		);
-	};
-}
-
-// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
-if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
-	$.fn.removeData = (function( removeData ) {
-		return function( key ) {
-			if ( arguments.length ) {
-				return removeData.call( this, $.camelCase( key ) );
-			} else {
-				return removeData.call( this );
-			}
-		};
-	})( $.fn.removeData );
-}
-
-
-
-
-
-// deprecated
-$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
-
-$.support.selectstart = "onselectstart" in document.createElement( "div" );
-$.fn.extend({
-	disableSelection: function() {
-		return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
-			".ui-disableSelection", function( event ) {
-				event.preventDefault();
-			});
-	},
-
-	enableSelection: function() {
-		return this.unbind( ".ui-disableSelection" );
-	}
-});
-
-$.extend( $.ui, {
-	// $.ui.plugin is deprecated. Use $.widget() extensions instead.
-	plugin: {
-		add: function( module, option, set ) {
-			var i,
-				proto = $.ui[ module ].prototype;
-			for ( i in set ) {
-				proto.plugins[ i ] = proto.plugins[ i ] || [];
-				proto.plugins[ i ].push( [ option, set[ i ] ] );
-			}
-		},
-		call: function( instance, name, args ) {
-			var i,
-				set = instance.plugins[ name ];
-			if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
-				return;
-			}
-
-			for ( i = 0; i < set.length; i++ ) {
-				if ( instance.options[ set[ i ][ 0 ] ] ) {
-					set[ i ][ 1 ].apply( instance.element, args );
-				}
-			}
-		}
-	},
-
-	// only used by resizable
-	hasScroll: function( el, a ) {
-
-		//If overflow is hidden, the element might have extra content, but the user wants to hide it
-		if ( $( el ).css( "overflow" ) === "hidden") {
-			return false;
-		}
-
-		var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
-			has = false;
-
-		if ( el[ scroll ] > 0 ) {
-			return true;
-		}
-
-		// TODO: determine which cases actually cause this to happen
-		// if the element doesn't have the scroll set, see if it's possible to
-		// set the scroll
-		el[ scroll ] = 1;
-		has = ( el[ scroll ] > 0 );
-		el[ scroll ] = 0;
-		return has;
-	}
-});
-
-})( jQuery );
-(function( $, undefined ) {
-
-var uuid = 0,
-	slice = Array.prototype.slice,
-	_cleanData = $.cleanData;
-$.cleanData = function( elems ) {
-	for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
-		try {
-			$( elem ).triggerHandler( "remove" );
-		// http://bugs.jquery.com/ticket/8235
-		} catch( e ) {}
-	}
-	_cleanData( elems );
-};
-
-$.widget = function( name, base, prototype ) {
-	var fullName, existingConstructor, constructor, basePrototype,
-		// proxiedPrototype allows the provided prototype to remain unmodified
-		// so that it can be used as a mixin for multiple widgets (#8876)
-		proxiedPrototype = {},
-		namespace = name.split( "." )[ 0 ];
-
-	name = name.split( "." )[ 1 ];
-	fullName = namespace + "-" + name;
-
-	if ( !prototype ) {
-		prototype = base;
-		base = $.Widget;
-	}
-
-	// create selector for plugin
-	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
-		return !!$.data( elem, fullName );
-	};
-
-	$[ namespace ] = $[ namespace ] || {};
-	existingConstructor = $[ namespace ][ name ];
-	constructor = $[ namespace ][ name ] = function( options, element ) {
-		// allow instantiation without "new" keyword
-		if ( !this._createWidget ) {
-			return new constructor( options, element );
-		}
-
-		// allow instantiation without initializing for simple inheritance
-		// must use "new" keyword (the code above always passes args)
-		if ( arguments.length ) {
-			this._createWidget( options, element );
-		}
-	};
-	// extend with the existing constructor to carry over any static properties
-	$.extend( constructor, existingConstructor, {
-		version: prototype.version,
-		// copy the object used to create the prototype in case we need to
-		// redefine the widget later
-		_proto: $.extend( {}, prototype ),
-		// track widgets that inherit from this widget in case this widget is
-		// redefined after a widget inherits from it
-		_childConstructors: []
-	});
-
-	basePrototype = new base();
-	// we need to make the options hash a property directly on the new instance
-	// otherwise we'll modify the options hash on the prototype that we're
-	// inheriting from
-	basePrototype.options = $.widget.extend( {}, basePrototype.options );
-	$.each( prototype, function( prop, value ) {
-		if ( !$.isFunction( value ) ) {
-			proxiedPrototype[ prop ] = value;
-			return;
-		}
-		proxiedPrototype[ prop ] = (function() {
-			var _super = function() {
-					return base.prototype[ prop ].apply( this, arguments );
-				},
-				_superApply = function( args ) {
-					return base.prototype[ prop ].apply( this, args );
-				};
-			return function() {
-				var __super = this._super,
-					__superApply = this._superApply,
-					returnValue;
-
-				this._super = _super;
-				this._superApply = _superApply;
-
-				returnValue = value.apply( this, arguments );
-
-				this._super = __super;
-				this._superApply = __superApply;
-
-				return returnValue;
-			};
-		})();
-	});
-	constructor.prototype = $.widget.extend( basePrototype, {
-		// TODO: remove support for widgetEventPrefix
-		// always use the name + a colon as the prefix, e.g., draggable:start
-		// don't prefix for widgets that aren't DOM-based
-		widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
-	}, proxiedPrototype, {
-		constructor: constructor,
-		namespace: namespace,
-		widgetName: name,
-		widgetFullName: fullName
-	});
-
-	// If this widget is being redefined then we need to find all widgets that
-	// are inheriting from it and redefine all of them so that they inherit from
-	// the new version of this widget. We're essentially trying to replace one
-	// level in the prototype chain.
-	if ( existingConstructor ) {
-		$.each( existingConstructor._childConstructors, function( i, child ) {
-			var childPrototype = child.prototype;
-
-			// redefine the child widget using the same prototype that was
-			// originally used, but inherit from the new version of the base
-			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
-		});
-		// remove the list of existing child constructors from the old constructor
-		// so the old child constructors can be garbage collected
-		delete existingConstructor._childConstructors;
-	} else {
-		base._childConstructors.push( constructor );
-	}
-
-	$.widget.bridge( name, constructor );
-};
-
-$.widget.extend = function( target ) {
-	var input = slice.call( arguments, 1 ),
-		inputIndex = 0,
-		inputLength = input.length,
-		key,
-		value;
-	for ( ; inputIndex < inputLength; inputIndex++ ) {
-		for ( key in input[ inputIndex ] ) {
-			value = input[ inputIndex ][ key ];
-			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
-				// Clone objects
-				if ( $.isPlainObject( value ) ) {
-					target[ key ] = $.isPlainObject( target[ key ] ) ?
-						$.widget.extend( {}, target[ key ], value ) :
-						// Don't extend strings, arrays, etc. with objects
-						$.widget.extend( {}, value );
-				// Copy everything else by reference
-				} else {
-					target[ key ] = value;
-				}
-			}
-		}
-	}
-	return target;
-};
-
-$.widget.bridge = function( name, object ) {
-	var fullName = object.prototype.widgetFullName || name;
-	$.fn[ name ] = function( options ) {
-		var isMethodCall = typeof options === "string",
-			args = slice.call( arguments, 1 ),
-			returnValue = this;
-
-		// allow multiple hashes to be passed on init
-		options = !isMethodCall && args.length ?
-			$.widget.extend.apply( null, [ options ].concat(args) ) :
-			options;
-
-		if ( isMethodCall ) {
-			this.each(function() {
-				var methodValue,
-					instance = $.data( this, fullName );
-				if ( !instance ) {
-					return $.error( "cannot call methods on " + name + " prior to initialization; " +
-						"attempted to call method '" + options + "'" );
-				}
-				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
-					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
-				}
-				methodValue = instance[ options ].apply( instance, args );
-				if ( methodValue !== instance && methodValue !== undefined ) {
-					returnValue = methodValue && methodValue.jquery ?
-						returnValue.pushStack( methodValue.get() ) :
-						methodValue;
-					return false;
-				}
-			});
-		} else {
-			this.each(function() {
-				var instance = $.data( this, fullName );
-				if ( instance ) {
-					instance.option( options || {} )._init();
-				} else {
-					$.data( this, fullName, new object( options, this ) );
-				}
-			});
-		}
-
-		return returnValue;
-	};
-};
-
-$.Widget = function( /* options, element */ ) {};
-$.Widget._childConstructors = [];
-
-$.Widget.prototype = {
-	widgetName: "widget",
-	widgetEventPrefix: "",
-	defaultElement: "<div>",
-	options: {
-		disabled: false,
-
-		// callbacks
-		create: null
-	},
-	_createWidget: function( options, element ) {
-		element = $( element || this.defaultElement || this )[ 0 ];
-		this.element = $( element );
-		this.uuid = uuid++;
-		this.eventNamespace = "." + this.widgetName + this.uuid;
-		this.options = $.widget.extend( {},
-			this.options,
-			this._getCreateOptions(),
-			options );
-
-		this.bindings = $();
-		this.hoverable = $();
-		this.focusable = $();
-
-		if ( element !== this ) {
-			$.data( element, this.widgetFullName, this );
-			this._on( true, this.element, {
-				remove: function( event ) {
-					if ( event.target === element ) {
-						this.destroy();
-					}
-				}
-			});
-			this.document = $( element.style ?
-				// element within the document
-				element.ownerDocument :
-				// element is window or document
-				element.document || element );
-			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
-		}
-
-		this._create();
-		this._trigger( "create", null, this._getCreateEventData() );
-		this._init();
-	},
-	_getCreateOptions: $.noop,
-	_getCreateEventData: $.noop,
-	_create: $.noop,
-	_init: $.noop,
-
-	destroy: function() {
-		this._destroy();
-		// we can probably remove the unbind calls in 2.0
-		// all event bindings should go through this._on()
-		this.element
-			.unbind( this.eventNamespace )
-			// 1.9 BC for #7810
-			// TODO remove dual storage
-			.removeData( this.widgetName )
-			.removeData( this.widgetFullName )
-			// support: jquery <1.6.3
-			// http://bugs.jquery.com/ticket/9413
-			.removeData( $.camelCase( this.widgetFullName ) );
-		this.widget()
-			.unbind( this.eventNamespace )
-			.removeAttr( "aria-disabled" )
-			.removeClass(
-				this.widgetFullName + "-disabled " +
-				"ui-state-disabled" );
-
-		// clean up events and states
-		this.bindings.unbind( this.eventNamespace );
-		this.hoverable.removeClass( "ui-state-hover" );
-		this.focusable.removeClass( "ui-state-focus" );
-	},
-	_destroy: $.noop,
-
-	widget: function() {
-		return this.element;
-	},
-
-	option: function( key, value ) {
-		var options = key,
-			parts,
-			curOption,
-			i;
-
-		if ( arguments.length === 0 ) {
-			// don't return a reference to the internal hash
-			return $.widget.extend( {}, this.options );
-		}
-
-		if ( typeof key === "string" ) {
-			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
-			options = {};
-			parts = key.split( "." );
-			key = parts.shift();
-			if ( parts.length ) {
-				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
-				for ( i = 0; i < parts.length - 1; i++ ) {
-					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
-					curOption = curOption[ parts[ i ] ];
-				}
-				key = parts.pop();
-				if ( arguments.length === 1 ) {
-					return curOption[ key ] === undefined ? null : curOption[ key ];
-				}
-				curOption[ key ] = value;
-			} else {
-				if ( arguments.length === 1 ) {
-					return this.options[ key ] === undefined ? null : this.options[ key ];
-				}
-				options[ key ] = value;
-			}
-		}
-
-		this._setOptions( options );
-
-		return this;
-	},
-	_setOptions: function( options ) {
-		var key;
-
-		for ( key in options ) {
-			this._setOption( key, options[ key ] );
-		}
-
-		return this;
-	},
-	_setOption: function( key, value ) {
-		this.options[ key ] = value;
-
-		if ( key === "disabled" ) {
-			this.widget()
-				.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
-				.attr( "aria-disabled", value );
-			this.hoverable.removeClass( "ui-state-hover" );
-			this.focusable.removeClass( "ui-state-focus" );
-		}
-
-		return this;
-	},
-
-	enable: function() {
-		return this._setOption( "disabled", false );
-	},
-	disable: function() {
-		return this._setOption( "disabled", true );
-	},
-
-	_on: function( suppressDisabledCheck, element, handlers ) {
-		var delegateElement,
-			instance = this;
-
-		// no suppressDisabledCheck flag, shuffle arguments
-		if ( typeof suppressDisabledCheck !== "boolean" ) {
-			handlers = element;
-			element = suppressDisabledCheck;
-			suppressDisabledCheck = false;
-		}
-
-		// no element argument, shuffle and use this.element
-		if ( !handlers ) {
-			handlers = element;
-			element = this.element;
-			delegateElement = this.widget();
-		} else {
-			// accept selectors, DOM elements
-			element = delegateElement = $( element );
-			this.bindings = this.bindings.add( element );
-		}
-
-		$.each( handlers, function( event, handler ) {
-			function handlerProxy() {
-				// allow widgets to customize the disabled handling
-				// - disabled as an array instead of boolean
-				// - disabled class as method for disabling individual parts
-				if ( !suppressDisabledCheck &&
-						( instance.options.disabled === true ||
-							$( this ).hasClass( "ui-state-disabled" ) ) ) {
-					return;
-				}
-				return ( typeof handler === "string" ? instance[ handler ] : handler )
-					.apply( instance, arguments );
-			}
-
-			// copy the guid so direct unbinding works
-			if ( typeof handler !== "string" ) {
-				handlerProxy.guid = handler.guid =
-					handler.guid || handlerProxy.guid || $.guid++;
-			}
-
-			var match = event.match( /^(\w+)\s*(.*)$/ ),
-				eventName = match[1] + instance.eventNamespace,
-				selector = match[2];
-			if ( selector ) {
-				delegateElement.delegate( selector, eventName, handlerProxy );
-			} else {
-				element.bind( eventName, handlerProxy );
-			}
-		});
-	},
-
-	_off: function( element, eventName ) {
-		eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
-		element.unbind( eventName ).undelegate( eventName );
-	},
-
-	_delay: function( handler, delay ) {
-		function handlerProxy() {
-			return ( typeof handler === "string" ? instance[ handler ] : handler )
-				.apply( instance, arguments );
-		}
-		var instance = this;
-		return setTimeout( handlerProxy, delay || 0 );
-	},
-
-	_hoverable: function( element ) {
-		this.hoverable = this.hoverable.add( element );
-		this._on( element, {
-			mouseenter: function( event ) {
-				$( event.currentTarget ).addClass( "ui-state-hover" );
-			},
-			mouseleave: function( event ) {
-				$( event.currentTarget ).removeClass( "ui-state-hover" );
-			}
-		});
-	},
-
-	_focusable: function( element ) {
-		this.focusable = this.focusable.add( element );
-		this._on( element, {
-			focusin: function( event ) {
-				$( event.currentTarget ).addClass( "ui-state-focus" );
-			},
-			focusout: function( event ) {
-				$( event.currentTarget ).removeClass( "ui-state-focus" );
-			}
-		});
-	},
-
-	_trigger: function( type, event, data ) {
-		var prop, orig,
-			callback = this.options[ type ];
-
-		data = data || {};
-		event = $.Event( event );
-		event.type = ( type === this.widgetEventPrefix ?
-			type :
-			this.widgetEventPrefix + type ).toLowerCase();
-		// the original event may come from any element
-		// so we need to reset the target on the new event
-		event.target = this.element[ 0 ];
-
-		// copy original event properties over to the new event
-		orig = event.originalEvent;
-		if ( orig ) {
-			for ( prop in orig ) {
-				if ( !( prop in event ) ) {
-					event[ prop ] = orig[ prop ];
-				}
-			}
-		}
-
-		this.element.trigger( event, data );
-		return !( $.isFunction( callback ) &&
-			callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
-			event.isDefaultPrevented() );
-	}
-};
-
-$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
-	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
-		if ( typeof options === "string" ) {
-			options = { effect: options };
-		}
-		var hasOptions,
-			effectName = !options ?
-				method :
-				options === true || typeof options === "number" ?
-					defaultEffect :
-					options.effect || defaultEffect;
-		options = options || {};
-		if ( typeof options === "number" ) {
-			options = { duration: options };
-		}
-		hasOptions = !$.isEmptyObject( options );
-		options.complete = callback;
-		if ( options.delay ) {
-			element.delay( options.delay );
-		}
-		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
-			element[ method ]( options );
-		} else if ( effectName !== method && element[ effectName ] ) {
-			element[ effectName ]( options.duration, options.easing, callback );
-		} else {
-			element.queue(function( next ) {
-				$( this )[ method ]();
-				if ( callback ) {
-					callback.call( element[ 0 ] );
-				}
-				next();
-			});
-		}
-	};
-});
-
-})( jQuery );
-(function( $, undefined ) {
-
-var mouseHandled = false;
-$( document ).mouseup( function() {
-	mouseHandled = false;
-});
-
-$.widget("ui.mouse", {
-	version: "1.10.4",
-	options: {
-		cancel: "input,textarea,button,select,option",
-		distance: 1,
-		delay: 0
-	},
-	_mouseInit: function() {
-		var that = this;
-
-		this.element
-			.bind("mousedown."+this.widgetName, function(event) {
-				return that._mouseDown(event);
-			})
-			.bind("click."+this.widgetName, function(event) {
-				if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
-					$.removeData(event.target, that.widgetName + ".preventClickEvent");
-					event.stopImmediatePropagation();
-					return false;
-				}
-			});
-
-		this.started = false;
-	},
-
-	// TODO: make sure destroying one instance of mouse doesn't mess with
-	// other instances of mouse
-	_mouseDestroy: function() {
-		this.element.unbind("."+this.widgetName);
-		if ( this._mouseMoveDelegate ) {
-			$(document)
-				.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
-				.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
-		}
-	},
-
-	_mouseDown: function(event) {
-		// don't let more than one widget handle mouseStart
-		if( mouseHandled ) { return; }
-
-		// we may have missed mouseup (out of window)
-		(this._mouseStarted && this._mouseUp(event));
-
-		this._mouseDownEvent = event;
-
-		var that = this,
-			btnIsLeft = (event.which === 1),
-			// event.target.nodeName works around a bug in IE 8 with
-			// disabled inputs (#7620)
-			elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
-		if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
-			return true;
-		}
-
-		this.mouseDelayMet = !this.options.delay;
-		if (!this.mouseDelayMet) {
-			this._mouseDelayTimer = setTimeout(function() {
-				that.mouseDelayMet = true;
-			}, this.options.delay);
-		}
-
-		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
-			this._mouseStarted = (this._mouseStart(event) !== false);
-			if (!this._mouseStarted) {
-				event.preventDefault();
-				return true;
-			}
-		}
-
-		// Click event may never have fired (Gecko & Opera)
-		if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
-			$.removeData(event.target, this.widgetName + ".preventClickEvent");
-		}
-
-		// these delegates are required to keep context
-		this._mouseMoveDelegate = function(event) {
-			return that._mouseMove(event);
-		};
-		this._mouseUpDelegate = function(event) {
-			return that._mouseUp(event);
-		};
-		$(document)
-			.bind("mousemove."+this.widgetName, this._mouseMoveDelegate)
-			.bind("mouseup."+this.widgetName, this._mouseUpDelegate);
-
-		event.preventDefault();
-
-		mouseHandled = true;
-		return true;
-	},
-
-	_mouseMove: function(event) {
-		// IE mouseup check - mouseup happened when mouse was out of window
-		if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) {
-			return this._mouseUp(event);
-		}
-
-		if (this._mouseStarted) {
-			this._mouseDrag(event);
-			return event.preventDefault();
-		}
-
-		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
-			this._mouseStarted =
-				(this._mouseStart(this._mouseDownEvent, event) !== false);
-			(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
-		}
-
-		return !this._mouseStarted;
-	},
-
-	_mouseUp: function(event) {
-		$(document)
-			.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
-			.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
-
-		if (this._mouseStarted) {
-			this._mouseStarted = false;
-
-			if (event.target === this._mouseDownEvent.target) {
-				$.data(event.target, this.widgetName + ".preventClickEvent", true);
-			}
-
-			this._mouseStop(event);
-		}
-
-		return false;
-	},
-
-	_mouseDistanceMet: function(event) {
-		return (Math.max(
-				Math.abs(this._mouseDownEvent.pageX - event.pageX),
-				Math.abs(this._mouseDownEvent.pageY - event.pageY)
-			) >= this.options.distance
-		);
-	},
-
-	_mouseDelayMet: function(/* event */) {
-		return this.mouseDelayMet;
-	},
-
-	// These are placeholder methods, to be overriden by extending plugin
-	_mouseStart: function(/* event */) {},
-	_mouseDrag: function(/* event */) {},
-	_mouseStop: function(/* event */) {},
-	_mouseCapture: function(/* event */) { return true; }
-});
-
-})(jQuery);
-(function( $, undefined ) {
-
-$.ui = $.ui || {};
-
-var cachedScrollbarWidth,
-	max = Math.max,
-	abs = Math.abs,
-	round = Math.round,
-	rhorizontal = /left|center|right/,
-	rvertical = /top|center|bottom/,
-	roffset = /[\+\-]\d+(\.[\d]+)?%?/,
-	rposition = /^\w+/,
-	rpercent = /%$/,
-	_position = $.fn.position;
-
-function getOffsets( offsets, width, height ) {
-	return [
-		parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
-		parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
-	];
-}
-
-function parseCss( element, property ) {
-	return parseInt( $.css( element, property ), 10 ) || 0;
-}
-
-function getDimensions( elem ) {
-	var raw = elem[0];
-	if ( raw.nodeType === 9 ) {
-		return {
-			width: elem.width(),
-			height: elem.height(),
-			offset: { top: 0, left: 0 }
-		};
-	}
-	if ( $.isWindow( raw ) ) {
-		return {
-			width: elem.width(),
-			height: elem.height(),
-			offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
-		};
-	}
-	if ( raw.preventDefault ) {
-		return {
-			width: 0,
-			height: 0,
-			offset: { top: raw.pageY, left: raw.pageX }
-		};
-	}
-	return {
-		width: elem.outerWidth(),
-		height: elem.outerHeight(),
-		offset: elem.offset()
-	};
-}
-
-$.position = {
-	scrollbarWidth: function() {
-		if ( cachedScrollbarWidth !== undefined ) {
-			return cachedScrollbarWidth;
-		}
-		var w1, w2,
-			div = $( "<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
-			innerDiv = div.children()[0];
-
-		$( "body" ).append( div );
-		w1 = innerDiv.offsetWidth;
-		div.css( "overflow", "scroll" );
-
-		w2 = innerDiv.offsetWidth;
-
-		if ( w1 === w2 ) {
-			w2 = div[0].clientWidth;
-		}
-
-		div.remove();
-
-		return (cachedScrollbarWidth = w1 - w2);
-	},
-	getScrollInfo: function( within ) {
-		var overflowX = within.isWindow || within.isDocument ? "" :
-				within.element.css( "overflow-x" ),
-			overflowY = within.isWindow || within.isDocument ? "" :
-				within.element.css( "overflow-y" ),
-			hasOverflowX = overflowX === "scroll" ||
-				( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
-			hasOverflowY = overflowY === "scroll" ||
-				( overflowY === "auto" && within.height < within.element[0].scrollHeight );
-		return {
-			width: hasOverflowY ? $.position.scrollbarWidth() : 0,
-			height: hasOverflowX ? $.position.scrollbarWidth() : 0
-		};
-	},
-	getWithinInfo: function( element ) {
-		var withinElement = $( element || window ),
-			isWindow = $.isWindow( withinElement[0] ),
-			isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9;
-		return {
-			element: withinElement,
-			isWindow: isWindow,
-			isDocument: isDocument,
-			offset: withinElement.offset() || { left: 0, top: 0 },
-			scrollLeft: withinElement.scrollLeft(),
-			scrollTop: withinElement.scrollTop(),
-			width: isWindow ? withinElement.width() : withinElement.outerWidth(),
-			height: isWindow ? withinElement.height() : withinElement.outerHeight()
-		};
-	}
-};
-
-$.fn.position = function( options ) {
-	if ( !options || !options.of ) {
-		return _position.apply( this, arguments );
-	}
-
-	// make a copy, we don't want to modify arguments
-	options = $.extend( {}, options );
-
-	var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
-		target = $( options.of ),
-		within = $.position.getWithinInfo( options.within ),
-		scrollInfo = $.position.getScrollInfo( within ),
-		collision = ( options.collision || "flip" ).split( " " ),
-		offsets = {};
-
-	dimensions = getDimensions( target );
-	if ( target[0].preventDefault ) {
-		// force left top to allow flipping
-		options.at = "left top";
-	}
-	targetWidth = dimensions.width;
-	targetHeight = dimensions.height;
-	targetOffset = dimensions.offset;
-	// clone to reuse original targetOffset later
-	basePosition = $.extend( {}, targetOffset );
-
-	// force my and at to have valid horizontal and vertical positions
-	// if a value is missing or invalid, it will be converted to center
-	$.each( [ "my", "at" ], function() {
-		var pos = ( options[ this ] || "" ).split( " " ),
-			horizontalOffset,
-			verticalOffset;
-
-		if ( pos.length === 1) {
-			pos = rhorizontal.test( pos[ 0 ] ) ?
-				pos.concat( [ "center" ] ) :
-				rvertical.test( pos[ 0 ] ) ?
-					[ "center" ].concat( pos ) :
-					[ "center", "center" ];
-		}
-		pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
-		pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
-
-		// calculate offsets
-		horizontalOffset = roffset.exec( pos[ 0 ] );
-		verticalOffset = roffset.exec( pos[ 1 ] );
-		offsets[ this ] = [
-			horizontalOffset ? horizontalOffset[ 0 ] : 0,
-			verticalOffset ? verticalOffset[ 0 ] : 0
-		];
-
-		// reduce to just the positions without the offsets
-		options[ this ] = [
-			rposition.exec( pos[ 0 ] )[ 0 ],
-			rposition.exec( pos[ 1 ] )[ 0 ]
-		];
-	});
-
-	// normalize collision option
-	if ( collision.length === 1 ) {
-		collision[ 1 ] = collision[ 0 ];
-	}
-
-	if ( options.at[ 0 ] === "right" ) {
-		basePosition.left += targetWidth;
-	} else if ( options.at[ 0 ] === "center" ) {
-		basePosition.left += targetWidth / 2;
-	}
-
-	if ( options.at[ 1 ] === "bottom" ) {
-		basePosition.top += targetHeight;
-	} else if ( options.at[ 1 ] === "center" ) {
-		basePosition.top += targetHeight / 2;
-	}
-
-	atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
-	basePosition.left += atOffset[ 0 ];
-	basePosition.top += atOffset[ 1 ];
-
-	return this.each(function() {
-		var collisionPosition, using,
-			elem = $( this ),
-			elemWidth = elem.outerWidth(),
-			elemHeight = elem.outerHeight(),
-			marginLeft = parseCss( this, "marginLeft" ),
-			marginTop = parseCss( this, "marginTop" ),
-			collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
-			collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
-			position = $.extend( {}, basePosition ),
-			myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
-
-		if ( options.my[ 0 ] === "right" ) {
-			position.left -= elemWidth;
-		} else if ( options.my[ 0 ] === "center" ) {
-			position.left -= elemWidth / 2;
-		}
-
-		if ( options.my[ 1 ] === "bottom" ) {
-			position.top -= elemHeight;
-		} else if ( options.my[ 1 ] === "center" ) {
-			position.top -= elemHeight / 2;
-		}
-
-		position.left += myOffset[ 0 ];
-		position.top += myOffset[ 1 ];
-
-		// if the browser doesn't support fractions, then round for consistent results
-		if ( !$.support.offsetFractions ) {
-			position.left = round( position.left );
-			position.top = round( position.top );
-		}
-
-		collisionPosition = {
-			marginLeft: marginLeft,
-			marginTop: marginTop
-		};
-
-		$.each( [ "left", "top" ], function( i, dir ) {
-			if ( $.ui.position[ collision[ i ] ] ) {
-				$.ui.position[ collision[ i ] ][ dir ]( position, {
-					targetWidth: targetWidth,
-					targetHeight: targetHeight,
-					elemWidth: elemWidth,
-					elemHeight: elemHeight,
-					collisionPosition: collisionPosition,
-					collisionWidth: collisionWidth,
-					collisionHeight: collisionHeight,
-					offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
-					my: options.my,
-					at: options.at,
-					within: within,
-					elem : elem
-				});
-			}
-		});
-
-		if ( options.using ) {
-			// adds feedback as second argument to using callback, if present
-			using = function( props ) {
-				var left = targetOffset.left - position.left,
-					right = left + targetWidth - elemWidth,
-					top = targetOffset.top - position.top,
-					bottom = top + targetHeight - elemHeight,
-					feedback = {
-						target: {
-							element: target,
-							left: targetOffset.left,
-							top: targetOffset.top,
-							width: targetWidth,
-							height: targetHeight
-						},
-						element: {
-							element: elem,
-							left: position.left,
-							top: position.top,
-							width: elemWidth,
-							height: elemHeight
-						},
-						horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
-						vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
-					};
-				if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
-					feedback.horizontal = "center";
-				}
-				if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
-					feedback.vertical = "middle";
-				}
-				if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
-					feedback.important = "horizontal";
-				} else {
-					feedback.important = "vertical";
-				}
-				options.using.call( this, props, feedback );
-			};
-		}
-
-		elem.offset( $.extend( position, { using: using } ) );
-	});
-};
-
-$.ui.position = {
-	fit: {
-		left: function( position, data ) {
-			var within = data.within,
-				withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
-				outerWidth = within.width,
-				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
-				overLeft = withinOffset - collisionPosLeft,
-				overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
-				newOverRight;
-
-			// element is wider than within
-			if ( data.collisionWidth > outerWidth ) {
-				// element is initially over the left side of within
-				if ( overLeft > 0 && overRight <= 0 ) {
-					newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
-					position.left += overLeft - newOverRight;
-				// element is initially over right side of within
-				} else if ( overRight > 0 && overLeft <= 0 ) {
-					position.left = withinOffset;
-				// element is initially over both left and right sides of within
-				} else {
-					if ( overLeft > overRight ) {
-						position.left = withinOffset + outerWidth - data.collisionWidth;
-					} else {
-						position.left = withinOffset;
-					}
-				}
-			// too far left -> align with left edge
-			} else if ( overLeft > 0 ) {
-				position.left += overLeft;
-			// too far right -> align with right edge
-			} else if ( overRight > 0 ) {
-				position.left -= overRight;
-			// adjust based on position and margin
-			} else {
-				position.left = max( position.left - collisionPosLeft, position.left );
-			}
-		},
-		top: function( position, data ) {
-			var within = data.within,
-				withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
-				outerHeight = data.within.height,
-				collisionPosTop = position.top - data.collisionPosition.marginTop,
-				overTop = withinOffset - collisionPosTop,
-				overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
-				newOverBottom;
-
-			// element is taller than within
-			if ( data.collisionHeight > outerHeight ) {
-				// element is initially over the top of within
-				if ( overTop > 0 && overBottom <= 0 ) {
-					newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
-					position.top += overTop - newOverBottom;
-				// element is initially over bottom of within
-				} else if ( overBottom > 0 && overTop <= 0 ) {
-					position.top = withinOffset;
-				// element is initially over both top and bottom of within
-				} else {
-					if ( overTop > overBottom ) {
-						position.top = withinOffset + outerHeight - data.collisionHeight;
-					} else {
-						position.top = withinOffset;
-					}
-				}
-			// too far up -> align with top
-			} else if ( overTop > 0 ) {
-				position.top += overTop;
-			// too far down -> align with bottom edge
-			} else if ( overBottom > 0 ) {
-				position.top -= overBottom;
-			// adjust based on position and margin
-			} else {
-				position.top = max( position.top - collisionPosTop, position.top );
-			}
-		}
-	},
-	flip: {
-		left: function( position, data ) {
-			var within = data.within,
-				withinOffset = within.offset.left + within.scrollLeft,
-				outerWidth = within.width,
-				offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
-				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
-				overLeft = collisionPosLeft - offsetLeft,
-				overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
-				myOffset = data.my[ 0 ] === "left" ?
-					-data.elemWidth :
-					data.my[ 0 ] === "right" ?
-						data.elemWidth :
-						0,
-				atOffset = data.at[ 0 ] === "left" ?
-					data.targetWidth :
-					data.at[ 0 ] === "right" ?
-						-data.targetWidth :
-						0,
-				offset = -2 * data.offset[ 0 ],
-				newOverRight,
-				newOverLeft;
-
-			if ( overLeft < 0 ) {
-				newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
-				if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
-					position.left += myOffset + atOffset + offset;
-				}
-			}
-			else if ( overRight > 0 ) {
-				newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
-				if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
-					position.left += myOffset + atOffset + offset;
-				}
-			}
-		},
-		top: function( position, data ) {
-			var within = data.within,
-				withinOffset = within.offset.top + within.scrollTop,
-				outerHeight = within.height,
-				offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
-				collisionPosTop = position.top - data.collisionPosition.marginTop,
-				overTop = collisionPosTop - offsetTop,
-				overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
-				top = data.my[ 1 ] === "top",
-				myOffset = top ?
-					-data.elemHeight :
-					data.my[ 1 ] === "bottom" ?
-						data.elemHeight :
-						0,
-				atOffset = data.at[ 1 ] === "top" ?
-					data.targetHeight :
-					data.at[ 1 ] === "bottom" ?
-						-data.targetHeight :
-						0,
-				offset = -2 * data.offset[ 1 ],
-				newOverTop,
-				newOverBottom;
-			if ( overTop < 0 ) {
-				newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
-				if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
-					position.top += myOffset + atOffset + offset;
-				}
-			}
-			else if ( overBottom > 0 ) {
-				newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
-				if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
-					position.top += myOffset + atOffset + offset;
-				}
-			}
-		}
-	},
-	flipfit: {
-		left: function() {
-			$.ui.position.flip.left.apply( this, arguments );
-			$.ui.position.fit.left.apply( this, arguments );
-		},
-		top: function() {
-			$.ui.position.flip.top.apply( this, arguments );
-			$.ui.position.fit.top.apply( this, arguments );
-		}
-	}
-};
-
-// fraction support test
-(function () {
-	var testElement, testElementParent, testElementStyle, offsetLeft, i,
-		body = document.getElementsByTagName( "body" )[ 0 ],
-		div = document.createElement( "div" );
-
-	//Create a "fake body" for testing based on method used in jQuery.support
-	testElement = document.createElement( body ? "div" : "body" );
-	testElementStyle = {
-		visibility: "hidden",
-		width: 0,
-		height: 0,
-		border: 0,
-		margin: 0,
-		background: "none"
-	};
-	if ( body ) {
-		$.extend( testElementStyle, {
-			position: "absolute",
-			left: "-1000px",
-			top: "-1000px"
-		});
-	}
-	for ( i in testElementStyle ) {
-		testElement.style[ i ] = testElementStyle[ i ];
-	}
-	testElement.appendChild( div );
-	testElementParent = body || document.documentElement;
-	testElementParent.insertBefore( testElement, testElementParent.firstChild );
-
-	div.style.cssText = "position: absolute; left: 10.7432222px;";
-
-	offsetLeft = $( div ).offset().left;
-	$.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;
-
-	testElement.innerHTML = "";
-	testElementParent.removeChild( testElement );
-})();
-
-}( jQuery ) );
-(function( $, undefined ) {
-
-$.widget("ui.draggable", $.ui.mouse, {
-	version: "1.10.4",
-	widgetEventPrefix: "drag",
-	options: {
-		addClasses: true,
-		appendTo: "parent",
-		axis: false,
-		connectToSortable: false,
-		containment: false,
-		cursor: "auto",
-		cursorAt: false,
-		grid: false,
-		handle: false,
-		helper: "original",
-		iframeFix: false,
-		opacity: false,
-		refreshPositions: false,
-		revert: false,
-		revertDuration: 500,
-		scope: "default",
-		scroll: true,
-		scrollSensitivity: 20,
-		scrollSpeed: 20,
-		snap: false,
-		snapMode: "both",
-		snapTolerance: 20,
-		stack: false,
-		zIndex: false,
-
-		// callbacks
-		drag: null,
-		start: null,
-		stop: null
-	},
-	_create: function() {
-
-		if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) {
-			this.element[0].style.position = "relative";
-		}
-		if (this.options.addClasses){
-			this.element.addClass("ui-draggable");
-		}
-		if (this.options.disabled){
-			this.element.addClass("ui-draggable-disabled");
-		}
-
-		this._mouseInit();
-
-	},
-
-	_destroy: function() {
-		this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
-		this._mouseDestroy();
-	},
-
-	_mouseCapture: function(event) {
-
-		var o = this.options;
-
-		// among others, prevent a drag on a resizable-handle
-		if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) {
-			return false;
-		}
-
-		//Quit if we're not on a valid handle
-		this.handle = this._getHandle(event);
-		if (!this.handle) {
-			return false;
-		}
-
-		$(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
-			$("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>")
-			.css({
-				width: this.offsetWidth+"px", height: this.offsetHeight+"px",
-				position: "absolute", opacity: "0.001", zIndex: 1000
-			})
-			.css($(this).offset())
-			.appendTo("body");
-		});
-
-		return true;
-
-	},
-
-	_mouseStart: function(event) {
-
-		var o = this.options;
-
-		//Create and append the visible helper
-		this.helper = this._createHelper(event);
-
-		this.helper.addClass("ui-draggable-dragging");
-
-		//Cache the helper size
-		this._cacheHelperProportions();
-
-		//If ddmanager is used for droppables, set the global draggable
-		if($.ui.ddmanager) {
-			$.ui.ddmanager.current = this;
-		}
-
-		/*
-		 * - Position generation -
-		 * This block generates everything position related - it's the core of draggables.
-		 */
-
-		//Cache the margins of the original element
-		this._cacheMargins();
-
-		//Store the helper's css position
-		this.cssPosition = this.helper.css( "position" );
-		this.scrollParent = this.helper.scrollParent();
-		this.offsetParent = this.helper.offsetParent();
-		this.offsetParentCssPosition = this.offsetParent.css( "position" );
-
-		//The element's absolute position on the page minus margins
-		this.offset = this.positionAbs = this.element.offset();
-		this.offset = {
-			top: this.offset.top - this.margins.top,
-			left: this.offset.left - this.margins.left
-		};
-
-		//Reset scroll cache
-		this.offset.scroll = false;
-
-		$.extend(this.offset, {
-			click: { //Where the click happened, relative to the element
-				left: event.pageX - this.offset.left,
-				top: event.pageY - this.offset.top
-			},
-			parent: this._getParentOffset(),
-			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
-		});
-
-		//Generate the original position
-		this.originalPosition = this.position = this._generatePosition(event);
-		this.originalPageX = event.pageX;
-		this.originalPageY = event.pageY;
-
-		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
-		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
-
-		//Set a containment if given in the options
-		this._setContainment();
-
-		//Trigger event + callbacks
-		if(this._trigger("start", event) === false) {
-			this._clear();
-			return false;
-		}
-
-		//Recache the helper size
-		this._cacheHelperProportions();
-
-		//Prepare the droppable offsets
-		if ($.ui.ddmanager && !o.dropBehaviour) {
-			$.ui.ddmanager.prepareOffsets(this, event);
-		}
-
-
-		this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
-
-		//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
-		if ( $.ui.ddmanager ) {
-			$.ui.ddmanager.dragStart(this, event);
-		}
-
-		return true;
-	},
-
-	_mouseDrag: function(event, noPropagation) {
-		// reset any necessary cached properties (see #5009)
-		if ( this.offsetParentCssPosition === "fixed" ) {
-			this.offset.parent = this._getParentOffset();
-		}
-
-		//Compute the helpers position
-		this.position = this._generatePosition(event);
-		this.positionAbs = this._convertPositionTo("absolute");
-
-		//Call plugins and callbacks and use the resulting position if something is returned
-		if (!noPropagation) {
-			var ui = this._uiHash();
-			if(this._trigger("drag", event, ui) === false) {
-				this._mouseUp({});
-				return false;
-			}
-			this.position = ui.position;
-		}
-
-		if(!this.options.axis || this.options.axis !== "y") {
-			this.helper[0].style.left = this.position.left+"px";
-		}
-		if(!this.options.axis || this.options.axis !== "x") {
-			this.helper[0].style.top = this.position.top+"px";
-		}
-		if($.ui.ddmanager) {
-			$.ui.ddmanager.drag(this, event);
-		}
-
-		return false;
-	},
-
-	_mouseStop: function(event) {
-
-		//If we are using droppables, inform the manager about the drop
-		var that = this,
-			dropped = false;
-		if ($.ui.ddmanager && !this.options.dropBehaviour) {
-			dropped = $.ui.ddmanager.drop(this, event);
-		}
-
-		//if a drop comes from outside (a sortable)
-		if(this.dropped) {
-			dropped = this.dropped;
-			this.dropped = false;
-		}
-
-		//if the original element is no longer in the DOM don't bother to continue (see #8269)
-		if ( this.options.helper === "original" && !$.contains( this.element[ 0 ].ownerDocument, this.element[ 0 ] ) ) {
-			return false;
-		}
-
-		if((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
-			$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
-				if(that._trigger("stop", event) !== false) {
-					that._clear();
-				}
-			});
-		} else {
-			if(this._trigger("stop", event) !== false) {
-				this._clear();
-			}
-		}
-
-		return false;
-	},
-
-	_mouseUp: function(event) {
-		//Remove frame helpers
-		$("div.ui-draggable-iframeFix").each(function() {
-			this.parentNode.removeChild(this);
-		});
-
-		//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
-		if( $.ui.ddmanager ) {
-			$.ui.ddmanager.dragStop(this, event);
-		}
-
-		return $.ui.mouse.prototype._mouseUp.call(this, event);
-	},
-
-	cancel: function() {
-
-		if(this.helper.is(".ui-draggable-dragging")) {
-			this._mouseUp({});
-		} else {
-			this._clear();
-		}
-
-		return this;
-
-	},
-
-	_getHandle: function(event) {
-		return this.options.handle ?
-			!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
-			true;
-	},
-
-	_createHelper: function(event) {
-
-		var o = this.options,
-			helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper === "clone" ? this.element.clone().removeAttr("id") : this.element);
-
-		if(!helper.parents("body").length) {
-			helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo));
-		}
-
-		if(helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) {
-			helper.css("position", "absolute");
-		}
-
-		return helper;
-
-	},
-
-	_adjustOffsetFromHelper: function(obj) {
-		if (typeof obj === "string") {
-			obj = obj.split(" ");
-		}
-		if ($.isArray(obj)) {
-			obj = {left: +obj[0], top: +obj[1] || 0};
-		}
-		if ("left" in obj) {
-			this.offset.click.left = obj.left + this.margins.left;
-		}
-		if ("right" in obj) {
-			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
-		}
-		if ("top" in obj) {
-			this.offset.click.top = obj.top + this.margins.top;
-		}
-		if ("bottom" in obj) {
-			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
-		}
-	},
-
-	_getParentOffset: function() {
-
-		//Get the offsetParent and cache its position
-		var po = this.offsetParent.offset();
-
-		// This is a special case where we need to modify a offset calculated on start, since the following happened:
-		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
-		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
-		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
-		if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
-			po.left += this.scrollParent.scrollLeft();
-			po.top += this.scrollParent.scrollTop();
-		}
-
-		//This needs to be actually done for all browsers, since pageX/pageY includes this information
-		//Ugly IE fix
-		if((this.offsetParent[0] === document.body) ||
-			(this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
-			po = { top: 0, left: 0 };
-		}
-
-		return {
-			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
-			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
-		};
-
-	},
-
-	_getRelativeOffset: function() {
-
-		if(this.cssPosition === "relative") {
-			var p = this.element.position();
-			return {
-				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
-				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
-			};
-		} else {
-			return { top: 0, left: 0 };
-		}
-
-	},
-
-	_cacheMargins: function() {
-		this.margins = {
-			left: (parseInt(this.element.css("marginLeft"),10) || 0),
-			top: (parseInt(this.element.css("marginTop"),10) || 0),
-			right: (parseInt(this.element.css("marginRight"),10) || 0),
-			bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
-		};
-	},
-
-	_cacheHelperProportions: function() {
-		this.helperProportions = {
-			width: this.helper.outerWidth(),
-			height: this.helper.outerHeight()
-		};
-	},
-
-	_setContainment: function() {
-
-		var over, c, ce,
-			o = this.options;
-
-		if ( !o.containment ) {
-			this.containment = null;
-			return;
-		}
-
-		if ( o.containment === "window" ) {
-			this.containment = [
-				$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
-				$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
-				$( window ).scrollLeft() + $( window ).width() - this.helperProportions.width - this.margins.left,
-				$( window ).scrollTop() + ( $( window ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
-			];
-			return;
-		}
-
-		if ( o.containment === "document") {
-			this.containment = [
-				0,
-				0,
-				$( document ).width() - this.helperProportions.width - this.margins.left,
-				( $( document ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
-			];
-			return;
-		}
-
-		if ( o.containment.constructor === Array ) {
-			this.containment = o.containment;
-			return;
-		}
-
-		if ( o.containment === "parent" ) {
-			o.containment = this.helper[ 0 ].parentNode;
-		}
-
-		c = $( o.containment );
-		ce = c[ 0 ];
-
-		if( !ce ) {
-			return;
-		}
-
-		over = c.css( "overflow" ) !== "hidden";
-
-		this.containment = [
-			( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
-			( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ) ,
-			( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - this.helperProportions.width - this.margins.left - this.margins.right,
-			( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - this.helperProportions.height - this.margins.top  - this.margins.bottom
-		];
-		this.relative_container = c;
-	},
-
-	_convertPositionTo: function(d, pos) {
-
-		if(!pos) {
-			pos = this.position;
-		}
-
-		var mod = d === "absolute" ? 1 : -1,
-			scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent;
-
-		//Cache the scroll
-		if (!this.offset.scroll) {
-			this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()};
-		}
-
-		return {
-			top: (
-				pos.top	+																// The absolute mouse position
-				this.offset.relative.top * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
-				this.offset.parent.top * mod -										// The offsetParent's offset without borders (offset + border)
-				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top ) * mod )
-			),
-			left: (
-				pos.left +																// The absolute mouse position
-				this.offset.relative.left * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
-				this.offset.parent.left * mod	-										// The offsetParent's offset without borders (offset + border)
-				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left ) * mod )
-			)
-		};
-
-	},
-
-	_generatePosition: function(event) {
-
-		var containment, co, top, left,
-			o = this.options,
-			scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent,
-			pageX = event.pageX,
-			pageY = event.pageY;
-
-		//Cache the scroll
-		if (!this.offset.scroll) {
-			this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()};
-		}
-
-		/*
-		 * - Position constraining -
-		 * Constrain the position to a mix of grid, containment.
-		 */
-
-		// If we are not dragging yet, we won't check for options
-		if ( this.originalPosition ) {
-			if ( this.containment ) {
-				if ( this.relative_container ){
-					co = this.relative_container.offset();
-					containment = [
-						this.containment[ 0 ] + co.left,
-						this.containment[ 1 ] + co.top,
-						this.containment[ 2 ] + co.left,
-						this.containment[ 3 ] + co.top
-					];
-				}
-				else {
-					containment = this.containment;
-				}
-
-				if(event.pageX - this.offset.click.left < containment[0]) {
-					pageX = containment[0] + this.offset.click.left;
-				}
-				if(event.pageY - this.offset.click.top < containment[1]) {
-					pageY = containment[1] + this.offset.click.top;
-				}
-				if(event.pageX - this.offset.click.left > containment[2]) {
-					pageX = containment[2] + this.offset.click.left;
-				}
-				if(event.pageY - this.offset.click.top > containment[3]) {
-					pageY = containment[3] + this.offset.click.top;
-				}
-			}
-
-			if(o.grid) {
-				//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
-				top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
-				pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
-
-				left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
-				pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
-			}
-
-		}
-
-		return {
-			top: (
-				pageY -																	// The absolute mouse position
-				this.offset.click.top	-												// Click offset (relative to the element)
-				this.offset.relative.top -												// Only for relative positioned nodes: Relative offset from element to offset parent
-				this.offset.parent.top +												// The offsetParent's offset without borders (offset + border)
-				( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top )
-			),
-			left: (
-				pageX -																	// The absolute mouse position
-				this.offset.click.left -												// Click offset (relative to the element)
-				this.offset.relative.left -												// Only for relative positioned nodes: Relative offset from element to offset parent
-				this.offset.parent.left +												// The offsetParent's offset without borders (offset + border)
-				( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left )
-			)
-		};
-
-	},
-
-	_clear: function() {
-		this.helper.removeClass("ui-draggable-dragging");
-		if(this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) {
-			this.helper.remove();
-		}
-		this.helper = null;
-		this.cancelHelperRemoval = false;
-	},
-
-	// From now on bulk stuff - mainly helpers
-
-	_trigger: function(type, event, ui) {
-		ui = ui || this._uiHash();
-		$.ui.plugin.call(this, type, [event, ui]);
-		//The absolute position has to be recalculated after plugins
-		if(type === "drag") {
-			this.positionAbs = this._convertPositionTo("absolute");
-		}
-		return $.Widget.prototype._trigger.call(this, type, event, ui);
-	},
-
-	plugins: {},
-
-	_uiHash: function() {
-		return {
-			helper: this.helper,
-			position: this.position,
-			originalPosition: this.originalPosition,
-			offset: this.positionAbs
-		};
-	}
-
-});
-
-$.ui.plugin.add("draggable", "connectToSortable", {
-	start: function(event, ui) {
-
-		var inst = $(this).data("ui-draggable"), o = inst.options,
-			uiSortable = $.extend({}, ui, { item: inst.element });
-		inst.sortables = [];
-		$(o.connectToSortable).each(function() {
-			var sortable = $.data(this, "ui-sortable");
-			if (sortable && !sortable.options.disabled) {
-				inst.sortables.push({
-					instance: sortable,
-					shouldRevert: sortable.options.revert
-				});
-				sortable.refreshPositions();	// Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
-				sortable._trigger("activate", event, uiSortable);
-			}
-		});
-
-	},
-	stop: function(event, ui) {
-
-		//If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
-		var inst = $(this).data("ui-draggable"),
-			uiSortable = $.extend({}, ui, { item: inst.element });
-
-		$.each(inst.sortables, function() {
-			if(this.instance.isOver) {
-
-				this.instance.isOver = 0;
-
-				inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
-				this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
-
-				//The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid"
-				if(this.shouldRevert) {
-					this.instance.options.revert = this.shouldRevert;
-				}
-
-				//Trigger the stop of the sortable
-				this.instance._mouseStop(event);
-
-				this.instance.options.helper = this.instance.options._helper;
-
-				//If the helper has been the original item, restore properties in the sortable
-				if(inst.options.helper === "original") {
-					this.instance.currentItem.css({ top: "auto", left: "auto" });
-				}
-
-			} else {
-				this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
-				this.instance._trigger("deactivate", event, uiSortable);
-			}
-
-		});
-
-	},
-	drag: function(event, ui) {
-
-		var inst = $(this).data("ui-draggable"), that = this;
-
-		$.each(inst.sortables, function() {
-
-			var innermostIntersecting = false,
-				thisSortable = this;
-
-			//Copy over some variables to allow calling the sortable's native _intersectsWith
-			this.instance.positionAbs = inst.positionAbs;
-			this.instance.helperProportions = inst.helperProportions;
-			this.instance.offset.click = inst.offset.click;
-
-			if(this.instance._intersectsWith(this.instance.containerCache)) {
-				innermostIntersecting = true;
-				$.each(inst.sortables, function () {
-					this.instance.positionAbs = inst.positionAbs;
-					this.instance.helperProportions = inst.helperProportions;
-					this.instance.offset.click = inst.offset.click;
-					if (this !== thisSortable &&
-						this.instance._intersectsWith(this.instance.containerCache) &&
-						$.contains(thisSortable.instance.element[0], this.instance.element[0])
-					) {
-						innermostIntersecting = false;
-					}
-					return innermostIntersecting;
-				});
-			}
-
-
-			if(innermostIntersecting) {
-				//If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
-				if(!this.instance.isOver) {
-
-					this.instance.isOver = 1;
-					//Now we fake the start of dragging for the sortable instance,
-					//by cloning the list group item, appending it to the sortable and using it as inst.currentItem
-					//We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
-					this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true);
-					this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
-					this.instance.options.helper = function() { return ui.helper[0]; };
-
-					event.target = this.instance.currentItem[0];
-					this.instance._mouseCapture(event, true);
-					this.instance._mouseStart(event, true, true);
-
-					//Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
-					this.instance.offset.click.top = inst.offset.click.top;
-					this.instance.offset.click.left = inst.offset.click.left;
-					this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
-					this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
-
-					inst._trigger("toSortable", event);
-					inst.dropped = this.instance.element; //draggable revert needs that
-					//hack so receive/update callbacks work (mostly)
-					inst.currentItem = inst.element;
-					this.instance.fromOutside = inst;
-
-				}
-
-				//Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
-				if(this.instance.currentItem) {
-					this.instance._mouseDrag(event);
-				}
-
-			} else {
-
-				//If it doesn't intersect with the sortable, and it intersected before,
-				//we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
-				if(this.instance.isOver) {
-
-					this.instance.isOver = 0;
-					this.instance.cancelHelperRemoval = true;
-
-					//Prevent reverting on this forced stop
-					this.instance.options.revert = false;
-
-					// The out event needs to be triggered independently
-					this.instance._trigger("out", event, this.instance._uiHash(this.instance));
-
-					this.instance._mouseStop(event, true);
-					this.instance.options.helper = this.instance.options._helper;
-
-					//Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
-					this.instance.currentItem.remove();
-					if(this.instance.placeholder) {
-						this.instance.placeholder.remove();
-					}
-
-					inst._trigger("fromSortable", event);
-					inst.dropped = false; //draggable revert needs that
-				}
-
-			}
-
-		});
-
-	}
-});
-
-$.ui.plugin.add("draggable", "cursor", {
-	start: function() {
-		var t = $("body"), o = $(this).data("ui-draggable").options;
-		if (t.css("cursor")) {
-			o._cursor = t.css("cursor");
-		}
-		t.css("cursor", o.cursor);
-	},
-	stop: function() {
-		var o = $(this).data("ui-draggable").options;
-		if (o._cursor) {
-			$("body").css("cursor", o._cursor);
-		}
-	}
-});
-
-$.ui.plugin.add("draggable", "opacity", {
-	start: function(event, ui) {
-		var t = $(ui.helper), o = $(this).data("ui-draggable").options;
-		if(t.css("opacity")) {
-			o._opacity = t.css("opacity");
-		}
-		t.css("opacity", o.opacity);
-	},
-	stop: function(event, ui) {
-		var o = $(this).data("ui-draggable").options;
-		if(o._opacity) {
-			$(ui.helper).css("opacity", o._opacity);
-		}
-	}
-});
-
-$.ui.plugin.add("draggable", "scroll", {
-	start: function() {
-		var i = $(this).data("ui-draggable");
-		if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {
-			i.overflowOffset = i.scrollParent.offset();
-		}
-	},
-	drag: function( event ) {
-
-		var i = $(this).data("ui-draggable"), o = i.options, scrolled = false;
-
-		if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {
-
-			if(!o.axis || o.axis !== "x") {
-				if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
-					i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
-				} else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) {
-					i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
-				}
-			}
-
-			if(!o.axis || o.axis !== "y") {
-				if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
-					i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
-				} else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) {
-					i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
-				}
-			}
-
-		} else {
-
-			if(!o.axis || o.axis !== "x") {
-				if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
-					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
-				} else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
-					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
-				}
-			}
-
-			if(!o.axis || o.axis !== "y") {
-				if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
-					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
-				} else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
-					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
-				}
-			}
-
-		}
-
-		if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
-			$.ui.ddmanager.prepareOffsets(i, event);
-		}
-
-	}
-});
-
-$.ui.plugin.add("draggable", "snap", {
-	start: function() {
-
-		var i = $(this).data("ui-draggable"),
-			o = i.options;
-
-		i.snapElements = [];
-
-		$(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() {
-			var $t = $(this),
-				$o = $t.offset();
-			if(this !== i.element[0]) {
-				i.snapElements.push({
-					item: this,
-					width: $t.outerWidth(), height: $t.outerHeight(),
-					top: $o.top, left: $o.left
-				});
-			}
-		});
-
-	},
-	drag: function(event, ui) {
-
-		var ts, bs, ls, rs, l, r, t, b, i, first,
-			inst = $(this).data("ui-draggable"),
-			o = inst.options,
-			d = o.snapTolerance,
-			x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
-			y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
-
-		for (i = inst.snapElements.length - 1; i >= 0; i--){
-
-			l = inst.snapElements[i].left;
-			r = l + inst.snapElements[i].width;
-			t = inst.snapElements[i].top;
-			b = t + inst.snapElements[i].height;
-
-			if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) {
-				if(inst.snapElements[i].snapping) {
-					(inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
-				}
-				inst.snapElements[i].snapping = false;
-				continue;
-			}
-
-			if(o.snapMode !== "inner") {
-				ts = Math.abs(t - y2) <= d;
-				bs = Math.abs(b - y1) <= d;
-				ls = Math.abs(l - x2) <= d;
-				rs = Math.abs(r - x1) <= d;
-				if(ts) {
-					ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
-				}
-				if(bs) {
-					ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
-				}
-				if(ls) {
-					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
-				}
-				if(rs) {
-					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
-				}
-			}
-
-			first = (ts || bs || ls || rs);
-
-			if(o.snapMode !== "outer") {
-				ts = Math.abs(t - y1) <= d;
-				bs = Math.abs(b - y2) <= d;
-				ls = Math.abs(l - x1) <= d;
-				rs = Math.abs(r - x2) <= d;
-				if(ts) {
-					ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
-				}
-				if(bs) {
-					ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
-				}
-				if(ls) {
-					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
-				}
-				if(rs) {
-					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
-				}
-			}
-
-			if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) {
-				(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
-			}
-			inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
-
-		}
-
-	}
-});
-
-$.ui.plugin.add("draggable", "stack", {
-	start: function() {
-		var min,
-			o = this.data("ui-draggable").options,
-			group = $.makeArray($(o.stack)).sort(function(a,b) {
-				return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
-			});
-
-		if (!group.length) { return; }
-
-		min = parseInt($(group[0]).css("zIndex"), 10) || 0;
-		$(group).each(function(i) {
-			$(this).css("zIndex", min + i);
-		});
-		this.css("zIndex", (min + group.length));
-	}
-});
-
-$.ui.plugin.add("draggable", "zIndex", {
-	start: function(event, ui) {
-		var t = $(ui.helper), o = $(this).data("ui-draggable").options;
-		if(t.css("zIndex")) {
-			o._zIndex = t.css("zIndex");
-		}
-		t.css("zIndex", o.zIndex);
-	},
-	stop: function(event, ui) {
-		var o = $(this).data("ui-draggable").options;
-		if(o._zIndex) {
-			$(ui.helper).css("zIndex", o._zIndex);
-		}
-	}
-});
-
-})(jQuery);
-(function( $, undefined ) {
-
-$.widget( "ui.menu", {
-	version: "1.10.4",
-	defaultElement: "<ul>",
-	delay: 300,
-	options: {
-		icons: {
-			submenu: "ui-icon-carat-1-e"
-		},
-		menus: "ul",
-		position: {
-			my: "left top",
-			at: "right top"
-		},
-		role: "menu",
-
-		// callbacks
-		blur: null,
-		focus: null,
-		select: null
-	},
-
-	_create: function() {
-		this.activeMenu = this.element;
-		// flag used to prevent firing of the click handler
-		// as the event bubbles up through nested menus
-		this.mouseHandled = false;
-		this.element
-			.uniqueId()
-			.addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
-			.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length )
-			.attr({
-				role: this.options.role,
-				tabIndex: 0
-			})
-			// need to catch all clicks on disabled menu
-			// not possible through _on
-			.bind( "click" + this.eventNamespace, $.proxy(function( event ) {
-				if ( this.options.disabled ) {
-					event.preventDefault();
-				}
-			}, this ));
-
-		if ( this.options.disabled ) {
-			this.element
-				.addClass( "ui-state-disabled" )
-				.attr( "aria-disabled", "true" );
-		}
-
-		this._on({
-			// Prevent focus from sticking to links inside menu after clicking
-			// them (focus should always stay on UL during navigation).
-			"mousedown .ui-menu-item > a": function( event ) {
-				event.preventDefault();
-			},
-			"click .ui-state-disabled > a": function( event ) {
-				event.preventDefault();
-			},
-			"click .ui-menu-item:has(a)": function( event ) {
-				var target = $( event.target ).closest( ".ui-menu-item" );
-				if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
-					this.select( event );
-
-					// Only set the mouseHandled flag if the event will bubble, see #9469.
-					if ( !event.isPropagationStopped() ) {
-						this.mouseHandled = true;
-					}
-
-					// Open submenu on click
-					if ( target.has( ".ui-menu" ).length ) {
-						this.expand( event );
-					} else if ( !this.element.is( ":focus" ) && $( this.document[ 0 ].activeElement ).closest( ".ui-menu" ).length ) {
-
-						// Redirect focus to the menu
-						this.element.trigger( "focus", [ true ] );
-
-						// If the active item is on the top level, let it stay active.
-						// Otherwise, blur the active item since it is no longer visible.
-						if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
-							clearTimeout( this.timer );
-						}
-					}
-				}
-			},
-			"mouseenter .ui-menu-item": function( event ) {
-				var target = $( event.currentTarget );
-				// Remove ui-state-active class from siblings of the newly focused menu item
-				// to avoid a jump caused by adjacent elements both having a class with a border
-				target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" );
-				this.focus( event, target );
-			},
-			mouseleave: "collapseAll",
-			"mouseleave .ui-menu": "collapseAll",
-			focus: function( event, keepActiveItem ) {
-				// If there's already an active item, keep it active
-				// If not, activate the first item
-				var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 );
-
-				if ( !keepActiveItem ) {
-					this.focus( event, item );
-				}
-			},
-			blur: function( event ) {
-				this._delay(function() {
-					if ( !$.contains( this.element[0], this.document[0].activeElement ) ) {
-						this.collapseAll( event );
-					}
-				});
-			},
-			keydown: "_keydown"
-		});
-
-		this.refresh();
-
-		// Clicks outside of a menu collapse any open menus
-		this._on( this.document, {
-			click: function( event ) {
-				if ( !$( event.target ).closest( ".ui-menu" ).length ) {
-					this.collapseAll( event );
-				}
-
-				// Reset the mouseHandled flag
-				this.mouseHandled = false;
-			}
-		});
-	},
-
-	_destroy: function() {
-		// Destroy (sub)menus
-		this.element
-			.removeAttr( "aria-activedescendant" )
-			.find( ".ui-menu" ).addBack()
-				.removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" )
-				.removeAttr( "role" )
-				.removeAttr( "tabIndex" )
-				.removeAttr( "aria-labelledby" )
-				.removeAttr( "aria-expanded" )
-				.removeAttr( "aria-hidden" )
-				.removeAttr( "aria-disabled" )
-				.removeUniqueId()
-				.show();
-
-		// Destroy menu items
-		this.element.find( ".ui-menu-item" )
-			.removeClass( "ui-menu-item" )
-			.removeAttr( "role" )
-			.removeAttr( "aria-disabled" )
-			.children( "a" )
-				.removeUniqueId()
-				.removeClass( "ui-corner-all ui-state-hover" )
-				.removeAttr( "tabIndex" )
-				.removeAttr( "role" )
-				.removeAttr( "aria-haspopup" )
-				.children().each( function() {
-					var elem = $( this );
-					if ( elem.data( "ui-menu-submenu-carat" ) ) {
-						elem.remove();
-					}
-				});
-
-		// Destroy menu dividers
-		this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" );
-	},
-
-	_keydown: function( event ) {
-		var match, prev, character, skip, regex,
-			preventDefault = true;
-
-		function escape( value ) {
-			return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
-		}
-
-		switch ( event.keyCode ) {
-		case $.ui.keyCode.PAGE_UP:
-			this.previousPage( event );
-			break;
-		case $.ui.keyCode.PAGE_DOWN:
-			this.nextPage( event );
-			break;
-		case $.ui.keyCode.HOME:
-			this._move( "first", "first", event );
-			break;
-		case $.ui.keyCode.END:
-			this._move( "last", "last", event );
-			break;
-		case $.ui.keyCode.UP:
-			this.previous( event );
-			break;
-		case $.ui.keyCode.DOWN:
-			this.next( event );
-			break;
-		case $.ui.keyCode.LEFT:
-			this.collapse( event );
-			break;
-		case $.ui.keyCode.RIGHT:
-			if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
-				this.expand( event );
-			}
-			break;
-		case $.ui.keyCode.ENTER:
-		case $.ui.keyCode.SPACE:
-			this._activate( event );
-			break;
-		case $.ui.keyCode.ESCAPE:
-			this.collapse( event );
-			break;
-		default:
-			preventDefault = false;
-			prev = this.previousFilter || "";
-			character = String.fromCharCode( event.keyCode );
-			skip = false;
-
-			clearTimeout( this.filterTimer );
-
-			if ( character === prev ) {
-				skip = true;
-			} else {
-				character = prev + character;
-			}
-
-			regex = new RegExp( "^" + escape( character ), "i" );
-			match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
-				return regex.test( $( this ).children( "a" ).text() );
-			});
-			match = skip && match.index( this.active.next() ) !== -1 ?
-				this.active.nextAll( ".ui-menu-item" ) :
-				match;
-
-			// If no matches on the current filter, reset to the last character pressed
-			// to move down the menu to the first item that starts with that character
-			if ( !match.length ) {
-				character = String.fromCharCode( event.keyCode );
-				regex = new RegExp( "^" + escape( character ), "i" );
-				match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
-					return regex.test( $( this ).children( "a" ).text() );
-				});
-			}
-
-			if ( match.length ) {
-				this.focus( event, match );
-				if ( match.length > 1 ) {
-					this.previousFilter = character;
-					this.filterTimer = this._delay(function() {
-						delete this.previousFilter;
-					}, 1000 );
-				} else {
-					delete this.previousFilter;
-				}
-			} else {
-				delete this.previousFilter;
-			}
-		}
-
-		if ( preventDefault ) {
-			event.preventDefault();
-		}
-	},
-
-	_activate: function( event ) {
-		if ( !this.active.is( ".ui-state-disabled" ) ) {
-			if ( this.active.children( "a[aria-haspopup='true']" ).length ) {
-				this.expand( event );
-			} else {
-				this.select( event );
-			}
-		}
-	},
-
-	refresh: function() {
-		var menus,
-			icon = this.options.icons.submenu,
-			submenus = this.element.find( this.options.menus );
-
-		this.element.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length );
-
-		// Initialize nested menus
-		submenus.filter( ":not(.ui-menu)" )
-			.addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
-			.hide()
-			.attr({
-				role: this.options.role,
-				"aria-hidden": "true",
-				"aria-expanded": "false"
-			})
-			.each(function() {
-				var menu = $( this ),
-					item = menu.prev( "a" ),
-					submenuCarat = $( "<span>" )
-						.addClass( "ui-menu-icon ui-icon " + icon )
-						.data( "ui-menu-submenu-carat", true );
-
-				item
-					.attr( "aria-haspopup", "true" )
-					.prepend( submenuCarat );
-				menu.attr( "aria-labelledby", item.attr( "id" ) );
-			});
-
-		menus = submenus.add( this.element );
-
-		// Don't refresh list items that are already adapted
-		menus.children( ":not(.ui-menu-item):has(a)" )
-			.addClass( "ui-menu-item" )
-			.attr( "role", "presentation" )
-			.children( "a" )
-				.uniqueId()
-				.addClass( "ui-corner-all" )
-				.attr({
-					tabIndex: -1,
-					role: this._itemRole()
-				});
-
-		// Initialize unlinked menu-items containing spaces and/or dashes only as dividers
-		menus.children( ":not(.ui-menu-item)" ).each(function() {
-			var item = $( this );
-			// hyphen, em dash, en dash
-			if ( !/[^\-\u2014\u2013\s]/.test( item.text() ) ) {
-				item.addClass( "ui-widget-content ui-menu-divider" );
-			}
-		});
-
-		// Add aria-disabled attribute to any disabled menu item
-		menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
-
-		// If the active item has been removed, blur the menu
-		if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
-			this.blur();
-		}
-	},
-
-	_itemRole: function() {
-		return {
-			menu: "menuitem",
-			listbox: "option"
-		}[ this.options.role ];
-	},
-
-	_setOption: function( key, value ) {
-		if ( key === "icons" ) {
-			this.element.find( ".ui-menu-icon" )
-				.removeClass( this.options.icons.submenu )
-				.addClass( value.submenu );
-		}
-		this._super( key, value );
-	},
-
-	focus: function( event, item ) {
-		var nested, focused;
-		this.blur( event, event && event.type === "focus" );
-
-		this._scrollIntoView( item );
-
-		this.active = item.first();
-		focused = this.active.children( "a" ).addClass( "ui-state-focus" );
-		// Only update aria-activedescendant if there's a role
-		// otherwise we assume focus is managed elsewhere
-		if ( this.options.role ) {
-			this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
-		}
-
-		// Highlight active parent menu item, if any
-		this.active
-			.parent()
-			.closest( ".ui-menu-item" )
-			.children( "a:first" )
-			.addClass( "ui-state-active" );
-
-		if ( event && event.type === "keydown" ) {
-			this._close();
-		} else {
-			this.timer = this._delay(function() {
-				this._close();
-			}, this.delay );
-		}
-
-		nested = item.children( ".ui-menu" );
-		if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) {
-			this._startOpening(nested);
-		}
-		this.activeMenu = item.parent();
-
-		this._trigger( "focus", event, { item: item } );
-	},
-
-	_scrollIntoView: function( item ) {
-		var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
-		if ( this._hasScroll() ) {
-			borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0;
-			paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0;
-			offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
-			scroll = this.activeMenu.scrollTop();
-			elementHeight = this.activeMenu.height();
-			itemHeight = item.height();
-
-			if ( offset < 0 ) {
-				this.activeMenu.scrollTop( scroll + offset );
-			} else if ( offset + itemHeight > elementHeight ) {
-				this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
-			}
-		}
-	},
-
-	blur: function( event, fromFocus ) {
-		if ( !fromFocus ) {
-			clearTimeout( this.timer );
-		}
-
-		if ( !this.active ) {
-			return;
-		}
-
-		this.active.children( "a" ).removeClass( "ui-state-focus" );
-		this.active = null;
-
-		this._trigger( "blur", event, { item: this.active } );
-	},
-
-	_startOpening: function( submenu ) {
-		clearTimeout( this.timer );
-
-		// Don't open if already open fixes a Firefox bug that caused a .5 pixel
-		// shift in the submenu position when mousing over the carat icon
-		if ( submenu.attr( "aria-hidden" ) !== "true" ) {
-			return;
-		}
-
-		this.timer = this._delay(function() {
-			this._close();
-			this._open( submenu );
-		}, this.delay );
-	},
-
-	_open: function( submenu ) {
-		var position = $.extend({
-			of: this.active
-		}, this.options.position );
-
-		clearTimeout( this.timer );
-		this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
-			.hide()
-			.attr( "aria-hidden", "true" );
-
-		submenu
-			.show()
-			.removeAttr( "aria-hidden" )
-			.attr( "aria-expanded", "true" )
-			.position( position );
-	},
-
-	collapseAll: function( event, all ) {
-		clearTimeout( this.timer );
-		this.timer = this._delay(function() {
-			// If we were passed an event, look for the submenu that contains the event
-			var currentMenu = all ? this.element :
-				$( event && event.target ).closest( this.element.find( ".ui-menu" ) );
-
-			// If we found no valid submenu ancestor, use the main menu to close all sub menus anyway
-			if ( !currentMenu.length ) {
-				currentMenu = this.element;
-			}
-
-			this._close( currentMenu );
-
-			this.blur( event );
-			this.activeMenu = currentMenu;
-		}, this.delay );
-	},
-
-	// With no arguments, closes the currently active menu - if nothing is active
-	// it closes all menus.  If passed an argument, it will search for menus BELOW
-	_close: function( startMenu ) {
-		if ( !startMenu ) {
-			startMenu = this.active ? this.active.parent() : this.element;
-		}
-
-		startMenu
-			.find( ".ui-menu" )
-				.hide()
-				.attr( "aria-hidden", "true" )
-				.attr( "aria-expanded", "false" )
-			.end()
-			.find( "a.ui-state-active" )
-				.removeClass( "ui-state-active" );
-	},
-
-	collapse: function( event ) {
-		var newItem = this.active &&
-			this.active.parent().closest( ".ui-menu-item", this.element );
-		if ( newItem && newItem.length ) {
-			this._close();
-			this.focus( event, newItem );
-		}
-	},
-
-	expand: function( event ) {
-		var newItem = this.active &&
-			this.active
-				.children( ".ui-menu " )
-				.children( ".ui-menu-item" )
-				.first();
-
-		if ( newItem && newItem.length ) {
-			this._open( newItem.parent() );
-
-			// Delay so Firefox will not hide activedescendant change in expanding submenu from AT
-			this._delay(function() {
-				this.focus( event, newItem );
-			});
-		}
-	},
-
-	next: function( event ) {
-		this._move( "next", "first", event );
-	},
-
-	previous: function( event ) {
-		this._move( "prev", "last", event );
-	},
-
-	isFirstItem: function() {
-		return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
-	},
-
-	isLastItem: function() {
-		return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
-	},
-
-	_move: function( direction, filter, event ) {
-		var next;
-		if ( this.active ) {
-			if ( direction === "first" || direction === "last" ) {
-				next = this.active
-					[ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
-					.eq( -1 );
-			} else {
-				next = this.active
-					[ direction + "All" ]( ".ui-menu-item" )
-					.eq( 0 );
-			}
-		}
-		if ( !next || !next.length || !this.active ) {
-			next = this.activeMenu.children( ".ui-menu-item" )[ filter ]();
-		}
-
-		this.focus( event, next );
-	},
-
-	nextPage: function( event ) {
-		var item, base, height;
-
-		if ( !this.active ) {
-			this.next( event );
-			return;
-		}
-		if ( this.isLastItem() ) {
-			return;
-		}
-		if ( this._hasScroll() ) {
-			base = this.active.offset().top;
-			height = this.element.height();
-			this.active.nextAll( ".ui-menu-item" ).each(function() {
-				item = $( this );
-				return item.offset().top - base - height < 0;
-			});
-
-			this.focus( event, item );
-		} else {
-			this.focus( event, this.activeMenu.children( ".ui-menu-item" )
-				[ !this.active ? "first" : "last" ]() );
-		}
-	},
-
-	previousPage: function( event ) {
-		var item, base, height;
-		if ( !this.active ) {
-			this.next( event );
-			return;
-		}
-		if ( this.isFirstItem() ) {
-			return;
-		}
-		if ( this._hasScroll() ) {
-			base = this.active.offset().top;
-			height = this.element.height();
-			this.active.prevAll( ".ui-menu-item" ).each(function() {
-				item = $( this );
-				return item.offset().top - base + height > 0;
-			});
-
-			this.focus( event, item );
-		} else {
-			this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() );
-		}
-	},
-
-	_hasScroll: function() {
-		return this.element.outerHeight() < this.element.prop( "scrollHeight" );
-	},
-
-	select: function( event ) {
-		// TODO: It should never be possible to not have an active item at this
-		// point, but the tests don't trigger mouseenter before click.
-		this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
-		var ui = { item: this.active };
-		if ( !this.active.has( ".ui-menu" ).length ) {
-			this.collapseAll( event, true );
-		}
-		this._trigger( "select", event, ui );
-	}
-});
-
-}( jQuery ));
+(function(e,t){function i(t,i){var s,a,o,r=t.nodeName.toLowerCase();return"area"===r?(s=t.parentNode,a=s.name,t.href&&a&&"map"===s.nodeName.toLowerCase()?(o=e("img[usemap=#"+a+"]")[0],!!o&&n(o)):!1):(/input|select|textarea|button|object/.test(r)?!t.disabled:"a"===r?t.href||i:i)&&n(t)}function n(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}var s=0,a=/^ui-id-\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:"1.10.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,n){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),n&&n.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css("zIndex",i);if(this.length)for(var n,s,a=e(this[0]);a.length&&a[0]!==document;){if(n=a.css("position"),("absolute"===n||"relative"===n||"fixed"===n)&&(s=parseInt(a.css("zIndex"),10),!isNaN(s)&&0!==s))return s;a=a.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++s)})},removeUniqueId:function(){return this.each(function(){a.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,n){return!!e.data(t,n[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),s=isNaN(n);return(s||n>=0)&&i(t,!s)}}),e("<a>").outerWidth(1).jquery||e.each(["Width","Height"],function(i,n){function s(t,i,n,s){return e.each(a,function(){i-=parseFloat(e.css(t,"padding"+this))||0,n&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var a="Width"===n?["Left","Right"]:["Top","Bottom"],o=n.toLowerCase(),r={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+n]=function(i){return i===t?r["inner"+n].call(this):this.each(function(){e(this).css(o,s(this,i)+"px")})},e.fn["outer"+n]=function(t,i){return"number"!=typeof t?r["outer"+n].call(this,t):this.each(function(){e(this).css(o,s(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("<a>").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,n){var s,a=e.ui[t].prototype;for(s in n)a.plugins[s]=a.plugins[s]||[],a.plugins[s].push([i,n[s]])},call:function(e,t,i){var n,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(n=0;s.length>n;n++)e.options[s[n][0]]&&s[n][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var n=i&&"left"===i?"scrollLeft":"scrollTop",s=!1;return t[n]>0?!0:(t[n]=1,s=t[n]>0,t[n]=0,s)}})})(jQuery);(function(t,e){var i=0,s=Array.prototype.slice,n=t.cleanData;t.cleanData=function(e){for(var i,s=0;null!=(i=e[s]);s++)try{t(i).triggerHandler("remove")}catch(o){}n(e)},t.widget=function(i,s,n){var o,a,r,h,l={},c=i.split(".")[0];i=i.split(".")[1],o=c+"-"+i,n||(n=s,s=t.Widget),t.expr[":"][o.toLowerCase()]=function(e){return!!t.data(e,o)},t[c]=t[c]||{},a=t[c][i],r=t[c][i]=function(t,i){return this._createWidget?(arguments.length&&this._createWidget(t,i),e):new r(t,i)},t.extend(r,a,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),h=new s,h.options=t.widget.extend({},h.options),t.each(n,function(i,n){return t.isFunction(n)?(l[i]=function(){var t=function(){return s.prototype[i].apply(this,arguments)},e=function(t){return s.prototype[i].apply(this,t)};return function(){var i,s=this._super,o=this._superApply;return this._super=t,this._superApply=e,i=n.apply(this,arguments),this._super=s,this._superApply=o,i}}(),e):(l[i]=n,e)}),r.prototype=t.widget.extend(h,{widgetEventPrefix:a?h.widgetEventPrefix||i:i},l,{constructor:r,namespace:c,widgetName:i,widgetFullName:o}),a?(t.each(a._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,r,i._proto)}),delete a._childConstructors):s._childConstructors.push(r),t.widget.bridge(i,r)},t.widget.extend=function(i){for(var n,o,a=s.call(arguments,1),r=0,h=a.length;h>r;r++)for(n in a[r])o=a[r][n],a[r].hasOwnProperty(n)&&o!==e&&(i[n]=t.isPlainObject(o)?t.isPlainObject(i[n])?t.widget.extend({},i[n],o):t.widget.extend({},o):o);return i},t.widget.bridge=function(i,n){var o=n.prototype.widgetFullName||i;t.fn[i]=function(a){var r="string"==typeof a,h=s.call(arguments,1),l=this;return a=!r&&h.length?t.widget.extend.apply(null,[a].concat(h)):a,r?this.each(function(){var s,n=t.data(this,o);return n?t.isFunction(n[a])&&"_"!==a.charAt(0)?(s=n[a].apply(n,h),s!==n&&s!==e?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):e):t.error("no such method '"+a+"' for "+i+" widget instance"):t.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+a+"'")}):this.each(function(){var e=t.data(this,o);e?e.option(a||{})._init():t.data(this,o,new n(a,this))}),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{disabled:!1,create:null},_createWidget:function(e,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(i,s){var n,o,a,r=i;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof i)if(r={},n=i.split("."),i=n.shift(),n.length){for(o=r[i]=t.widget.extend({},this.options[i]),a=0;n.length-1>a;a++)o[n[a]]=o[n[a]]||{},o=o[n[a]];if(i=n.pop(),1===arguments.length)return o[i]===e?null:o[i];o[i]=s}else{if(1===arguments.length)return this.options[i]===e?null:this.options[i];r[i]=s}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,"disabled"===t&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!e).attr("aria-disabled",e),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var o,a=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=o=t(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,o=this.widget()),t.each(n,function(n,r){function h(){return i||a.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof r?a[r]:r).apply(a,arguments):e}"string"!=typeof r&&(h.guid=r.guid=r.guid||h.guid||t.guid++);var l=n.match(/^(\w+)\s*(.*)$/),c=l[1]+a.eventNamespace,u=l[2];u?o.delegate(u,c,h):s.bind(c,h)})},_off:function(t,e){e=(e||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass("ui-state-hover")},mouseleave:function(e){t(e.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass("ui-state-focus")},focusout:function(e){t(e.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}})})(jQuery);(function(t){var e=!1;t(document).mouseup(function(){e=!1}),t.widget("ui.mouse",{version:"1.10.4",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.bind("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).bind("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(i){if(!e){this._mouseStarted&&this._mouseUp(i),this._mouseDownEvent=i;var s=this,n=1===i.which,a="string"==typeof this.options.cancel&&i.target.nodeName?t(i.target).closest(this.options.cancel).length:!1;return n&&!a&&this._mouseCapture(i)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){s.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(i)&&this._mouseDelayMet(i)&&(this._mouseStarted=this._mouseStart(i)!==!1,!this._mouseStarted)?(i.preventDefault(),!0):(!0===t.data(i.target,this.widgetName+".preventClickEvent")&&t.removeData(i.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return s._mouseMove(t)},this._mouseUpDelegate=function(t){return s._mouseUp(t)},t(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),i.preventDefault(),e=!0,!0)):!0}},_mouseMove:function(e){return t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button?this._mouseUp(e):this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){return t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),!1},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(t,e){function i(t,e,i){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?i/100:1)]}function s(e,i){return parseInt(t.css(e,i),10)||0}function n(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}t.ui=t.ui||{};var a,o=Math.max,r=Math.abs,l=Math.round,h=/left|center|right/,c=/top|center|bottom/,u=/[\+\-]\d+(\.[\d]+)?%?/,d=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(a!==e)return a;var i,s,n=t("<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),o=n.children()[0];return t("body").append(n),i=o.offsetWidth,n.css("overflow","scroll"),s=o.offsetWidth,i===s&&(s=n[0].clientWidth),n.remove(),a=i-s},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.width<e.element[0].scrollWidth,a="scroll"===s||"auto"===s&&e.height<e.element[0].scrollHeight;return{width:a?t.position.scrollbarWidth():0,height:n?t.position.scrollbarWidth():0}},getWithinInfo:function(e){var i=t(e||window),s=t.isWindow(i[0]),n=!!i[0]&&9===i[0].nodeType;return{element:i,isWindow:s,isDocument:n,offset:i.offset()||{left:0,top:0},scrollLeft:i.scrollLeft(),scrollTop:i.scrollTop(),width:s?i.width():i.outerWidth(),height:s?i.height():i.outerHeight()}}},t.fn.position=function(e){if(!e||!e.of)return f.apply(this,arguments);e=t.extend({},e);var a,p,g,m,v,_,b=t(e.of),y=t.position.getWithinInfo(e.within),k=t.position.getScrollInfo(y),w=(e.collision||"flip").split(" "),D={};return _=n(b),b[0].preventDefault&&(e.at="left top"),p=_.width,g=_.height,m=_.offset,v=t.extend({},m),t.each(["my","at"],function(){var t,i,s=(e[this]||"").split(" ");1===s.length&&(s=h.test(s[0])?s.concat(["center"]):c.test(s[0])?["center"].concat(s):["center","center"]),s[0]=h.test(s[0])?s[0]:"center",s[1]=c.test(s[1])?s[1]:"center",t=u.exec(s[0]),i=u.exec(s[1]),D[this]=[t?t[0]:0,i?i[0]:0],e[this]=[d.exec(s[0])[0],d.exec(s[1])[0]]}),1===w.length&&(w[1]=w[0]),"right"===e.at[0]?v.left+=p:"center"===e.at[0]&&(v.left+=p/2),"bottom"===e.at[1]?v.top+=g:"center"===e.at[1]&&(v.top+=g/2),a=i(D.at,p,g),v.left+=a[0],v.top+=a[1],this.each(function(){var n,h,c=t(this),u=c.outerWidth(),d=c.outerHeight(),f=s(this,"marginLeft"),_=s(this,"marginTop"),x=u+f+s(this,"marginRight")+k.width,C=d+_+s(this,"marginBottom")+k.height,M=t.extend({},v),T=i(D.my,c.outerWidth(),c.outerHeight());"right"===e.my[0]?M.left-=u:"center"===e.my[0]&&(M.left-=u/2),"bottom"===e.my[1]?M.top-=d:"center"===e.my[1]&&(M.top-=d/2),M.left+=T[0],M.top+=T[1],t.support.offsetFractions||(M.left=l(M.left),M.top=l(M.top)),n={marginLeft:f,marginTop:_},t.each(["left","top"],function(i,s){t.ui.position[w[i]]&&t.ui.position[w[i]][s](M,{targetWidth:p,targetHeight:g,elemWidth:u,elemHeight:d,collisionPosition:n,collisionWidth:x,collisionHeight:C,offset:[a[0]+T[0],a[1]+T[1]],my:e.my,at:e.at,within:y,elem:c})}),e.using&&(h=function(t){var i=m.left-M.left,s=i+p-u,n=m.top-M.top,a=n+g-d,l={target:{element:b,left:m.left,top:m.top,width:p,height:g},element:{element:c,left:M.left,top:M.top,width:u,height:d},horizontal:0>s?"left":i>0?"right":"center",vertical:0>a?"top":n>0?"bottom":"middle"};u>p&&p>r(i+s)&&(l.horizontal="center"),d>g&&g>r(n+a)&&(l.vertical="middle"),l.important=o(r(i),r(s))>o(r(n),r(a))?"horizontal":"vertical",e.using.call(this,t,l)}),c.offset(t.extend(M,{using:h}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,l=n-r,h=r+e.collisionWidth-a-n;e.collisionWidth>a?l>0&&0>=h?(i=t.left+l+e.collisionWidth-a-n,t.left+=l-i):t.left=h>0&&0>=l?n:l>h?n+a-e.collisionWidth:n:l>0?t.left+=l:h>0?t.left-=h:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,l=n-r,h=r+e.collisionHeight-a-n;e.collisionHeight>a?l>0&&0>=h?(i=t.top+l+e.collisionHeight-a-n,t.top+=l-i):t.top=h>0&&0>=l?n:l>h?n+a-e.collisionHeight:n:l>0?t.top+=l:h>0?t.top-=h:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,a=n.offset.left+n.scrollLeft,o=n.width,l=n.isWindow?n.scrollLeft:n.offset.left,h=t.left-e.collisionPosition.marginLeft,c=h-l,u=h+e.collisionWidth-o-l,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-o-a,(0>i||r(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-l,(s>0||u>r(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,a=n.offset.top+n.scrollTop,o=n.height,l=n.isWindow?n.scrollTop:n.offset.top,h=t.top-e.collisionPosition.marginTop,c=h-l,u=h+e.collisionHeight-o-l,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-o-a,t.top+p+f+g>c&&(0>s||r(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-l,t.top+p+f+g>u&&(i>0||u>r(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}},function(){var e,i,s,n,a,o=document.getElementsByTagName("body")[0],r=document.createElement("div");e=document.createElement(o?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},o&&t.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(a in s)e.style[a]=s[a];e.appendChild(r),i=o||document.documentElement,i.insertBefore(e,i.firstChild),r.style.cssText="position: absolute; left: 10.7432222px;",n=t(r).offset().left,t.support.offsetFractions=n>10&&11>n,e.innerHTML="",i.removeChild(e)}()})(jQuery);(function(t){t.widget("ui.draggable",t.ui.mouse,{version:"1.10.4",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"!==this.options.helper||/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy()},_mouseCapture:function(e){var i=this.options;return this.helper||i.disabled||t(e.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(e),this.handle?(t(i.iframeFix===!0?"iframe":i.iframeFix).each(function(){t("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>").css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(t(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(e){var i=this.options;return this.helper=this._createHelper(e),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),t.ui.ddmanager&&(t.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offsetParent=this.helper.offsetParent(),this.offsetParentCssPosition=this.offsetParent.css("position"),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},this.offset.scroll=!1,t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),this._trigger("start",e)===!1?(this._clear(),!1):(this._cacheHelperProportions(),t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this._mouseDrag(e,!0),t.ui.ddmanager&&t.ui.ddmanager.dragStart(this,e),!0)},_mouseDrag:function(e,i){if("fixed"===this.offsetParentCssPosition&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",e,s)===!1)return this._mouseUp({}),!1;this.position=s.position}return this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),!1},_mouseStop:function(e){var i=this,s=!1;return t.ui.ddmanager&&!this.options.dropBehaviour&&(s=t.ui.ddmanager.drop(this,e)),this.dropped&&(s=this.dropped,this.dropped=!1),"original"!==this.options.helper||t.contains(this.element[0].ownerDocument,this.element[0])?("invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||this.options.revert===!0||t.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?t(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){i._trigger("stop",e)!==!1&&i._clear()}):this._trigger("stop",e)!==!1&&this._clear(),!1):!1},_mouseUp:function(e){return t("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),t.ui.ddmanager&&t.ui.ddmanager.dragStop(this,e),t.ui.mouse.prototype._mouseUp.call(this,e)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(e){return this.options.handle?!!t(e.target).closest(this.element.find(this.options.handle)).length:!0},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return s.parents("body").length||s.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s[0]===this.element[0]||/(fixed|absolute)/.test(s.css("position"))||s.css("position","absolute"),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.element.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;return n.containment?"window"===n.containment?(this.containment=[t(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,t(window).scrollLeft()+t(window).width()-this.helperProportions.width-this.margins.left,t(window).scrollTop()+(t(window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],undefined):"document"===n.containment?(this.containment=[0,0,t(document).width()-this.helperProportions.width-this.margins.left,(t(document).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],undefined):n.containment.constructor===Array?(this.containment=n.containment,undefined):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=t(n.containment),s=i[0],s&&(e="hidden"!==i.css("overflow"),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(e?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=i),undefined):(this.containment=null,undefined)},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent;return this.offset.scroll||(this.offset.scroll={top:n.scrollTop(),left:n.scrollLeft()}),{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():this.offset.scroll.top)*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():this.offset.scroll.left)*s}},_generatePosition:function(e){var i,s,n,a,o=this.options,r="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,l=e.pageX,h=e.pageY;return this.offset.scroll||(this.offset.scroll={top:r.scrollTop(),left:r.scrollLeft()}),this.originalPosition&&(this.containment&&(this.relative_container?(s=this.relative_container.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,e.pageX-this.offset.click.left<i[0]&&(l=i[0]+this.offset.click.left),e.pageY-this.offset.click.top<i[1]&&(h=i[1]+this.offset.click.top),e.pageX-this.offset.click.left>i[2]&&(l=i[2]+this.offset.click.left),e.pageY-this.offset.click.top>i[3]&&(h=i[3]+this.offset.click.top)),o.grid&&(n=o.grid[1]?this.originalPageY+Math.round((h-this.originalPageY)/o.grid[1])*o.grid[1]:this.originalPageY,h=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-o.grid[1]:n+o.grid[1]:n,a=o.grid[0]?this.originalPageX+Math.round((l-this.originalPageX)/o.grid[0])*o.grid[0]:this.originalPageX,l=i?a-this.offset.click.left>=i[0]||a-this.offset.click.left>i[2]?a:a-this.offset.click.left>=i[0]?a-o.grid[0]:a+o.grid[0]:a)),{top:h-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():this.offset.scroll.top),left:l-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():this.offset.scroll.left)}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1},_trigger:function(e,i,s){return s=s||this._uiHash(),t.ui.plugin.call(this,e,[i,s]),"drag"===e&&(this.positionAbs=this._convertPositionTo("absolute")),t.Widget.prototype._trigger.call(this,e,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),t.ui.plugin.add("draggable","connectToSortable",{start:function(e,i){var s=t(this).data("ui-draggable"),n=s.options,a=t.extend({},i,{item:s.element});s.sortables=[],t(n.connectToSortable).each(function(){var i=t.data(this,"ui-sortable");i&&!i.options.disabled&&(s.sortables.push({instance:i,shouldRevert:i.options.revert}),i.refreshPositions(),i._trigger("activate",e,a))})},stop:function(e,i){var s=t(this).data("ui-draggable"),n=t.extend({},i,{item:s.element});t.each(s.sortables,function(){this.instance.isOver?(this.instance.isOver=0,s.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=this.shouldRevert),this.instance._mouseStop(e),this.instance.options.helper=this.instance.options._helper,"original"===s.options.helper&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",e,n))})},drag:function(e,i){var s=t(this).data("ui-draggable"),n=this;t.each(s.sortables,function(){var a=!1,o=this;this.instance.positionAbs=s.positionAbs,this.instance.helperProportions=s.helperProportions,this.instance.offset.click=s.offset.click,this.instance._intersectsWith(this.instance.containerCache)&&(a=!0,t.each(s.sortables,function(){return this.instance.positionAbs=s.positionAbs,this.instance.helperProportions=s.helperProportions,this.instance.offset.click=s.offset.click,this!==o&&this.instance._intersectsWith(this.instance.containerCache)&&t.contains(o.instance.element[0],this.instance.element[0])&&(a=!1),a})),a?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=t(n).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return i.helper[0]},e.target=this.instance.currentItem[0],this.instance._mouseCapture(e,!0),this.instance._mouseStart(e,!0,!0),this.instance.offset.click.top=s.offset.click.top,this.instance.offset.click.left=s.offset.click.left,this.instance.offset.parent.left-=s.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=s.offset.parent.top-this.instance.offset.parent.top,s._trigger("toSortable",e),s.dropped=this.instance.element,s.currentItem=s.element,this.instance.fromOutside=s),this.instance.currentItem&&this.instance._mouseDrag(e)):this.instance.isOver&&(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",e,this.instance._uiHash(this.instance)),this.instance._mouseStop(e,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),s._trigger("fromSortable",e),s.dropped=!1)})}}),t.ui.plugin.add("draggable","cursor",{start:function(){var e=t("body"),i=t(this).data("ui-draggable").options;e.css("cursor")&&(i._cursor=e.css("cursor")),e.css("cursor",i.cursor)},stop:function(){var e=t(this).data("ui-draggable").options;e._cursor&&t("body").css("cursor",e._cursor)}}),t.ui.plugin.add("draggable","opacity",{start:function(e,i){var s=t(i.helper),n=t(this).data("ui-draggable").options;s.css("opacity")&&(n._opacity=s.css("opacity")),s.css("opacity",n.opacity)},stop:function(e,i){var s=t(this).data("ui-draggable").options;s._opacity&&t(i.helper).css("opacity",s._opacity)}}),t.ui.plugin.add("draggable","scroll",{start:function(){var e=t(this).data("ui-draggable");e.scrollParent[0]!==document&&"HTML"!==e.scrollParent[0].tagName&&(e.overflowOffset=e.scrollParent.offset())},drag:function(e){var i=t(this).data("ui-draggable"),s=i.options,n=!1;i.scrollParent[0]!==document&&"HTML"!==i.scrollParent[0].tagName?(s.axis&&"x"===s.axis||(i.overflowOffset.top+i.scrollParent[0].offsetHeight-e.pageY<s.scrollSensitivity?i.scrollParent[0].scrollTop=n=i.scrollParent[0].scrollTop+s.scrollSpeed:e.pageY-i.overflowOffset.top<s.scrollSensitivity&&(i.scrollParent[0].scrollTop=n=i.scrollParent[0].scrollTop-s.scrollSpeed)),s.axis&&"y"===s.axis||(i.overflowOffset.left+i.scrollParent[0].offsetWidth-e.pageX<s.scrollSensitivity?i.scrollParent[0].scrollLeft=n=i.scrollParent[0].scrollLeft+s.scrollSpeed:e.pageX-i.overflowOffset.left<s.scrollSensitivity&&(i.scrollParent[0].scrollLeft=n=i.scrollParent[0].scrollLeft-s.scrollSpeed))):(s.axis&&"x"===s.axis||(e.pageY-t(document).scrollTop()<s.scrollSensitivity?n=t(document).scrollTop(t(document).scrollTop()-s.scrollSpeed):t(window).height()-(e.pageY-t(document).scrollTop())<s.scrollSensitivity&&(n=t(document).scrollTop(t(document).scrollTop()+s.scrollSpeed))),s.axis&&"y"===s.axis||(e.pageX-t(document).scrollLeft()<s.scrollSensitivity?n=t(document).scrollLeft(t(document).scrollLeft()-s.scrollSpeed):t(window).width()-(e.pageX-t(document).scrollLeft())<s.scrollSensitivity&&(n=t(document).scrollLeft(t(document).scrollLeft()+s.scrollSpeed)))),n!==!1&&t.ui.ddmanager&&!s.dropBehaviour&&t.ui.ddmanager.prepareOffsets(i,e)}}),t.ui.plugin.add("draggable","snap",{start:function(){var e=t(this).data("ui-draggable"),i=e.options;e.snapElements=[],t(i.snap.constructor!==String?i.snap.items||":data(ui-draggable)":i.snap).each(function(){var i=t(this),s=i.offset();this!==e.element[0]&&e.snapElements.push({item:this,width:i.outerWidth(),height:i.outerHeight(),top:s.top,left:s.left})})},drag:function(e,i){var s,n,a,o,r,l,h,c,u,d,p=t(this).data("ui-draggable"),g=p.options,f=g.snapTolerance,m=i.offset.left,_=m+p.helperProportions.width,v=i.offset.top,b=v+p.helperProportions.height;for(u=p.snapElements.length-1;u>=0;u--)r=p.snapElements[u].left,l=r+p.snapElements[u].width,h=p.snapElements[u].top,c=h+p.snapElements[u].height,r-f>_||m>l+f||h-f>b||v>c+f||!t.contains(p.snapElements[u].item.ownerDocument,p.snapElements[u].item)?(p.snapElements[u].snapping&&p.options.snap.release&&p.options.snap.release.call(p.element,e,t.extend(p._uiHash(),{snapItem:p.snapElements[u].item})),p.snapElements[u].snapping=!1):("inner"!==g.snapMode&&(s=f>=Math.abs(h-b),n=f>=Math.abs(c-v),a=f>=Math.abs(r-_),o=f>=Math.abs(l-m),s&&(i.position.top=p._convertPositionTo("relative",{top:h-p.helperProportions.height,left:0}).top-p.margins.top),n&&(i.position.top=p._convertPositionTo("relative",{top:c,left:0}).top-p.margins.top),a&&(i.position.left=p._convertPositionTo("relative",{top:0,left:r-p.helperProportions.width}).left-p.margins.left),o&&(i.position.left=p._convertPositionTo("relative",{top:0,left:l}).left-p.margins.left)),d=s||n||a||o,"outer"!==g.snapMode&&(s=f>=Math.abs(h-v),n=f>=Math.abs(c-b),a=f>=Math.abs(r-m),o=f>=Math.abs(l-_),s&&(i.position.top=p._convertPositionTo("relative",{top:h,left:0}).top-p.margins.top),n&&(i.position.top=p._convertPositionTo("relative",{top:c-p.helperProportions.height,left:0}).top-p.margins.top),a&&(i.position.left=p._convertPositionTo("relative",{top:0,left:r}).left-p.margins.left),o&&(i.position.left=p._convertPositionTo("relative",{top:0,left:l-p.helperProportions.width}).left-p.margins.left)),!p.snapElements[u].snapping&&(s||n||a||o||d)&&p.options.snap.snap&&p.options.snap.snap.call(p.element,e,t.extend(p._uiHash(),{snapItem:p.snapElements[u].item})),p.snapElements[u].snapping=s||n||a||o||d)}}),t.ui.plugin.add("draggable","stack",{start:function(){var e,i=this.data("ui-draggable").options,s=t.makeArray(t(i.stack)).sort(function(e,i){return(parseInt(t(e).css("zIndex"),10)||0)-(parseInt(t(i).css("zIndex"),10)||0)});s.length&&(e=parseInt(t(s[0]).css("zIndex"),10)||0,t(s).each(function(i){t(this).css("zIndex",e+i)}),this.css("zIndex",e+s.length))}}),t.ui.plugin.add("draggable","zIndex",{start:function(e,i){var s=t(i.helper),n=t(this).data("ui-draggable").options;s.css("zIndex")&&(n._zIndex=s.css("zIndex")),s.css("zIndex",n.zIndex)},stop:function(e,i){var s=t(this).data("ui-draggable").options;s._zIndex&&t(i.helper).css("zIndex",s._zIndex)}})})(jQuery);(function(t){function e(t,e,i){return t>e&&e+i>t}t.widget("ui.droppable",{version:"1.10.4",widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var e,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=t.isFunction(s)?s:function(t){return t.is(s)},this.proportions=function(){return arguments.length?(e=arguments[0],undefined):e?e:e={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight}},t.ui.ddmanager.droppables[i.scope]=t.ui.ddmanager.droppables[i.scope]||[],t.ui.ddmanager.droppables[i.scope].push(this),i.addClasses&&this.element.addClass("ui-droppable")},_destroy:function(){for(var e=0,i=t.ui.ddmanager.droppables[this.options.scope];i.length>e;e++)i[e]===this&&i.splice(e,1);this.element.removeClass("ui-droppable ui-droppable-disabled")},_setOption:function(e,i){"accept"===e&&(this.accept=t.isFunction(i)?i:function(t){return t.is(i)}),t.Widget.prototype._setOption.apply(this,arguments)},_activate:function(e){var i=t.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),i&&this._trigger("activate",e,this.ui(i))},_deactivate:function(e){var i=t.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),i&&this._trigger("deactivate",e,this.ui(i))},_over:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",e,this.ui(i)))},_out:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",e,this.ui(i)))},_drop:function(e,i){var s=i||t.ui.ddmanager.current,n=!1;return s&&(s.currentItem||s.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var e=t.data(this,"ui-droppable");return e.options.greedy&&!e.options.disabled&&e.options.scope===s.options.scope&&e.accept.call(e.element[0],s.currentItem||s.element)&&t.ui.intersect(s,t.extend(e,{offset:e.element.offset()}),e.options.tolerance)?(n=!0,!1):undefined}),n?!1:this.accept.call(this.element[0],s.currentItem||s.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",e,this.ui(s)),this.element):!1):!1},ui:function(t){return{draggable:t.currentItem||t.element,helper:t.helper,position:t.position,offset:t.positionAbs}}}),t.ui.intersect=function(t,i,s){if(!i.offset)return!1;var n,a,o=(t.positionAbs||t.position.absolute).left,r=(t.positionAbs||t.position.absolute).top,l=o+t.helperProportions.width,h=r+t.helperProportions.height,c=i.offset.left,u=i.offset.top,d=c+i.proportions().width,p=u+i.proportions().height;switch(s){case"fit":return o>=c&&d>=l&&r>=u&&p>=h;case"intersect":return o+t.helperProportions.width/2>c&&d>l-t.helperProportions.width/2&&r+t.helperProportions.height/2>u&&p>h-t.helperProportions.height/2;case"pointer":return n=(t.positionAbs||t.position.absolute).left+(t.clickOffset||t.offset.click).left,a=(t.positionAbs||t.position.absolute).top+(t.clickOffset||t.offset.click).top,e(a,u,i.proportions().height)&&e(n,c,i.proportions().width);case"touch":return(r>=u&&p>=r||h>=u&&p>=h||u>r&&h>p)&&(o>=c&&d>=o||l>=c&&d>=l||c>o&&l>d);default:return!1}},t.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,i){var s,n,a=t.ui.ddmanager.droppables[e.options.scope]||[],o=i?i.type:null,r=(e.currentItem||e.element).find(":data(ui-droppable)").addBack();t:for(s=0;a.length>s;s++)if(!(a[s].options.disabled||e&&!a[s].accept.call(a[s].element[0],e.currentItem||e.element))){for(n=0;r.length>n;n++)if(r[n]===a[s].element[0]){a[s].proportions().height=0;continue t}a[s].visible="none"!==a[s].element.css("display"),a[s].visible&&("mousedown"===o&&a[s]._activate.call(a[s],i),a[s].offset=a[s].element.offset(),a[s].proportions({width:a[s].element[0].offsetWidth,height:a[s].element[0].offsetHeight}))}},drop:function(e,i){var s=!1;return t.each((t.ui.ddmanager.droppables[e.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&t.ui.intersect(e,this,this.options.tolerance)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],e.currentItem||e.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(e,i){e.element.parentsUntil("body").bind("scroll.droppable",function(){e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)})},drag:function(e,i){e.options.refreshPositions&&t.ui.ddmanager.prepareOffsets(e,i),t.each(t.ui.ddmanager.droppables[e.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,a,o=t.ui.intersect(e,this,this.options.tolerance),r=!o&&this.isover?"isout":o&&!this.isover?"isover":null;r&&(this.options.greedy&&(n=this.options.scope,a=this.element.parents(":data(ui-droppable)").filter(function(){return t.data(this,"ui-droppable").options.scope===n}),a.length&&(s=t.data(a[0],"ui-droppable"),s.greedyChild="isover"===r)),s&&"isover"===r&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[r]=!0,this["isout"===r?"isover":"isout"]=!1,this["isover"===r?"_over":"_out"].call(this,i),s&&"isout"===r&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(e,i){e.element.parentsUntil("body").unbind("scroll.droppable"),e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)}}})(jQuery);(function(t){function e(t,e,i){return t>e&&e+i>t}function i(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))}t.widget("ui.sortable",t.ui.mouse,{version:"1.10.4",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_create:function(){var t=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?"x"===t.axis||i(this.items[0].item):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_setOption:function(e,i){"disabled"===e?(this.options[e]=i,this.widget().toggleClass("ui-sortable-disabled",!!i)):t.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+"-item")===o?(s=t(this),!1):undefined}),t.data(e.target,o.widgetName+"-item")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&"auto"!==a.cursor&&(o=this.document.find("body"),this.storedCursor=o.css("cursor"),o.css("cursor",a.cursor),this.storedStylesheet=t("<style>*{ cursor: "+a.cursor+" !important; }</style>").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY<a.scrollSensitivity?this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop+a.scrollSpeed:e.pageY-this.overflowOffset.top<a.scrollSensitivity&&(this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop-a.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-e.pageX<a.scrollSensitivity?this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft+a.scrollSpeed:e.pageX-this.overflowOffset.left<a.scrollSensitivity&&(this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft-a.scrollSpeed)):(e.pageY-t(document).scrollTop()<a.scrollSensitivity?r=t(document).scrollTop(t(document).scrollTop()-a.scrollSpeed):t(window).height()-(e.pageY-t(document).scrollTop())<a.scrollSensitivity&&(r=t(document).scrollTop(t(document).scrollTop()+a.scrollSpeed)),e.pageX-t(document).scrollLeft()<a.scrollSensitivity?r=t(document).scrollLeft(t(document).scrollLeft()-a.scrollSpeed):t(window).width()-(e.pageX-t(document).scrollLeft())<a.scrollSensitivity&&(r=t(document).scrollLeft(t(document).scrollLeft()+a.scrollSpeed))),r!==!1&&t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e)),this.positionAbs=this._convertPositionTo("absolute"),this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),i=this.items.length-1;i>=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),"original"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,h=r+t.height,l=this.offset.click.top,c=this.offset.click.left,u="x"===this.options.axis||s+l>r&&h>s+l,d="y"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var i="x"===this.options.axis||e(this.positionAbs.top+this.offset.click.top,t.top,t.height),s="y"===this.options.axis||e(this.positionAbs.left+this.offset.click.left,t.left,t.width),n=i&&s,o=this._getDragVerticalDirection(),a=this._getDragHorizontalDirection();return n?this.floating?a&&"right"===a||"down"===o?2:1:o&&("down"===o?2:1):!1},_intersectsWithSides:function(t){var i=e(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),s=e(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),n=this._getDragVerticalDirection(),o=this._getDragHorizontalDirection();return this.floating&&o?"right"===o&&s||"left"===o&&!s:n&&("down"===n&&i||"up"===n&&!i)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){r.push(this)}var s,n,o,a,r=[],h=[],l=this._connectWith();if(l&&e)for(s=l.length-1;s>=0;s--)for(o=t(l[s]),n=o.length-1;n>=0;n--)a=t.data(o[n],this.widgetFullName),a&&a!==this&&!a.options.disabled&&h.push([t.isFunction(a.options.items)?a.options.items.call(a.element):t(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a]);for(h.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return t(r)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,h,l,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,l=r.length;l>s;s++)h=t(r[s]),h.data(this.widgetName+"-item",a),c.push({item:h,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t("<"+s+">",e.document[0]).addClass(i||e.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");return"tr"===s?e.currentItem.children().each(function(){t("<td>&#160;</td>",e.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(n)}):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_contactContainers:function(s){var n,o,a,r,h,l,c,u,d,p,f=null,g=null;for(n=this.containers.length-1;n>=0;n--)if(!t.contains(this.currentItem[0],this.containers[n].element[0]))if(this._intersectsWith(this.containers[n].containerCache)){if(f&&t.contains(this.containers[n].element[0],f.element[0]))continue;f=this.containers[n],g=n}else this.containers[n].containerCache.over&&(this.containers[n]._trigger("out",s,this._uiHash(this)),this.containers[n].containerCache.over=0);if(f)if(1===this.containers.length)this.containers[g].containerCache.over||(this.containers[g]._trigger("over",s,this._uiHash(this)),this.containers[g].containerCache.over=1);else{for(a=1e4,r=null,p=f.floating||i(this.currentItem),h=p?"left":"top",l=p?"width":"height",c=this.positionAbs[h]+this.offset.click[h],o=this.items.length-1;o>=0;o--)t.contains(this.containers[g].element[0],this.items[o].item[0])&&this.items[o].item[0]!==this.currentItem[0]&&(!p||e(this.positionAbs.top+this.offset.click.top,this.items[o].top,this.items[o].height))&&(u=this.items[o].item.offset()[h],d=!1,Math.abs(u-c)>Math.abs(u+this.items[o][l]-c)&&(d=!0,u+=this.items[o][l]),a>Math.abs(u-c)&&(a=Math.abs(u-c),r=this.items[o],this.direction=d?"up":"down"));if(!r&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[g])return;r?this._rearrange(s,r,null,!0):this._rearrange(s,null,this.containers[g].element,!0),this._trigger("change",s,this._uiHash()),this.containers[g]._trigger("change",s,this._uiHash(this)),this.currentContainer=this.containers[g],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[g]._trigger("over",s,this._uiHash(this)),this.containers[g].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,t("document"===n.containment?document:window).width()-this.helperProportions.width-this.margins.left,(t("document"===n.containment?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==document&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.left<this.containment[0]&&(o=this.containment[0]+this.offset.click.left),e.pageY-this.offset.click.top<this.containment[1]&&(a=this.containment[1]+this.offset.click.top),e.pageX-this.offset.click.left>this.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&n.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||n.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(n.push(function(t){this._trigger("remove",t,this._uiHash())}),n.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,this.cancelHelperRemoval){if(!e){for(this._trigger("beforeStop",t,this._uiHash()),s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!1}if(e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null,!e){for(s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}})})(jQuery);(function(t){t.widget("ui.menu",{version:"1.10.4",defaultElement:"<ul>",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content ui-corner-all").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}).bind("click"+this.eventNamespace,t.proxy(function(t){this.options.disabled&&t.preventDefault()},this)),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item > a":function(t){t.preventDefault()},"click .ui-state-disabled > a":function(t){t.preventDefault()},"click .ui-menu-item:has(a)":function(e){var i=t(e.target).closest(".ui-menu-item");!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&t(this.document[0].activeElement).closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){var i=t(e.currentTarget);i.siblings().children(".ui-state-active").removeClass("ui-state-active"),this.focus(e,i)},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.children(".ui-menu-item").eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){t.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(e){t(e.target).closest(".ui-menu").length||this.collapseAll(e),this.mouseHandled=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeClass("ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").children("a").removeUniqueId().removeClass("ui-corner-all ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var e=t(this);e.data("ui-menu-submenu-carat")&&e.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(e){function i(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}var s,n,a,o,r,l=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:l=!1,n=this.previousFilter||"",a=String.fromCharCode(e.keyCode),o=!1,clearTimeout(this.filterTimer),a===n?o=!0:a=n+a,r=RegExp("^"+i(a),"i"),s=this.activeMenu.children(".ui-menu-item").filter(function(){return r.test(t(this).children("a").text())}),s=o&&-1!==s.index(this.active.next())?this.active.nextAll(".ui-menu-item"):s,s.length||(a=String.fromCharCode(e.keyCode),r=RegExp("^"+i(a),"i"),s=this.activeMenu.children(".ui-menu-item").filter(function(){return r.test(t(this).children("a").text())})),s.length?(this.focus(e,s),s.length>1?(this.previousFilter=a,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter):delete this.previousFilter}l&&e.preventDefault()},_activate:function(t){this.active.is(".ui-state-disabled")||(this.active.children("a[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i=this.options.icons.submenu,s=this.element.find(this.options.menus);this.element.toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length),s.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-corner-all").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),s=e.prev("a"),n=t("<span>").addClass("ui-menu-icon ui-icon "+i).data("ui-menu-submenu-carat",!0);s.attr("aria-haspopup","true").prepend(n),e.attr("aria-labelledby",s.attr("id"))}),e=s.add(this.element),e.children(":not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","presentation").children("a").uniqueId().addClass("ui-corner-all").attr({tabIndex:-1,role:this._itemRole()}),e.children(":not(.ui-menu-item)").each(function(){var e=t(this);/[^\-\u2014\u2013\s]/.test(e.text())||e.addClass("ui-widget-content ui-menu-divider")}),e.children(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){"icons"===t&&this.element.find(".ui-menu-icon").removeClass(this.options.icons.submenu).addClass(e.submenu),this._super(t,e)},focus:function(t,e){var i,s;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children("a").addClass("ui-state-focus"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,a,o,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,a=this.activeMenu.scrollTop(),o=this.activeMenu.height(),r=e.height(),0>n?this.activeMenu.scrollTop(a+n):n+r>o&&this.activeMenu.scrollTop(a+n-o+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this.active.children("a").removeClass("ui-state-focus"),this.active=null,this._trigger("blur",t,{item:this.active}))},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find("a.ui-state-active").removeClass("ui-state-active")},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").children(".ui-menu-item").first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.children(".ui-menu-item")[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.children(".ui-menu-item")[this.active?"last":"first"]())),undefined):(this.next(e),undefined)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.children(".ui-menu-item").first())),undefined):(this.next(e),undefined)},_hasScroll:function(){return this.element.outerHeight()<this.element.prop("scrollHeight")},select:function(e){this.active=this.active||t(e.target).closest(".ui-menu-item");var i={item:this.active};this.active.has(".ui-menu").length||this.collapseAll(e,!0),this._trigger("select",e,i)}})})(jQuery);
\ No newline at end of file

From 0682b5c5ea76f1fd51ca617d299f4d5a35c75be4 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Thu, 27 Feb 2014 15:44:34 -0500
Subject: [PATCH 108/193] fix admin users link

---
 public/templates/admin/users.tpl | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/public/templates/admin/users.tpl b/public/templates/admin/users.tpl
index dd178658a2..6a8fdacfd4 100644
--- a/public/templates/admin/users.tpl
+++ b/public/templates/admin/users.tpl
@@ -20,9 +20,7 @@
 <ul id="users-container" class="users admin">
 	<!-- BEGIN users -->
 	<div class="users-box" data-uid="{users.uid}" data-admin="{users.administrator}" data-username="{users.username}" data-banned="{users.banned}">
-		<a href="{relative_path}/user/{users.userslug}">
-			<img src="{users.picture}" class="img-thumbnail"/>
-		</a>
+		<a href="{relative_path}/user/{users.userslug}"><img src="{users.picture}" class="img-thumbnail"/></a>
 		<br/>
 		<a href="{relative_path}/user/{users.userslug}">{users.username}</a>
 		<br/>

From 20b5d577dd703a1ede7c06d7f0ba1dfcb565fa11 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Thu, 27 Feb 2014 20:36:04 -0500
Subject: [PATCH 109/193] ability to restart nodebb via executable

---
 loader.js | 12 ++++++++----
 nodebb    | 11 ++++++++++-
 2 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/loader.js b/loader.js
index 3bb103d417..2effb4ffa0 100644
--- a/loader.js
+++ b/loader.js
@@ -14,10 +14,7 @@ var	nconf = require('nconf'),
 
 				nbb.on('message', function(cmd) {
 					if (cmd === 'nodebb:restart') {
-						nbb.on('exit', function() {
-							nbb_start();
-						});
-						nbb.kill();
+						nbb_restart();
 					}
 				});
 			},
@@ -29,10 +26,17 @@ var	nconf = require('nconf'),
 						fs.unlinkSync(pidFilePath);
 					}
 				}
+			},
+			nbb_restart = function() {
+				nbb.on('exit', function() {
+					nbb_start();
+				});
+				nbb.kill();
 			};
 
 		process.on('SIGINT', nbb_stop);
 		process.on('SIGTERM', nbb_stop);
+		process.on('SIGHUP', nbb_restart);
 
 		nbb_start();
 	},
diff --git a/nodebb b/nodebb
index 107d96259e..bc52d69657 100755
--- a/nodebb
+++ b/nodebb
@@ -9,6 +9,7 @@ case "$1" in
 		echo "Starting NodeBB";
 		echo "  \"./nodebb stop\" to stop the NodeBB server";
 		echo "  \"./nodebb log\" to view server output";
+		echo "" > ./logs/output.log;
 		node loader -d "$@"
 		;;
 
@@ -17,7 +18,13 @@ case "$1" in
 		kill `cat pidfile`;
 		;;
 
+	reload|restart)
+		echo "Restarting NodeBB.";
+		kill -1 `cat pidfile`;
+		;;
+
 	log)
+		clear;
 		tail -F ./logs/output.log;
 		;;
 
@@ -54,11 +61,13 @@ case "$1" in
 
 	*)
 		echo "Welcome to NodeBB"
-		echo $"Usage: $0 {start|stop|log|setup|reset|upgrade|dev|watch}"
+		echo $"Usage: $0 {start|stop|reoad|restart|log|setup|reset|upgrade|dev|watch}"
 		echo ''
 		column -s '	' -t <<< '
 		start	Start the NodeBB server
 		stop	Stops the NodeBB server
+		reload	Restarts NodeBB
+		restart	Restarts NodeBB
 		log	Opens the logging interface (useful for debugging)
 		setup	Runs the NodeBB setup script
 		reset	Disables all plugins, restores the default theme.

From ed0a17b94a77c39767b39f766b4189a9371bdc70 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Thu, 27 Feb 2014 23:24:52 -0500
Subject: [PATCH 110/193] reoad, lol

---
 nodebb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nodebb b/nodebb
index bc52d69657..d33baeba04 100755
--- a/nodebb
+++ b/nodebb
@@ -61,7 +61,7 @@ case "$1" in
 
 	*)
 		echo "Welcome to NodeBB"
-		echo $"Usage: $0 {start|stop|reoad|restart|log|setup|reset|upgrade|dev|watch}"
+		echo $"Usage: $0 {start|stop|reload|restart|log|setup|reset|upgrade|dev|watch}"
 		echo ''
 		column -s '	' -t <<< '
 		start	Start the NodeBB server

From 091723a8c56e51a63390c08bd15240e6ae13ddb8 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Thu, 27 Feb 2014 23:45:12 -0500
Subject: [PATCH 111/193] closes #1101

---
 public/src/ajaxify.js         |   8 +-
 public/src/app.js             |   7 +-
 public/src/forum/category.js  | 205 ++++++++++++++++++++++++++++++----
 public/templates/category.tpl |   2 +-
 src/categories.js             |  23 +++-
 src/socket.io/posts.js        |   4 +-
 src/socket.io/topics.js       |  10 +-
 src/topics.js                 |  20 ++++
 8 files changed, 250 insertions(+), 29 deletions(-)

diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
index 6b88546cba..47a6a0b2a0 100644
--- a/public/src/ajaxify.js
+++ b/public/src/ajaxify.js
@@ -21,8 +21,9 @@ var ajaxify = {};
 
 	window.onpopstate = function (event) {
 		if (event !== null && event.state && event.state.url !== undefined && !ajaxify.initialLoad) {
-			ajaxify.go(event.state.url, null, true);
-			$(window).trigger('action:popstate', {url: event.state.url});
+			ajaxify.go(event.state.url, function() {
+				$(window).trigger('action:popstate', {url: event.state.url});
+			}, true);
 		}
 	};
 
@@ -30,6 +31,7 @@ var ajaxify = {};
 	ajaxify.initialLoad = false;
 
 	ajaxify.go = function (url, callback, quiet) {
+
 		// "quiet": If set to true, will not call pushState
 		app.enterRoom('global');
 
@@ -102,7 +104,7 @@ var ajaxify = {};
 					}
 				});
 
-				if (callback) {
+				if (typeof callback === 'function') {
 					callback();
 				}
 
diff --git a/public/src/app.js b/public/src/app.js
index 17415d392d..3201e2b563 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -446,15 +446,18 @@ var socket,
 
 	var previousScrollTop = 0;
 
+
+
 	app.enableInfiniteLoading = function(callback) {
 		$(window).off('scroll').on('scroll', function() {
+
 			var top = $(window).height() * 0.1;
 			var bottom = ($(document).height() - $(window).height()) * 0.9;
 			var currentScrollTop = $(window).scrollTop();
 
-			if($(window).scrollTop() < top && previousScrollTop > currentScrollTop) {
+			if(currentScrollTop < top && currentScrollTop < previousScrollTop) {
 				callback(-1);
-			} else if ($(window).scrollTop() > bottom && previousScrollTop < currentScrollTop) {
+			} else if (currentScrollTop > bottom && currentScrollTop > previousScrollTop) {
 				callback(1);
 			}
 			previousScrollTop = currentScrollTop;
diff --git a/public/src/forum/category.js b/public/src/forum/category.js
index da62a01226..9e778148e6 100644
--- a/public/src/forum/category.js
+++ b/public/src/forum/category.js
@@ -2,10 +2,6 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 	var Category = {},
 		loadingMoreTopics = false;
 
-	$(window).on('action:popstate', function(ev, data) {
-
-	});
-
 	Category.init = function() {
 		var	cid = templates.get('category_id'),
 			categoryName = templates.get('category_name'),
@@ -42,13 +38,128 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		socket.on('event:new_topic', Category.onNewTopic);
 
 		enableInfiniteLoading();
+
+		$('#topics-container').on('click', '.topic-title', function() {
+			var clickedTid = $(this).parents('li.category-item[data-tid]').attr('data-tid');
+			$('#topics-container li.category-item').each(function(index, el) {
+				if($(el).offset().top - $(window).scrollTop() > 0) {
+					tid = $(el).attr('data-tid');
+
+					localStorage.setItem('category:bookmark', tid);
+					localStorage.setItem('category:bookmark:clicked', clickedTid);
+					return false;
+				}
+			});
+		});
+	};
+
+	$(window).on('action:popstate', function(ev, data) {
+		if(data.url.indexOf('category/') === 0) {
+			var bookmark = localStorage.getItem('category:bookmark');
+			var clicked = localStorage.getItem('category:bookmark:clicked');
+
+			if (bookmark) {
+
+				if(config.usePagination) {
+					socket.emit('topics.getTidPage', bookmark, function(err, page) {
+						if(err) {
+							return;
+						}
+						if(parseInt(page, 10) !== pagination.currentPage) {
+							pagination.loadPage(page);
+						} else {
+							Category.scrollToTopic(bookmark, clicked, 400);
+						}
+					});
+				} else {
+
+					socket.emit('topics.getTidIndex', bookmark, function(err, index) {
+						if(err) {
+							return;
+						}
+
+						if(index === 0) {
+							Category.highlightTopic(clicked);
+							return;
+						}
+
+						if (index < 0) {
+							index = 0;
+						}
+
+						$('#topics-container').empty();
+						loadingMoreTopics = false;
+
+						Category.loadMoreTopics(templates.get('category_id'), after, function() {
+							Category.scrollToTopic(bookmark, clicked, 0);
+						});
+					});
+				}
+			}
+		}
+	});
+
+	Category.highlightTopic = function(tid) {
+		var highlight = $('#topics-container li.category-item[data-tid="' + tid + '"]');
+		if(highlight.length && !highlight.hasClass('highlight')) {
+			highlight.addClass('highlight');
+			setTimeout(function() {
+				highlight.removeClass('highlight');
+			}, 5000);
+		}
+	};
+
+	Category.scrollToTopic = function(tid, clickedTid, duration, offset) {
+		if(!tid) {
+			return;
+		}
+
+		if(!offset) {
+			offset = 0;
+		}
+
+		if($('#topics-container li.category-item[data-tid="' + tid + '"]').length) {
+			var	cid = templates.get('category_id');
+			var scrollTo = $('#topics-container li.category-item[data-tid="' + tid + '"]');
+
+			if (cid && scrollTo.length) {
+				$('html, body').animate({
+					scrollTop: (scrollTo.offset().top - $('#header-menu').height() - offset) + 'px'
+				}, duration !== undefined ? duration : 400, function() {
+					Category.highlightTopic(clickedTid);
+				});
+			}
+		}
 	};
 
 	function enableInfiniteLoading() {
 		if(!config.usePagination) {
-			app.enableInfiniteLoading(function() {
-				if(!loadingMoreTopics) {
-					Category.loadMoreTopics(templates.get('category_id'));
+			app.enableInfiniteLoading(function(direction) {
+
+				if(!loadingMoreTopics && $('#topics-container').children().length) {
+
+					var after = 0;
+					var el = null;
+
+					if(direction > 0) {
+						el = $('#topics-container .category-item[data-tid]').last();
+						after = parseInt(el.attr('data-index'), 10) + 1;
+					} else {
+						el = $('#topics-container .category-item[data-tid]').first();
+						after = parseInt(el.attr('data-index'), 10);
+						after -= config.topicsPerPage;
+						if(after < 0) {
+							after = 0;
+						}
+					}
+
+					var offset = el.offset().top - $('#header-menu').offset().top + $('#header-menu').height();
+
+					Category.loadMoreTopics(templates.get('category_id'), after, function() {
+						if(direction < 0 && el) {
+							Category.scrollToTopic(el.attr('data-tid'), null, 0, offset);
+						}
+					});
 				}
 			});
 		} else {
@@ -98,56 +209,112 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 
 			$(window).trigger('action:categories.new_topic.loaded');
 		});
-	}
+	};
+
+	Category.onTopicsLoaded = function(topics, callback) {
+		if(!topics || !topics.length) {
+			return;
+		}
+
+		function removeAlreadyAddedTopics() {
+			topics = topics.filter(function(topic) {
+				return $('#topics-container li[data-tid="' + topic.tid +'"]').length === 0;
+			});
+		}
+
+		var after = null,
+			before = null;
+
+		function findInsertionPoint() {
+			if (!$('#topics-container .category-item[data-tid]').length) {
+				return;
+			}
+			var last = $('#topics-container .category-item[data-tid]').last();
+			var lastIndex = last.attr('data-index');
+			var firstIndex = topics[topics.length - 1].index;
+			if (firstIndex > lastIndex) {
+				after = last;
+			} else {
+				before = $('#topics-container .category-item[data-tid]').first();
+			}
+		}
+
+		removeAlreadyAddedTopics();
+		if(!topics.length) {
+			return;
+		}
+
+		findInsertionPoint();
 
-	Category.onTopicsLoaded = function(topics) {
 		var html = templates.prepare(templates['category'].blocks['topics']).parse({
 			topics: topics
 		});
 
 		translator.translate(html, function(translatedHTML) {
-			var container = $('#topics-container');
+			var container = $('#topics-container'),
+				html = $(translatedHTML);
 
 			$('#topics-container, .category-sidebar').removeClass('hidden');
 			$('#category-no-topics').remove();
 
-			html = $(translatedHTML);
-
 			if(config.usePagination) {
 				container.empty().append(html);
 			} else {
-				container.append(html);
+				if(after) {
+					html.insertAfter(after);
+				} else if(before) {
+					html.insertBefore(before);
+				} else {
+					container.append(html);
+				}
 			}
 
 			$('#topics-container span.timeago').timeago();
 			app.createUserTooltips();
 			app.makeNumbersHumanReadable(html.find('.human-readable-number'));
+
+			if (typeof callback === 'function') {
+				callback(topics);
+			}
 		});
-	}
+	};
 
-	Category.loadMoreTopics = function(cid) {
+	Category.loadMoreTopics = function(cid, after, callback) {
 		if (loadingMoreTopics || !$('#topics-container').length) {
 			return;
 		}
 
+		if(after === 0 && $('#topics-container li.category-item[data-index="0"]').length) {
+			return;
+		}
+
 		$(window).trigger('action:categories.loading');
 		loadingMoreTopics = true;
+
 		socket.emit('categories.loadMore', {
 			cid: cid,
-			after: $('#topics-container').attr('data-nextstart')
+			after: after
 		}, function (err, data) {
+			loadingMoreTopics = false;
+
 			if(err) {
 				return app.alertError(err.message);
 			}
 
 			if (data && data.topics.length) {
-				Category.onTopicsLoaded(data.topics);
+				Category.onTopicsLoaded(data.topics, callback);
 				$('#topics-container').attr('data-nextstart', data.nextStart);
+			} else {
+
+				if (typeof callback === 'function') {
+					callback(data.topics);
+				}
 			}
-			loadingMoreTopics = false;
+
+
 			$(window).trigger('action:categories.loaded');
 		});
-	}
+	};
 
 	return Category;
 });
\ No newline at end of file
diff --git a/public/templates/category.tpl b/public/templates/category.tpl
index 25bc8c0a31..f1b6a9c365 100644
--- a/public/templates/category.tpl
+++ b/public/templates/category.tpl
@@ -39,7 +39,7 @@
 		<ul id="topics-container" itemscope itemtype="http://www.schema.org/ItemList" data-nextstart="{nextStart}">
 			<meta itemprop="itemListOrder" content="descending">
 			<!-- BEGIN topics -->
-			<li class="category-item <!-- IF topics.deleted -->deleted<!-- ENDIF topics.deleted --><!-- IF topics.unread -->unread<!-- ENDIF topics.unread -->" itemprop="itemListElement">
+			<li class="category-item <!-- IF topics.deleted -->deleted<!-- ENDIF topics.deleted --><!-- IF topics.unread -->unread<!-- ENDIF topics.unread -->" itemprop="itemListElement" data-tid="{topics.tid}" data-index="{topics.index}">
 
 				<div class="col-md-12 col-xs-12 panel panel-default topic-row">
 
diff --git a/src/categories.js b/src/categories.js
index 48af337280..5a86d8532c 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -85,11 +85,13 @@ var db = require('./database'),
 	};
 
 	Categories.getCategoryTopics = function(cid, start, stop, uid, callback) {
+		var tids;
 		async.waterfall([
 			function(next) {
 				Categories.getTopicIds(cid, start, stop, next);
 			},
-			function(tids, next) {
+			function(topicIds, next) {
+				tids = topicIds;
 				topics.getTopicsByTids(tids, uid, next);
 			},
 			function(topics, next) {
@@ -100,6 +102,15 @@ var db = require('./database'),
 					});
 				}
 
+				var indices = {};
+				for(var i=0; i<tids.length; ++i) {
+					indices[tids[i]] = start + i;
+				}
+
+				for(var i=0; i<topics.length; ++i) {
+					topics[i].index = indices[topics[i].tid];
+				}
+
 				db.sortedSetRevRank('categories:' + cid + ':tid', topics[topics.length - 1].tid, function(err, rank) {
 					if(err) {
 						return next(err);
@@ -118,6 +129,16 @@ var db = require('./database'),
 		db.getSortedSetRevRange('categories:' + cid + ':tid', start, stop, callback);
 	};
 
+	Categories.getTopicIndex = function(tid, callback) {
+		topics.getTopicField(tid, 'cid', function(err, cid) {
+			if(err) {
+				return callback(err);
+			}
+
+			db.sortedSetRevRank('categories:' + cid + ':tid', tid, callback);
+		});
+	};
+
 	Categories.getPageCount = function(cid, uid, callback) {
 		db.sortedSetCard('categories:' + cid + ':tid', function(err, topicCount) {
 			if(err) {
diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js
index dcbf85daa1..df0ebfeebd 100644
--- a/src/socket.io/posts.js
+++ b/src/socket.io/posts.js
@@ -243,11 +243,11 @@ SocketPosts.getFavouritedUsers = function(socket, pid, callback) {
 
 SocketPosts.getPidPage = function(socket, pid, callback) {
 	posts.getPidPage(pid, socket.uid, callback);
-}
+};
 
 SocketPosts.getPidIndex = function(socket, pid, callback) {
 	posts.getPidIndex(pid, callback);
-}
+};
 
 SocketPosts.flag = function(socket, pid, callback) {
 	if (!socket.uid) {
diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js
index e96fc2611d..ec19101cd2 100644
--- a/src/socket.io/topics.js
+++ b/src/socket.io/topics.js
@@ -1,4 +1,5 @@
 var topics = require('../topics'),
+	categories = require('../categories'),
 	threadTools = require('../threadTools'),
 	index = require('./index'),
 	user = require('../user'),
@@ -291,9 +292,16 @@ SocketTopics.loadMoreFromSet = function(socket, data, callback) {
 	topics.getTopicsFromSet(socket.uid, data.set, start, end, callback);
 };
 
-
 SocketTopics.getPageCount = function(socket, tid, callback) {
 	topics.getPageCount(tid, socket.uid, callback);
 };
 
+SocketTopics.getTidPage = function(socket, tid, callback) {
+	topics.getTidPage(tid, socket.uid, callback);
+};
+
+SocketTopics.getTidIndex = function(socket, tid, callback) {
+	categories.getTopicIndex(tid, callback);
+};
+
 module.exports = SocketTopics;
\ No newline at end of file
diff --git a/src/topics.js b/src/topics.js
index 1c2727f5ff..e0c56b52f5 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -426,6 +426,26 @@ var async = require('async'),
 		});
 	};
 
+	Topics.getTidPage = function(tid, uid, callback) {
+		if(!tid) {
+			return callback(new Error('invalid-tid'));
+		}
+
+		async.parallel({
+			index: function(next) {
+				categories.getTopicIndex(tid, next);
+			},
+			settings: function(next) {
+				user.getSettings(uid, next);
+			}
+		}, function(err, results) {
+			if(err) {
+				return callback(err);
+			}
+			callback(null, Math.ceil((results.index + 1) / results.settings.topicsPerPage));
+		});
+	};
+
 	Topics.getCategoryData = function(tid, callback) {
 		Topics.getTopicField(tid, 'cid', function(err, cid) {
 			if(err) {

From 9b4ca12dc121371d3d7dbabbb79224a59ec98049 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Fri, 28 Feb 2014 00:14:11 -0500
Subject: [PATCH 112/193] fixed missing var, posts.js cleanup

---
 public/src/forum/category.js |  2 +-
 src/posts.js                 | 41 +++++++++++++++---------------------
 2 files changed, 18 insertions(+), 25 deletions(-)

diff --git a/public/src/forum/category.js b/public/src/forum/category.js
index 9e778148e6..68d3df5b7b 100644
--- a/public/src/forum/category.js
+++ b/public/src/forum/category.js
@@ -90,7 +90,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 						$('#topics-container').empty();
 						loadingMoreTopics = false;
 
-						Category.loadMoreTopics(templates.get('category_id'), after, function() {
+						Category.loadMoreTopics(templates.get('category_id'), index, function() {
 							Category.scrollToTopic(bookmark, clicked, 0);
 						});
 					});
diff --git a/src/posts.js b/src/posts.js
index 1ad0750d16..18d71deb0f 100644
--- a/src/posts.js
+++ b/src/posts.js
@@ -1,3 +1,5 @@
+'use strict';
+
 var db = require('./database'),
 	utils = require('./../public/src/utils'),
 	user = require('./user'),
@@ -43,7 +45,7 @@ var db = require('./database'),
 			},
 			function(pid, next) {
 				plugins.fireHook('filter:post.save', content, function(err, newContent) {
-					next(err, pid, newContent)
+					next(err, pid, newContent);
 				});
 			},
 			function(pid, newContent, next) {
@@ -62,7 +64,7 @@ var db = require('./database'),
 					};
 
 				if (toPid) {
-					postData['toPid'] = toPid;
+					postData.toPid = toPid;
 				}
 
 				db.setObject('post:' + pid, postData, function(err) {
@@ -196,7 +198,7 @@ var db = require('./database'),
 
 					db.sortedSetRevRank('uid:' + uid + ':posts', posts[posts.length - 1].pid, function(err, rank) {
 						if(err) {
-							return calllback(err);
+							return callback(err);
 						}
 						var userPosts = {
 							posts: posts,
@@ -207,7 +209,7 @@ var db = require('./database'),
 				});
 			});
 		});
-	}
+	};
 
 	Posts.addUserInfoToPost = function(post, callback) {
 		user.getUserFields(post.uid, ['username', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned'], function(err, userData) {
@@ -299,7 +301,7 @@ var db = require('./database'),
 							postData.title = validator.escape(topicData.title);
 							postData.topicSlug = topicData.slug;
 							next(null, postData);
-						})
+						});
 					});
 				},
 				function(postData, next) {
@@ -404,7 +406,7 @@ var db = require('./database'),
 				}
 			});
 		});
-	}
+	};
 
 	Posts.uploadPostImage = function(image, callback) {
 
@@ -418,7 +420,7 @@ var db = require('./database'),
 				callback(new Error('Uploads are disabled!'));
 			}
 		}
-	}
+	};
 
 	Posts.uploadPostFile = function(file, callback) {
 
@@ -450,9 +452,8 @@ var db = require('./database'),
 				});
 			});
 		}
-	}
+	};
 
-	// this function should really be called User.getFavouritePosts
 	Posts.getFavourites = function(uid, start, end, callback) {
 		db.getSortedSetRevRange('uid:' + uid + ':favourites', start, end, function(err, pids) {
 			if (err) {
@@ -470,7 +471,7 @@ var db = require('./database'),
 
 				db.sortedSetRevRank('uid:' + uid + ':favourites', posts[posts.length - 1].pid, function(err, rank) {
 					if(err) {
-						return calllback(err);
+						return callback(err);
 					}
 					var favourites = {
 						posts: posts,
@@ -480,28 +481,20 @@ var db = require('./database'),
 				});
 			});
 		});
-	}
+	};
 
 	Posts.getPidPage = function(pid, uid, callback) {
 		if(!pid) {
 			return callback(new Error('invalid-pid'));
 		}
+
 		var index = 0;
 		async.waterfall([
 			function(next) {
-				Posts.getPostField(pid, 'tid', next);
+				Posts.getPidIndex(pid, next);
 			},
-			function(tid, next) {
-				topics.getPids(tid, next);
-			},
-			function(pids, next) {
-				index = pids.indexOf(pid.toString());
-				if(index === -1) {
-					return next(new Error('pid not found'));
-				}
-				next();
-			},
-			function(next) {
+			function(result, next) {
+				index = result;
 				user.getSettings(uid, next);
 			},
 			function(settings, next) {
@@ -518,6 +511,6 @@ var db = require('./database'),
 
 			db.sortedSetRank('tid:' + tid + ':posts', pid, callback);
 		});
-	}
+	};
 
 }(exports));

From 8064f7f0dbed243cf8e698af636377821bc01685 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Fri, 28 Feb 2014 00:59:35 -0500
Subject: [PATCH 113/193] scroll fix

---
 public/src/app.js         | 2 --
 public/src/forum/topic.js | 9 +--------
 2 files changed, 1 insertion(+), 10 deletions(-)

diff --git a/public/src/app.js b/public/src/app.js
index 3201e2b563..621fd0f44c 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -446,8 +446,6 @@ var socket,
 
 	var previousScrollTop = 0;
 
-
-
 	app.enableInfiniteLoading = function(callback) {
 		$(window).off('scroll').on('scroll', function() {
 
diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 1f3c58a24b..84eafc8621 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -1027,14 +1027,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				}
 				$('#pagination').html(index + ' out of ' + Topic.postCount);
 				$('.progress-bar').width((index / Topic.postCount * 100) + '%');
-				return false;
-			}
-		});
 
-		$('.posts > .post-row').each(function() {
-			var el = $(this);
-
-			if (elementInView(el)) {
 				if(!parseInt(el.attr('data-index'), 10)) {
 					localStorage.removeItem('topic:' + templates.get('topic_id') + ':bookmark');
 				} else {
@@ -1063,7 +1056,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 
 		var elTop = el.offset().top;
 		var elBottom = elTop + Math.floor(el.height());
-		return !(elTop > scrollBottom || elBottom < scrollTop);
+		return (elTop >= scrollTop && elBottom <= scrollBottom) || (elTop <= scrollTop && elBottom >= scrollTop);
 	}
 
 	Topic.scrollToPost = function(pid, highlight, duration, offset) {

From a2a9c8fd8a530594958d0537e32e2123a470f8aa Mon Sep 17 00:00:00 2001
From: Matthew Conlen <mc@mathisonian.com>
Date: Fri, 28 Feb 2014 12:58:05 -0500
Subject: [PATCH 114/193] fix error with undefined callback

---
 src/socket.io/admin.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index f4d11f6d4b..eddfd78342 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -120,7 +120,7 @@ SocketAdmin.categories.create = function(socket, data, callback) {
 
 SocketAdmin.categories.update = function(socket, data) {
 	if(!data) {
-		return callback(new Error('invalid data'));
+		throw new Error('invalid data');
 	}
 
 	admin.categories.update(data, socket);
@@ -380,4 +380,4 @@ SocketAdmin.groups.update = function(socket, data, callback) {
 	});
 };
 
-module.exports = SocketAdmin;
\ No newline at end of file
+module.exports = SocketAdmin;

From ccd29bfd61a242490f8f7ba13308f5aebc735db6 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Fri, 28 Feb 2014 15:25:50 -0500
Subject: [PATCH 115/193] added callback to category update

---
 public/src/forum/admin/categories.js | 15 ++++++++-
 src/admin/categories.js              | 46 +++++++++++++++++-----------
 src/socket.io/admin.js               |  6 ++--
 3 files changed, 45 insertions(+), 22 deletions(-)

diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js
index f963c6918d..2bcc541cd8 100644
--- a/public/src/forum/admin/categories.js
+++ b/public/src/forum/admin/categories.js
@@ -14,7 +14,20 @@ define(['uploader'], function(uploader) {
 
 		function save() {
 			if(Object.keys(modified_categories).length) {
-				socket.emit('admin.categories.update', modified_categories);
+				socket.emit('admin.categories.update', modified_categories, function(err, result) {
+					if (err) {
+						return app.alertError(err.message);
+					}
+
+					if (result && result.length) {
+						app.alert({
+							title: 'Updated Categories',
+							message: 'Category IDs ' + result.join(', ') + ' was successfully updated.',
+							type: 'success',
+							timeout: 2000
+						});
+					}
+				});
 				modified_categories = {};
 			}
 			return false;
diff --git a/src/admin/categories.js b/src/admin/categories.js
index 0522837a05..0980032734 100644
--- a/src/admin/categories.js
+++ b/src/admin/categories.js
@@ -1,35 +1,45 @@
-var db = require('./../database'),
+
+'use strict';
+
+var async = require('async'),
+	db = require('./../database'),
 	utils = require('./../../public/src/utils'),
 	categories = require('./../categories');
 
 (function(CategoriesAdmin) {
 
-	CategoriesAdmin.update = function(modified, socket) {
-		var updated = [];
+	CategoriesAdmin.update = function(modified, socket, callback) {
 
-		for (var cid in modified) {
+		function updateCategory(cid, next) {
 			var category = modified[cid];
+			var fields = Object.keys(category);
 
-			for (var key in category) {
-				db.setObjectField('category:' + cid, key, category[key]);
+			async.each(fields, function(key, next) {
+				updateCategoryField(cid, key, category[key], next);
+			}, next);
+		}
+
+		function updateCategoryField(cid, key, value, next) {
+			db.setObjectField('category:' + cid, key, value, function(err) {
+				if(err) {
+					return next(err);
+				}
 
 				if (key === 'name') {
-					// reset slugs if name is updated
-					var slug = cid + '/' + utils.slugify(category[key]);
-					db.setObjectField('category:' + cid, 'slug', slug);
+					var slug = cid + '/' + utils.slugify(value);
+					db.setObjectField('category:' + cid, 'slug', slug, next);
 				} else if (key === 'order') {
-					db.sortedSetAdd('categories:cid', category[key], cid);
+					db.sortedSetAdd('categories:cid', value, cid, next);
+				} else {
+					next();
 				}
-			}
-
-			updated.push(cid);
+			});
 		}
 
-		socket.emit('event:alert', {
-			title: 'Updated Categories',
-			message: 'Category IDs ' + updated.join(', ') + ' was successfully updated.',
-			type: 'success',
-			timeout: 2000
+		var cids = Object.keys(modified);
+
+		async.each(cids, updateCategory, function(err) {
+			callback(err, cids);
 		});
 	};
 
diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index eddfd78342..3d5fd87726 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -118,12 +118,12 @@ SocketAdmin.categories.create = function(socket, data, callback) {
 	categories.create(data, callback);
 };
 
-SocketAdmin.categories.update = function(socket, data) {
+SocketAdmin.categories.update = function(socket, data, callback) {
 	if(!data) {
-		throw new Error('invalid data');
+		return callback(new Error('invalid data'));
 	}
 
-	admin.categories.update(data, socket);
+	admin.categories.update(data, socket, callback);
 };
 
 SocketAdmin.categories.search = function(socket, data, callback) {

From 7eae79cee9aa792bdfd49e084d3e6ab8b0077efa Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Fri, 28 Feb 2014 15:36:57 -0500
Subject: [PATCH 116/193] anons cant chat

---
 public/src/app.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/src/app.js b/public/src/app.js
index 621fd0f44c..9ad598be89 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -407,7 +407,7 @@ var socket,
 			return;
 		}
 
-		if (!app.username) {
+		if (!app.uid) {
 			app.alert({
 				type: 'danger',
 				title: 'Not Logged In',

From eac201cae93e66b348fde61592f088a09a9859b3 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Fri, 28 Feb 2014 15:56:30 -0500
Subject: [PATCH 117/193] no need to do these on every ajaxify into categories

---
 public/src/forum/category.js | 13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)

diff --git a/public/src/forum/category.js b/public/src/forum/category.js
index 68d3df5b7b..91064f7aab 100644
--- a/public/src/forum/category.js
+++ b/public/src/forum/category.js
@@ -3,27 +3,22 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		loadingMoreTopics = false;
 
 	Category.init = function() {
-		var	cid = templates.get('category_id'),
-			categoryName = templates.get('category_name'),
-			categoryUrl = encodeURIComponent(window.location.href),
-			twitterUrl = "https://twitter.com/intent/tweet?url=" + categoryUrl + "&text=" + encodeURIComponent(categoryName),
-			facebookUrl = "https://www.facebook.com/sharer/sharer.php?u=" + categoryUrl,
-			googleUrl = "https://plus.google.com/share?url=" + categoryUrl;
+		var	cid = templates.get('category_id');
 
 		app.enterRoom('category_' + cid);
 
 		$('#twitter-share').on('click', function () {
-			window.open(twitterUrl, '_blank', 'width=550,height=420,scrollbars=no,status=no');
+			window.open('https://twitter.com/intent/tweet?url=' + encodeURIComponent(window.location.href) + '&text=' + encodeURIComponent(templates.get('category_name')), '_blank', 'width=550,height=420,scrollbars=no,status=no');
 			return false;
 		});
 
 		$('#facebook-share').on('click', function () {
-			window.open(facebookUrl, '_blank', 'width=626,height=436,scrollbars=no,status=no');
+			window.open('https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent(window.location.href), '_blank', 'width=626,height=436,scrollbars=no,status=no');
 			return false;
 		});
 
 		$('#google-share').on('click', function () {
-			window.open(googleUrl, '_blank', 'width=500,height=570,scrollbars=no,status=no');
+			window.open('https://plus.google.com/share?url=' + encodeURIComponent(window.location.href), '_blank', 'width=500,height=570,scrollbars=no,status=no');
 			return false;
 		});
 

From eea677655f694899c8d6e5a98718cea0133605b3 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Fri, 28 Feb 2014 16:13:19 -0500
Subject: [PATCH 118/193] minor clean up to categories.js

---
 public/src/forum/category.js | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/public/src/forum/category.js b/public/src/forum/category.js
index 91064f7aab..80231bc931 100644
--- a/public/src/forum/category.js
+++ b/public/src/forum/category.js
@@ -38,9 +38,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			var clickedTid = $(this).parents('li.category-item[data-tid]').attr('data-tid');
 			$('#topics-container li.category-item').each(function(index, el) {
 				if($(el).offset().top - $(window).scrollTop() > 0) {
-					tid = $(el).attr('data-tid');
-
-					localStorage.setItem('category:bookmark', tid);
+					localStorage.setItem('category:bookmark', $(el).attr('data-tid'));
 					localStorage.setItem('category:bookmark:clicked', clickedTid);
 					return false;
 				}
@@ -133,8 +131,9 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 
 				if(!loadingMoreTopics && $('#topics-container').children().length) {
 
-					var after = 0;
-					var el = null;
+					var after = 0,
+						offset = 0,
+						el = null;
 
 					if(direction > 0) {
 						el = $('#topics-container .category-item[data-tid]').last();
@@ -146,10 +145,9 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 						if(after < 0) {
 							after = 0;
 						}
+						offset = el.offset().top - $('#header-menu').offset().top + $('#header-menu').height();
 					}
 
-					var offset = el.offset().top - $('#header-menu').offset().top + $('#header-menu').height();
-
 					Category.loadMoreTopics(templates.get('category_id'), after, function() {
 						if(direction < 0 && el) {
 							Category.scrollToTopic(el.attr('data-tid'), null, 0, offset);
@@ -199,7 +197,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				pagination.recreatePaginationLinks(newPageCount);
 			});
 
-			$('#topics-container span.timeago').timeago();
+			topic.find('span.timeago').timeago();
 			app.createUserTooltips();
 
 			$(window).trigger('action:categories.new_topic.loaded');
@@ -264,7 +262,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				}
 			}
 
-			$('#topics-container span.timeago').timeago();
+			html.find('span.timeago').timeago();
 			app.createUserTooltips();
 			app.makeNumbersHumanReadable(html.find('.human-readable-number'));
 

From 561b42d0f92d40cae7735f87eb9ebc4b2e1751e6 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Fri, 28 Feb 2014 16:21:02 -0500
Subject: [PATCH 119/193] new staticDirs format, @mrwaffle

---
 package.json          |  3 ++-
 src/plugins.js        |  2 +-
 src/routes/plugins.js | 36 ++++++++++++++++++++++++++++--------
 3 files changed, 31 insertions(+), 10 deletions(-)

diff --git a/package.json b/package.json
index 9024cc068b..b3e7350f72 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,8 @@
     "nodebb-theme-cerulean": "~0.0.13",
     "nodebb-theme-lavender": "~0.0.22",
     "less": "^1.6.3",
-    "daemon": "~1.1.0"
+    "daemon": "~1.1.0",
+    "underscore": "^1.6.0"
   },
   "optionalDependencies": {
     "redis": "0.8.3",
diff --git a/src/plugins.js b/src/plugins.js
index 30731712e8..4d01840498 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -174,7 +174,7 @@ var fs = require('fs'),
 									(function(staticDir) {
 										fs.exists(staticDir, function(exists) {
 											if (exists) {
-												Plugins.staticDirs[mappedPath] = staticDir;
+												Plugins.staticDirs[path.join(pluginData.id, mappedPath)] = staticDir;
 											} else {
 												winston.warn('[plugins/' + pluginData.id + '] Mapped path \'' + mappedPath + ' => ' + staticDir + '\' not found.');
 											}
diff --git a/src/routes/plugins.js b/src/routes/plugins.js
index 96638a86dd..774fee7217 100644
--- a/src/routes/plugins.js
+++ b/src/routes/plugins.js
@@ -4,6 +4,8 @@ var	nconf = require('nconf'),
 	path = require('path'),
 	fs = require('fs'),
 	validator = require('validator'),
+	_ = require('underscore'),
+	async = require('async'),
 	plugins = require('../plugins'),
 
 	PluginRoutes = function(app) {
@@ -31,16 +33,34 @@ var	nconf = require('nconf'),
 
 		// Static Assets
 		app.get('/plugins/:id/*', function(req, res) {
-			var	relPath = req._parsedUrl.pathname.replace(nconf.get('relative_path') + '/plugins/' + req.params.id, '');
+			var	relPath = req._parsedUrl.pathname.replace(nconf.get('relative_path') + '/plugins/', ''),
+				matches = _.map(plugins.staticDirs, function(realPath, mappedPath) {
+					if (relPath.match(mappedPath)) {
+						return mappedPath;
+					} else {
+						return null;
+					}
+				}).filter(function(a) { return a; });
+
+			if (matches) {
+				async.map(matches, function(mappedPath, next) {
+					var	filePath = path.join(plugins.staticDirs[mappedPath], relPath.slice(mappedPath.length));
 
-			if (plugins.staticDirs[req.params.id]) {
-				var	fullPath = path.join(plugins.staticDirs[req.params.id], decodeURIComponent(relPath));
+					fs.exists(filePath, function(exists) {
+						if (exists) {
+							next(null, filePath);
+						} else {
+							next();
+						}
+					});
+				}, function(err, matches) {
+					// Filter out the nulls
+					matches = matches.filter(function(a) {
+						return a;
+					});
 
-				fs.exists(fullPath, function(exists) {
-					if (exists) {
-						res.sendfile(fullPath, {
-							maxAge: app.enabled('cache') ? 5184000000 : 0
-						});
+					if (matches.length) {
+						res.sendfile(matches[0]);
 					} else {
 						res.redirect('/404');
 					}

From 6bf36a04683094e6654fc679c9c6359844771b9b Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Fri, 28 Feb 2014 16:23:46 -0500
Subject: [PATCH 120/193] if alert is closed dont fire clickfn

---
 public/src/app.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/public/src/app.js b/public/src/app.js
index 9ad598be89..e33a0f545d 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -218,9 +218,10 @@ var socket,
 				$('#' + params.location).prepend(alert.fadeIn('100'));
 
 				if(typeof params.closefn === 'function') {
-					alert.find('button').on('click', function () {
+					alert.find('button').on('click', function() {
 						params.closefn();
 						fadeOut();
+						return false;
 					});
 				}
 			});

From 7081c7dcc418627362e655449ed15f80264f76a4 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Fri, 28 Feb 2014 16:50:39 -0500
Subject: [PATCH 121/193] moved ip log to api

---
 src/routes/api.js | 3 +++
 src/webserver.js  | 3 ---
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/routes/api.js b/src/routes/api.js
index 4b326fe565..d7e4419b22 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -30,6 +30,9 @@ var path = require('path'),
 					user.updateLastOnlineTime(req.user.uid);
 				}
 
+				// Log IP address
+				db.sortedSetAdd('ip:recent', +new Date(), req.ip || 'Unknown');
+
 				next();
 			});
 
diff --git a/src/webserver.js b/src/webserver.js
index b4d5afe7b6..8d340c3dd1 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -289,9 +289,6 @@ process.on('uncaughtException', function(err) {
 					// Disable framing
 					res.setHeader('X-Frame-Options', 'SAMEORIGIN');
 
-					// Log IP address
-					db.sortedSetAdd('ip:recent', +new Date(), req.ip || 'Unknown');
-
 					next();
 				});
 

From e75c303b89bc21d9e775738e1a3cc44f25aafa96 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Fri, 28 Feb 2014 17:19:42 -0500
Subject: [PATCH 122/193] added unique visitor count to admin dashboard

---
 public/src/forum/admin/index.js  | 11 +++++++++++
 public/templates/admin/index.tpl | 25 +++++++++++++++++++++++++
 src/routes/api.js                |  3 +--
 src/socket.io/admin.js           | 25 +++++++++++++++++++++++++
 4 files changed, 62 insertions(+), 2 deletions(-)

diff --git a/public/src/forum/admin/index.js b/public/src/forum/admin/index.js
index 1870884328..7c9f6db3bd 100644
--- a/public/src/forum/admin/index.js
+++ b/public/src/forum/admin/index.js
@@ -35,6 +35,17 @@ define(function() {
 		$('.restart').on('click', function() {
 			socket.emit('admin.restart');
 		});
+
+		socket.emit('admin.getVisitorCount', function(err, data) {
+			if(err) {
+				return app.alertError(err.message);
+			}
+
+			var uniqueVisitors = $('#unique-visitors');
+			for(var key in data) {
+				uniqueVisitors.find('#' + key).text(data[key]);
+			}
+		});
 	};
 
 	Admin.updateRoomUsage = function(err, data) {
diff --git a/public/templates/admin/index.tpl b/public/templates/admin/index.tpl
index 2d548c459c..3fac2b0917 100644
--- a/public/templates/admin/index.tpl
+++ b/public/templates/admin/index.tpl
@@ -36,4 +36,29 @@
 			</div>
 		</div>
 	</div>
+	<div class="col-sm-6">
+		<div class="panel panel-default">
+			<div class="panel-heading">Unique Visitors</div>
+			<div class="panel-body">
+				<div id="unique-visitors">
+					<div class="text-center pull-left">
+						<div id="day"></div>
+						<div>Day</div>
+					</div>
+					<div class="text-center pull-left">
+						<div id="week"></div>
+						<div>Week</div>
+					</div>
+					<div class="text-center pull-left">
+						<div id="month"></div>
+						<div>Month</div>
+					</div>
+					<div class="text-center pull-left">
+						<div id="alltime"></div>
+						<div>All Time</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
 </div>
\ No newline at end of file
diff --git a/src/routes/api.js b/src/routes/api.js
index d7e4419b22..134964d185 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -30,8 +30,7 @@ var path = require('path'),
 					user.updateLastOnlineTime(req.user.uid);
 				}
 
-				// Log IP address
-				db.sortedSetAdd('ip:recent', +new Date(), req.ip || 'Unknown');
+				db.sortedSetAdd('ip:recent', Date.now(), req.ip || 'Unknown');
 
 				next();
 			});
diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index 3d5fd87726..316d77a347 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -9,6 +9,7 @@ var	groups = require('../groups'),
 	categories = require('../categories'),
 	CategoryTools = require('../categoryTools'),
 	logger = require('../logger'),
+	db = require('../database'),
 	admin = {
 		user: require('../admin/user'),
 		categories: require('../admin/categories')
@@ -35,6 +36,30 @@ SocketAdmin.restart = function(socket, data, callback) {
 	meta.restart();
 };
 
+
+SocketAdmin.getVisitorCount = function(socket, data, callback) {
+	var terms = {
+		day: 86400000,
+		week: 604800000,
+		month: 2592000000
+	};
+	var now = Date.now();
+	async.parallel({
+		day: function(next) {
+			db.sortedSetCount('ip:recent', now - terms.day, now, next);
+		},
+		week: function(next) {
+			db.sortedSetCount('ip:recent', now - terms.week, now, next);
+		},
+		month: function(next) {
+			db.sortedSetCount('ip:recent', now - terms.month, now, next);
+		},
+		alltime: function(next) {
+			db.sortedSetCount('ip:recent', 0, now, next);
+		}
+	}, callback);
+}
+
 /* Topics */
 
 SocketAdmin.topics = {};

From 84dc012198c74fc58417e674433845947c41c4ac Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Fri, 28 Feb 2014 18:17:17 -0500
Subject: [PATCH 123/193] closes #1142

---
 public/src/forum/topic.js | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 84eafc8621..4ab60fe365 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -141,16 +141,16 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 							}
 							loadingEl.remove();
 
-							categoriesEl.on('click', function(e) {
-								var el = $(e.target);
+							categoriesEl.on('click', 'li[data-cid]', function(e) {
+								var el = $(this);
 								if (el.is('li')) {
-									confirmCat.html(e.target.innerHTML);
+									confirmCat.html(el.html());
 									confirmDiv.css({display: 'block'});
 									targetCid = el.attr('data-cid');
-									targetCatLabel = e.html();
+									targetCatLabel = el.html();
 									commitEl.prop('disabled', false);
 								}
-							}, false);
+							});
 
 							commitEl.on('click', function() {
 								if (!commitEl.prop('disabled') && targetCid) {

From d63ff461f319487abcdb1fe2455f2b9420094687 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Fri, 28 Feb 2014 19:56:00 -0500
Subject: [PATCH 124/193] changed executable to not run watch-mode using the
 loader

---
 nodebb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nodebb b/nodebb
index d33baeba04..26ec782725 100755
--- a/nodebb
+++ b/nodebb
@@ -56,7 +56,7 @@ case "$1" in
 		echo "Launching NodeBB in \"development\" mode."
 		echo "To run the production build of NodeBB, please use \"forever\"."
 		echo "More Information: https://github.com/designcreateplay/NodeBB/wiki/How-to-run-NodeBB"
-		NODE_ENV=development supervisor -q --extensions 'node|js|tpl' -- loader "$@"
+		NODE_ENV=development supervisor -q --extensions 'node|js|tpl' -- app "$@"
 		;;
 
 	*)

From 5e2460e17e23b6534d5701266bf473494bac356b Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Fri, 28 Feb 2014 20:05:19 -0500
Subject: [PATCH 125/193] fixed #1144

---
 src/plugins.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/plugins.js b/src/plugins.js
index 4d01840498..9462785054 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -246,7 +246,7 @@ var fs = require('fs'),
 				`data.priority`, the relative priority of the method when it is eventually called (default: 10)
 		*/
 
-		if (data.hook && data.method) {
+		if (data.hook && data.method && typeof data.method === 'string' && data.method.length > 0) {
 			data.id = id;
 			if (!data.priority) data.priority = 10;
 			data.method = data.method.split('.').reduce(function(memo, prop) {

From 5540313b7f913e939f49c925e7a443df8ebdd6c1 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Fri, 28 Feb 2014 20:13:28 -0500
Subject: [PATCH 126/193] fixing path resolution for plugins in production mode

---
 src/meta.js | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/src/meta.js b/src/meta.js
index f4411904fd..88e8504762 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -3,6 +3,7 @@ var fs = require('fs'),
 	async = require('async'),
 	winston = require('winston'),
 	nconf = require('nconf'),
+	_ = require('underscore'),
 
 	utils = require('./../public/src/utils'),
 	translator = require('./../public/src/translator'),
@@ -250,12 +251,17 @@ var fs = require('fs'),
 						jsPath = path.normalize(jsPath);
 
 						if (jsPath.substring(0, 7) === 'plugins') {
-							var paths = jsPath.split(path.sep),
-								mappedPath = paths[1];
+							var	matches = _.map(plugins.staticDirs, function(realPath, mappedPath) {
+								if (jsPath.match(mappedPath)) {
+									return mappedPath;
+								} else {
+									return null;
+								}
+							}).filter(function(a) { return a; });
 
-							if (plugins.staticDirs[mappedPath]) {
-								jsPath = jsPath.replace(path.join('plugins', mappedPath), '');
-								return path.join(plugins.staticDirs[mappedPath], jsPath);
+							if (matches.length) {
+								var	relPath = jsPath.slice(new String('plugins/' + matches[0]).length);
+								return plugins.staticDirs[matches[0]] + relPath;
 							} else {
 								winston.warn('[meta.scripts.get] Could not resolve mapped path: ' + mappedPath + '. Are you sure it is defined by a plugin?');
 								return null;

From 1c19ae48bd8ede4626cab81430650573c87db501 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Fri, 28 Feb 2014 20:39:27 -0500
Subject: [PATCH 127/193] fixed #1143 -- also removed near-meaningless info
 messages saying that a Hook had been registered.

---
 src/plugins.js | 21 ++++++++++++++-------
 1 file changed, 14 insertions(+), 7 deletions(-)

diff --git a/src/plugins.js b/src/plugins.js
index 9462785054..f334d8b0da 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -246,24 +246,31 @@ var fs = require('fs'),
 				`data.priority`, the relative priority of the method when it is eventually called (default: 10)
 		*/
 
+		var method;
+
 		if (data.hook && data.method && typeof data.method === 'string' && data.method.length > 0) {
 			data.id = id;
 			if (!data.priority) data.priority = 10;
-			data.method = data.method.split('.').reduce(function(memo, prop) {
-				if (memo[prop]) {
+			method = data.method.split('.').reduce(function(memo, prop) {
+				if (memo !== null && memo[prop]) {
 					return memo[prop];
 				} else {
-					// Couldn't find method by path, assuming property with periods in it (evil!)
-					Plugins.libraries[data.id][data.method];
+					// Couldn't find method by path, aborting
+					return null;
 				}
 			}, Plugins.libraries[data.id]);
 
+			if (method === null) {
+				winston.warn('[plugins/' + id + '] Hook method mismatch: ' + data.hook + ' => ' + data.method);
+				return callback();
+			}
+
+			// Write the actual method reference to the hookObj
+			data.method = method;
+
 			Plugins.loadedHooks[data.hook] = Plugins.loadedHooks[data.hook] || [];
 			Plugins.loadedHooks[data.hook].push(data);
 
-			if (global.env === 'development') {
-				winston.info('[plugins] Hook registered: ' + data.hook + ' will call ' + id);
-			}
 			callback();
 		} else return;
 	};

From 24b669bd396f49de392c34d22f7bcfa6ed17ca19 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Fri, 28 Feb 2014 20:47:49 -0500
Subject: [PATCH 128/193] some fixes for search plugin

---
 src/postTools.js  |  4 +---
 src/posts.js      |  4 ++--
 src/routes/api.js | 10 ++++------
 3 files changed, 7 insertions(+), 11 deletions(-)

diff --git a/src/postTools.js b/src/postTools.js
index a097df2d30..b610e19415 100644
--- a/src/postTools.js
+++ b/src/postTools.js
@@ -90,9 +90,7 @@ var winston = require('winston'),
 
 									topics.setTopicField(tid, 'thumb', options.topic_thumb);
 
-									db.searchRemove('topic', tid, function() {
-										db.searchIndex('topic', title, tid);
-									});
+									plugins.fireHook('action:topic.edit', tid);
 								}
 
 								posts.getPostData(pid, function(err, postData) {
diff --git a/src/posts.js b/src/posts.js
index 18d71deb0f..ac428870d0 100644
--- a/src/posts.js
+++ b/src/posts.js
@@ -92,10 +92,10 @@ var db = require('./database'),
 						return next(err);
 					}
 
-					postData.content = content;
-
 					plugins.fireHook('action:post.save', postData);
 
+					postData.content = content;
+
 					next(null, postData);
 				});
 			}
diff --git a/src/routes/api.js b/src/routes/api.js
index 134964d185..3274b4c584 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -393,15 +393,13 @@ var path = require('path'),
 					return res.redirect('/404');
 				}
 
-				var limit = 50;
-
 				function searchPosts(callback) {
 					Plugins.fireHook('filter:search.query', {
 						index: 'post',
-						query: req.params.terms
+						query: req.params.term
 					}, function(err, pids) {
 						if (err) {
-							return callback(err, null);
+							return callback(err);
 						}
 
 						posts.getPostSummaryByPids(pids, false, callback);
@@ -411,10 +409,10 @@ var path = require('path'),
 				function searchTopics(callback) {
 					Plugins.fireHook('filter:search.query', {
 						index: 'topic',
-						query: req.params.terms
+						query: req.params.term
 					}, function(err, tids) {
 						if (err) {
-							return callback(err, null);
+							return callback(err);
 						}
 
 						topics.getTopicsByTids(tids, 0, callback);

From 1f136c6a727cc5e6f8eeb786bfa89f28dcdd5ab6 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Fri, 28 Feb 2014 20:51:12 -0500
Subject: [PATCH 129/193] ninjafix to mappedPath

---
 src/meta.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/meta.js b/src/meta.js
index 88e8504762..93f53e04d3 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -263,7 +263,7 @@ var fs = require('fs'),
 								var	relPath = jsPath.slice(new String('plugins/' + matches[0]).length);
 								return plugins.staticDirs[matches[0]] + relPath;
 							} else {
-								winston.warn('[meta.scripts.get] Could not resolve mapped path: ' + mappedPath + '. Are you sure it is defined by a plugin?');
+								winston.warn('[meta.scripts.get] Could not resolve mapped path: ' + jsPath + '. Are you sure it is defined by a plugin?');
 								return null;
 							}
 						} else {

From 8eca1955304bcc7c9330387e02cb30590e36cdce Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Fri, 28 Feb 2014 22:08:33 -0500
Subject: [PATCH 130/193] updateHeader once on load

---
 public/src/forum/topic.js | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 4ab60fe365..9a86aaec32 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -322,11 +322,10 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 						localStorage.removeItem('topic:' + tid + ':bookmark');
 					}
 				});
-				updateHeader();
-			} else {
-				updateHeader();
 			}
 
+			updateHeader();
+
 			$('#post-container').on('mouseenter', '.favourite-tooltip', function(e) {
 				if (!$(this).data('users-loaded')) {
 					$(this).data('users-loaded', "true");

From 0fecbf7cbffb5553fd0c046075ed55b18a689964 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Fri, 28 Feb 2014 23:38:04 -0500
Subject: [PATCH 131/193] entity decoding in filenames

---
 src/routes/plugins.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/routes/plugins.js b/src/routes/plugins.js
index 774fee7217..00f37d8d02 100644
--- a/src/routes/plugins.js
+++ b/src/routes/plugins.js
@@ -44,7 +44,7 @@ var	nconf = require('nconf'),
 
 			if (matches) {
 				async.map(matches, function(mappedPath, next) {
-					var	filePath = path.join(plugins.staticDirs[mappedPath], relPath.slice(mappedPath.length));
+					var	filePath = path.join(plugins.staticDirs[mappedPath], decodeURIComponent(relPath.slice(mappedPath.length)));
 
 					fs.exists(filePath, function(exists) {
 						if (exists) {

From fff3ba5bec05db5e866d8ff8a401dcab37c154d8 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 1 Mar 2014 15:45:43 -0500
Subject: [PATCH 132/193] hinted redis.js

---
 src/database/redis.js | 136 +++++++++++++++++++-----------------------
 1 file changed, 63 insertions(+), 73 deletions(-)

diff --git a/src/database/redis.js b/src/database/redis.js
index 0c1d9b9673..57debf6efa 100644
--- a/src/database/redis.js
+++ b/src/database/redis.js
@@ -1,9 +1,10 @@
-
+'use strict';
 
 (function(module) {
-	'use strict';
+
 	var winston = require('winston'),
 		nconf = require('nconf'),
+		path = require('path'),
 		express = require('express'),
 		redis_socket_or_host = nconf.get('redis:host'),
 		utils = require('./../../public/src/utils.js'),
@@ -53,13 +54,12 @@
 		return reds.client || (reds.client = redisClient);
 	};
 
-	var	userSearch = reds.createSearch('nodebbusersearch'),
-		postSearch = reds.createSearch('nodebbpostsearch'),
+	var	postSearch = reds.createSearch('nodebbpostsearch'),
 		topicSearch = reds.createSearch('nodebbtopicsearch');
 
 	var db = parseInt(nconf.get('redis:database'), 10);
 
-	if (db){
+	if (db) {
 		redisClient.select(db, function(error) {
 			if(error) {
 				winston.error("NodeBB could not connect to your Redis database. Redis returned the following error: " + error.message);
@@ -70,24 +70,22 @@
 
 	module.init = function(callback) {
 		callback(null);
-	}
+	};
 
 	module.close = function() {
 		redisClient.quit();
-	}
+	};
 
 	//
 	// Exported functions
 	//
 	module.searchIndex = function(key, content, id) {
-		if(key === 'post') {
+		if (key === 'post') {
 			postSearch.index(content, id);
 		} else if(key === 'topic') {
 			topicSearch.index(content, id);
-		} else if(key === 'user') {
-			userSearch.index(content, id);
 		}
-	}
+	};
 
 	module.search = function(key, term, limit, callback) {
 		function search(searchObj, callback) {
@@ -102,34 +100,30 @@
 			search(postSearch, callback);
 		} else if(key === 'topic') {
 			search(topicSearch, callback);
-		} else if(key === 'user') {
-			search(userSearch, callback);
 		}
-	}
+	};
 
 	module.searchRemove = function(key, id, callback) {
 		if(key === 'post') {
 			postSearch.remove(id);
 		} else if(key === 'topic') {
 			topicSearch.remove(id);
-		} else if(key === 'user') {
-			userSearch.remove(id);
 		}
 
 		if (typeof callback === 'function') {
-			callback()
+			callback();
 		}
-	}
+	};
 
 	module.flushdb = function(callback) {
 		redisClient.send_command('flushdb', [], function(err) {
-			if(err){
-				winston.error(error);
+			if (err) {
+				winston.error(err.message);
 				return callback(err);
 			}
-			callback(null);
+			callback();
 		});
-	}
+	};
 
 	module.getFileName = function(callback) {
 		var multi = redisClient.multi();
@@ -149,8 +143,7 @@
 			var dbFile = path.join(results.dir, results.dbfilename);
 			callback(null, dbFile);
 		});
-	}
-
+	};
 
 	module.info = function(callback) {
 		redisClient.info(function (err, data) {
@@ -172,7 +165,7 @@
 
 			callback(null, redisData);
 		});
-	}
+	};
 
 	// key
 
@@ -180,35 +173,35 @@
 		redisClient.exists(key, function(err, exists) {
 			callback(err, exists === 1);
 		});
-	}
+	};
 
 	module.delete = function(key, callback) {
 		redisClient.del(key, callback);
-	}
+	};
 
 	module.get = function(key, callback) {
 		redisClient.get(key, callback);
-	}
+	};
 
 	module.set = function(key, value, callback) {
 		redisClient.set(key, value, callback);
-	}
+	};
 
 	module.keys = function(key, callback) {
 		redisClient.keys(key, callback);
-	}
+	};
 
 	module.rename = function(oldKey, newKey, callback) {
 		redisClient.rename(oldKey, newKey, callback);
-	}
+	};
 
 	module.expire = function(key, seconds, callback) {
 		redisClient.expire(key, seconds, callback);
-	}
+	};
 
 	module.expireAt = function(key, timestamp, callback) {
 		redisClient.expireat(key, timestamp, callback);
-	}
+	};
 
 	//hashes
 
@@ -219,15 +212,15 @@
 				callback(err, res);
 			}
 		});
-	}
+	};
 
 	module.setObjectField = function(key, field, value, callback) {
 		redisClient.hset(key, field, value, callback);
-	}
+	};
 
 	module.getObject = function(key, callback) {
 		redisClient.hgetall(key, callback);
-	}
+	};
 
 	module.getObjects = function(keys, callback) {
 		var	multi = redisClient.multi();
@@ -239,7 +232,7 @@
 		multi.exec(function (err, replies) {
 			callback(err, replies);
 		});
-	}
+	};
 
 	module.getObjectField = function(key, field, callback) {
 		module.getObjectFields(key, [field], function(err, data) {
@@ -249,7 +242,7 @@
 
 			callback(null, data[field]);
 		});
-	}
+	};
 
 	module.getObjectFields = function(key, fields, callback) {
 		redisClient.hmget(key, fields, function(err, data) {
@@ -265,48 +258,48 @@
 
 			callback(null, returnData);
 		});
-	}
+	};
 
 	module.getObjectKeys = function(key, callback) {
 		redisClient.hkeys(key, callback);
-	}
+	};
 
 	module.getObjectValues = function(key, callback) {
 		redisClient.hvals(key, callback);
-	}
+	};
 
 	module.isObjectField = function(key, field, callback) {
 		redisClient.hexists(key, field, function(err, exists) {
 			callback(err, exists === 1);
 		});
-	}
+	};
 
 	module.deleteObjectField = function(key, field, callback) {
 		redisClient.hdel(key, field, callback);
-	}
+	};
 
 	module.incrObjectField = function(key, field, callback) {
 		redisClient.hincrby(key, field, 1, callback);
-	}
+	};
 
 	module.decrObjectField = function(key, field, callback) {
 		redisClient.hincrby(key, field, -1, callback);
-	}
+	};
 
 	module.incrObjectFieldBy = function(key, field, value, callback) {
 		redisClient.hincrby(key, field, value, callback);
-	}
+	};
 
 
 	// sets
 
 	module.setAdd = function(key, value, callback) {
 		redisClient.sadd(key, value, callback);
-	}
+	};
 
 	module.setRemove = function(key, value, callback) {
 		redisClient.srem(key, value, callback);
-	}
+	};
 
 	module.isSetMember = function(key, value, callback) {
 		redisClient.sismember(key, value, function(err, result) {
@@ -316,7 +309,7 @@
 
 			callback(null, result === 1);
 		});
-	}
+	};
 
 	module.isMemberOfSets = function(sets, value, callback) {
 		var batch = redisClient.multi();
@@ -326,67 +319,67 @@
 		}
 
 		batch.exec(callback);
-	}
+	};
 
 	module.getSetMembers = function(key, callback) {
 		redisClient.smembers(key, callback);
-	}
+	};
 
 	module.setCount = function(key, callback) {
 		redisClient.scard(key, callback);
-	}
+	};
 
 	module.setRemoveRandom = function(key, callback) {
 		redisClient.spop(key, callback);
-	}
+	};
 
 	// sorted sets
 
 	module.sortedSetAdd = function(key, score, value, callback) {
 		redisClient.zadd(key, score, value, callback);
-	}
+	};
 
 	module.sortedSetRemove = function(key, value, callback) {
 		redisClient.zrem(key, value, callback);
-	}
+	};
 
 	module.getSortedSetRange = function(key, start, stop, callback) {
 		redisClient.zrange(key, start, stop, callback);
-	}
+	};
 
 	module.getSortedSetRevRange = function(key, start, stop, callback) {
 		redisClient.zrevrange(key, start, stop, callback);
-	}
+	};
 
 	module.getSortedSetRevRangeByScore = function(args, callback) {
 		redisClient.zrevrangebyscore(args, callback);
-	}
+	};
 
 	module.sortedSetCount = function(key, min, max, callback) {
 		redisClient.zcount(key, min, max, callback);
-	}
+	};
 
 	module.sortedSetCard = function(key, callback) {
 		redisClient.zcard(key, callback);
-	}
+	};
 
 	module.sortedSetRank = function(key, value, callback) {
 		redisClient.zrank(key, value, callback);
-	}
+	};
 
 	module.sortedSetRevRank = function(key, value, callback) {
 		redisClient.zrevrank(key, value, callback);
-	}
+	};
 
 	module.sortedSetScore = function(key, value, callback) {
 		redisClient.zscore(key, value, callback);
-	}
+	};
 
 	module.isSortedSetMember = function(key, value, callback) {
 		module.sortedSetScore(key, value, function(err, score) {
 			callback(err, !!score);
 		});
-	}
+	};
 
 	module.sortedSetsScore = function(keys, value, callback) {
 		var	multi = redisClient.multi();
@@ -396,31 +389,28 @@
 		}
 
 		multi.exec(callback);
-	}
+	};
 
 	// lists
 	module.listPrepend = function(key, value, callback) {
 		redisClient.lpush(key, value, callback);
-	}
+	};
 
 	module.listAppend = function(key, value, callback) {
 		redisClient.rpush(key, value, callback);
-	}
+	};
 
 	module.listRemoveLast = function(key, callback) {
 		redisClient.rpop(key, callback);
-	}
+	};
 
 	module.listRemoveAll = function(key, value, callback) {
 		redisClient.lrem(key, 0, value, callback);
-	}
+	};
 
 	module.getListRange = function(key, start, stop, callback) {
 		redisClient.lrange(key, start, stop, callback);
-	}
-
-
-
+	};
 
 }(exports));
 

From b3d7ae1c861ad807abfd7392d8f8729c06ba728d Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sat, 1 Mar 2014 15:46:13 -0500
Subject: [PATCH 133/193] showing who is replying in the active users block

---
 public/src/forum/topic.js      | 11 ++++++++++-
 public/src/modules/composer.js | 19 +++++++++++++++++++
 src/socket.io/modules.js       | 25 ++++++++++++++++++++++++-
 3 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 9a86aaec32..4ebfb4aae7 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -613,7 +613,8 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			'event:topic_deleted', 'event:topic_restored', 'event:topic:locked',
 			'event:topic_unlocked', 'event:topic_pinned', 'event:topic_unpinned',
 			'event:topic_moved', 'event:post_edited', 'event:post_deleted', 'event:post_restored',
-			'posts.favourite', 'user.isOnline', 'posts.upvote', 'posts.downvote'
+			'posts.favourite', 'user.isOnline', 'posts.upvote', 'posts.downvote',
+			'event:topic.replyStart', 'event:topic.replyStop'
 		]);
 
 		socket.on('get_users_in_room', function(data) {
@@ -876,6 +877,14 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			}
 		});
 
+		socket.on('event:topic.replyStart', function(uid) {
+			$('.thread_active_users [data-uid="' + uid + '"]').addClass('replying');
+		});
+
+		socket.on('event:topic.replyStop', function(uid) {
+			$('.thread_active_users [data-uid="' + uid + '"]').removeClass('replying');
+		});
+
 		function adjust_rep(value, pid, uid) {
 			var votes = $('li[data-pid="' + pid + '"] .votes'),
 				reputationElements = $('.reputation[data-uid="' + uid + '"]'),
diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js
index 55a0b1450b..0c4f3193ad 100644
--- a/public/src/modules/composer.js
+++ b/public/src/modules/composer.js
@@ -4,6 +4,15 @@ define(['taskbar'], function(taskbar) {
 		posts: {}
 	};
 
+	function initialise() {
+		socket.on('event:composer.ping', function(post_uuid) {
+			if (composer.active !== post_uuid) {
+				socket.emit('modules.composer.pingInactive', post_uuid);
+			}
+		});
+	};
+	initialise();
+
 	function maybeParse(response) {
 		if (typeof response == 'string')  {
 			try {
@@ -380,6 +389,16 @@ define(['taskbar'], function(taskbar) {
 		} else {
 			composer.createNewComposer(post_uuid);
 		}
+
+		var	tid = templates.get('topic_id');
+		if (tid) {
+			// Replying to a topic
+			socket.emit('modules.composer.register', {
+				uuid: post_uuid,
+				tid: templates.get('topic_id'),
+				uid: app.uid
+			});
+		}
 	};
 
 	composer.createNewComposer = function(post_uuid) {
diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js
index a9d9d659be..817cca5096 100644
--- a/src/socket.io/modules.js
+++ b/src/socket.io/modules.js
@@ -18,7 +18,9 @@ var	posts = require('../posts'),
 
 /* Posts Composer */
 
-SocketModules.composer = {};
+SocketModules.composer = {
+	replyHash: {}
+};
 
 SocketModules.composer.push = function(socket, pid, callback) {
 	if (socket.uid || parseInt(meta.config.allowGuestPosting, 10)) {
@@ -74,6 +76,27 @@ SocketModules.composer.renderHelp = function(socket, data, callback) {
 	plugins.fireHook('filter:composer.help', '', callback);
 };
 
+SocketModules.composer.register = function(socket, data) {
+	server.in('topic_' + data.tid).emit('event:topic.replyStart', data.uid);
+
+	data.socket = socket;
+	data.timer = setInterval(function() {
+		// Ping the socket to see if the composer is still active
+		socket.emit('event:composer.ping', data.uuid);
+	}, 1000*10);	// Every 10 seconds...
+
+	SocketModules.composer.replyHash[data.uuid] = data;
+};
+
+SocketModules.composer.pingInactive = function(socket, uuid) {
+	var	data = SocketModules.composer.replyHash[uuid];
+	if (SocketModules.composer.replyHash[uuid]) {
+		server.in('topic_' + data.tid).emit('event:topic.replyStop', data.uid);
+		clearInterval(data.timer);
+		delete SocketModules.composer.replyHash[uuid];
+	}
+};
+
 /* Chat */
 
 SocketModules.chats = {};

From 7ef84e0daa46c8306ab2c08fb0fc0339ebc46bc3 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sat, 1 Mar 2014 16:53:41 -0500
Subject: [PATCH 134/193] switched to 'ping active' system

---
 public/src/modules/composer.js |  6 ++++--
 src/socket.io/modules.js       | 31 +++++++++++++++++++++++--------
 2 files changed, 27 insertions(+), 10 deletions(-)

diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js
index 0c4f3193ad..8b30a5aacf 100644
--- a/public/src/modules/composer.js
+++ b/public/src/modules/composer.js
@@ -6,8 +6,8 @@ define(['taskbar'], function(taskbar) {
 
 	function initialise() {
 		socket.on('event:composer.ping', function(post_uuid) {
-			if (composer.active !== post_uuid) {
-				socket.emit('modules.composer.pingInactive', post_uuid);
+			if (composer.active === post_uuid) {
+				socket.emit('modules.composer.pingActive', post_uuid);
 			}
 		});
 	};
@@ -832,6 +832,8 @@ define(['taskbar'], function(taskbar) {
 			taskbar.discard('composer', post_uuid);
 			$('body').css({'margin-bottom': 0});
 			$('.action-bar button').removeAttr('disabled');
+
+			socket.emit('modules.composer.unregister', post_uuid);
 		}
 	};
 
diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js
index 817cca5096..289a7fa12b 100644
--- a/src/socket.io/modules.js
+++ b/src/socket.io/modules.js
@@ -77,23 +77,38 @@ SocketModules.composer.renderHelp = function(socket, data, callback) {
 };
 
 SocketModules.composer.register = function(socket, data) {
+	var	now = Date.now();
 	server.in('topic_' + data.tid).emit('event:topic.replyStart', data.uid);
 
 	data.socket = socket;
+	data.lastPing = now;
+	data.lastAnswer = now;
 	data.timer = setInterval(function() {
-		// Ping the socket to see if the composer is still active
-		socket.emit('event:composer.ping', data.uuid);
-	}, 1000*10);	// Every 10 seconds...
+		if (data.lastPing === data.lastAnswer) {
+			// Ping the socket to see if the composer is still active
+			data.lastPing = Date.now();
+			socket.emit('event:composer.ping', data.uuid);
+		} else {
+			server.in('topic_' + data.tid).emit('event:topic.replyStop', data.uid);
+			delete SocketModules.composer.replyHash[data.uuid];
+		}
+	}, 1000*5);	// Every 5 seconds...
 
 	SocketModules.composer.replyHash[data.uuid] = data;
 };
 
-SocketModules.composer.pingInactive = function(socket, uuid) {
+SocketModules.composer.unregister = function(socket, uuid) {
+	var	replyObj = SocketModules.composer.replyHash[uuid];
+	if (uuid && replyObj) {
+		server.in('topic_' + replyObj.tid).emit('event:topic.replyStop', replyObj.uid);
+		delete SocketModules.composer.replyHash[replyObj.uuid];
+	}
+};
+
+SocketModules.composer.pingActive = function(socket, uuid) {
 	var	data = SocketModules.composer.replyHash[uuid];
-	if (SocketModules.composer.replyHash[uuid]) {
-		server.in('topic_' + data.tid).emit('event:topic.replyStop', data.uid);
-		clearInterval(data.timer);
-		delete SocketModules.composer.replyHash[uuid];
+	if (data) {
+		data.lastAnswer = data.lastPing;
 	}
 };
 

From 87f337f2fbb8809a5fe74d22632b5652776e33dd Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 1 Mar 2014 16:59:04 -0500
Subject: [PATCH 135/193] cleanup

---
 src/categories.js     |   9 +--
 src/database/mongo.js | 163 +++++++++++++++++++-----------------------
 src/database/redis.js |  98 ++++++++++---------------
 src/meta.js           |   6 --
 src/routes/admin.js   |  22 ------
 5 files changed, 119 insertions(+), 179 deletions(-)

diff --git a/src/categories.js b/src/categories.js
index 5a86d8532c..7709f33f64 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -1,3 +1,6 @@
+
+'use strict';
+
 var db = require('./database'),
 	posts = require('./posts'),
 	utils = require('./../public/src/utils'),
@@ -13,7 +16,6 @@ var db = require('./database'),
 	nconf = require('nconf');
 
 (function(Categories) {
-	"use strict";
 
 	Categories.create = function(data, callback) {
 		db.incrObjectField('global', 'nextCid', function(err, cid) {
@@ -161,11 +163,11 @@ var db = require('./database'),
 
 	Categories.getAllCategories = function(uid, callback) {
 		db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) {
-			if(err) {
+			if (err) {
 				return callback(err);
 			}
 
-			if(cids && cids.length === 0) {
+			if (!cids || (cids && cids.length === 0)) {
 				return callback(null, {categories : []});
 			}
 
@@ -339,7 +341,6 @@ var db = require('./database'),
 				'categories': categories
 			});
 		});
-
 	};
 
 	Categories.isUserActiveIn = function(cid, uid, callback) {
diff --git a/src/database/mongo.js b/src/database/mongo.js
index d80cd35488..e6b3050b08 100644
--- a/src/database/mongo.js
+++ b/src/database/mongo.js
@@ -1,7 +1,8 @@
 
+'use strict';
 
 (function(module) {
-	'use strict';
+
 	var winston = require('winston'),
 		async = require('async'),
 		nconf = require('nconf'),
@@ -68,15 +69,15 @@
 				});
 
 				if(typeof callback === 'function') {
-					callback(null);
+					callback();
 				}
 			}
 		});
-	}
+	};
 
 	module.close = function() {
 		db.close();
-	}
+	};
 
 	//
 	// helper functions
@@ -129,7 +130,7 @@
 				winston.error('Error indexing ' + err.message);
 			}
 		});
-	}
+	};
 
 	module.search = function(key, term, limit, callback) {
 		db.command({text:"search" , search: term, filter: {key:key}, limit: limit }, function(err, result) {
@@ -150,7 +151,7 @@
 				callback(null, []);
 			}
 		});
-	}
+	};
 
 	module.searchRemove = function(key, id, callback) {
 		db.collection('search').remove({id:id, key:key}, function(err, result) {
@@ -161,27 +162,22 @@
 				callback();
 			}
 		});
-	}
+	};
 
 	module.flushdb = function(callback) {
 		db.dropDatabase(function(err, result) {
-			if(err){
-				winston.error(error);
-				if(typeof callback === 'function') {
+			if (err) {
+				winston.error(err.message);
+				if (typeof callback === 'function') {
 					return callback(err);
 				}
 			}
 
-			if(typeof callback === 'function') {
-				callback(null);
+			if (typeof callback === 'function') {
+				callback();
 			}
 		});
-	}
-
-
-	module.getFileName = function(callback) {
-		throw new Error('not-implemented');
-	}
+	};
 
 	module.info = function(callback) {
 		db.stats({scale:1024}, function(err, stats) {
@@ -189,10 +185,6 @@
 				return callback(err);
 			}
 
-			// TODO : if this it not deleted the templates break,
-			// it is a nested object inside stats
-			delete stats.dataFileVersion;
-
 			stats.avgObjSize = (stats.avgObjSize / 1024).toFixed(2);
 
 			stats.raw = JSON.stringify(stats, null, 4);
@@ -201,7 +193,7 @@
 
 			callback(null, stats);
 		});
-	}
+	};
 
 	// key
 
@@ -209,7 +201,7 @@
 		db.collection('objects').findOne({_key:key}, function(err, item) {
 			callback(err, item !== undefined && item !== null);
 		});
-	}
+	};
 
 	module.delete = function(key, callback) {
 		db.collection('objects').remove({_key:key}, function(err, result) {
@@ -217,22 +209,22 @@
 				callback(err, result);
 			}
 		});
-	}
+	};
 
 	module.get = function(key, callback) {
 		module.getObjectField(key, 'value', callback);
-	}
+	};
 
 	module.set = function(key, value, callback) {
 		var data = {value:value};
 		module.setObject(key, data, callback);
-	}
+	};
 
 	module.keys = function(key, callback) {
 		db.collection('objects').find( { _key: { $regex: key /*, $options: 'i'*/ } }, function(err, result) {
 			callback(err, result);
 		});
-	}
+	};
 
 	module.rename = function(oldKey, newKey, callback) {
 		db.collection('objects').update({_key: oldKey}, {$set:{_key: newKey}}, function(err, result) {
@@ -240,25 +232,25 @@
 				callback(err, result);
 			}
 		});
-	}
+	};
 
 	module.expire = function(key, seconds, callback) {
 		module.expireAt(key, Math.round(Date.now() / 1000) + seconds, callback);
-	}
+	};
 
 	module.expireAt = function(key, timestamp, callback) {
 		module.setObjectField(key, 'expireAt', new Date(timestamp * 1000), callback);
-	}
+	};
 
 	//hashes
 	module.setObject = function(key, data, callback) {
-		data['_key'] = key;
+		data._key = key;
 		db.collection('objects').update({_key:key}, {$set:data}, {upsert:true, w: 1}, function(err, result) {
 			if(typeof callback === 'function') {
 				callback(err, result);
 			}
 		});
-	}
+	};
 
 	module.setObjectField = function(key, field, value, callback) {
 		var data = {};
@@ -273,7 +265,7 @@
 				callback(err, result);
 			}
 		});
-	}
+	};
 
 	module.getObject = function(key, callback) {
 		db.collection('objects').findOne({_key:key}, function(err, item) {
@@ -281,7 +273,7 @@
 
 			callback(err, item);
 		});
-	}
+	};
 
 	module.getObjects = function(keys, callback) {
 
@@ -299,7 +291,7 @@
 
 			callback(null, returnData);
 		});
-	}
+	};
 
 	module.getObjectField = function(key, field, callback) {
 		module.getObjectFields(key, [field], function(err, data) {
@@ -309,7 +301,7 @@
 
 			callback(null, data[field]);
 		});
-	}
+	};
 
 	module.getObjectFields = function(key, fields, callback) {
 
@@ -342,7 +334,7 @@
 
 			callback(null, item);
 		});
-	}
+	};
 
 	module.getObjectKeys = function(key, callback) {
 		module.getObject(key, function(err, data) {
@@ -356,7 +348,7 @@
 				callback(null, []);
 			}
 		});
-	}
+	};
 
 	module.getObjectValues = function(key, callback) {
 		module.getObject(key, function(err, data) {
@@ -370,7 +362,7 @@
 			}
 			callback(null, values);
 		});
-	}
+	};
 
 	module.isObjectField = function(key, field, callback) {
 		var data = {};
@@ -385,7 +377,7 @@
 			}
 			callback(err, !!item && item[field] !== undefined && item[field] !== null);
 		});
-	}
+	};
 
 	module.deleteObjectField = function(key, field, callback) {
 		var data = {};
@@ -399,15 +391,15 @@
 				callback(err, result);
 			}
 		});
-	}
+	};
 
 	module.incrObjectField = function(key, field, callback) {
 		module.incrObjectFieldBy(key, field, 1, callback);
-	}
+	};
 
 	module.decrObjectField = function(key, field, callback) {
 		module.incrObjectFieldBy(key, field, -1, callback);
-	}
+	};
 
 	module.incrObjectFieldBy = function(key, field, value, callback) {
 		var data = {};
@@ -422,7 +414,7 @@
 				callback(err, result ? result[field] : null);
 			}
 		});
-	}
+	};
 
 
 	// sets
@@ -437,19 +429,12 @@
 		});
 
 
-		db.collection('objects').update({_key:key},
-			{
-				$addToSet: { members: { $each: value } }
-			},
-			{
-				upsert:true, w: 1
-			}
-		, function(err, result) {
+		db.collection('objects').update({_key: key}, { $addToSet: { members: { $each: value } }	}, { upsert: true, w: 1 }, function(err, result) {
 			if(typeof callback === 'function') {
 				callback(err, result);
 			}
 		});
-	}
+	};
 
 	module.setRemove = function(key, value, callback) {
 		if(!Array.isArray(value)) {
@@ -465,7 +450,7 @@
 				callback(err, result);
 			}
 		});
-	}
+	};
 
 	module.isSetMember = function(key, value, callback) {
 		if(value !== null && value !== undefined) {
@@ -475,7 +460,7 @@
 		db.collection('objects').findOne({_key:key, members: value}, function(err, item) {
 			callback(err, item !== null && item !== undefined);
 		});
-	}
+	};
 
 	module.isMemberOfSets = function(sets, value, callback) {
 
@@ -498,7 +483,7 @@
 
 			callback(err, result);
 		});
-	}
+	};
 
 	module.getSetMembers = function(key, callback) {
 		db.collection('objects').findOne({_key:key}, {members:1}, function(err, data) {
@@ -512,7 +497,7 @@
 				callback(null, data.members);
 			}
 		});
-	}
+	};
 
 	module.setCount = function(key, callback) {
 		db.collection('objects').findOne({_key:key}, function(err, data) {
@@ -525,7 +510,7 @@
 
 			callback(null, data.members.length);
 		});
-	}
+	};
 
 	module.setRemoveRandom = function(key, callback) {
 		db.collection('objects').findOne({_key:key}, function(err, data) {
@@ -551,7 +536,7 @@
 				});
 			}
 		});
-	}
+	};
 
 
 	// sorted sets
@@ -570,7 +555,7 @@
 				callback(err, result);
 			}
 		});
-	}
+	};
 
 	module.sortedSetRemove = function(key, value, callback) {
 		if(value !== null && value !== undefined) {
@@ -582,7 +567,7 @@
 				callback(err, result);
 			}
 		});
-	}
+	};
 
 	function getSortedSetRange(key, start, stop, sort, callback) {
 		db.collection('objects').find({_key:key}, {fields:{value:1}})
@@ -590,26 +575,29 @@
 			.skip(start)
 			.sort({score: sort})
 			.toArray(function(err, data) {
-				if(err) {
+				if (err) {
 					return callback(err);
 				}
 
-				// maybe this can be done with mongo?
+				if (!data) {
+					return callback(null, null);
+				}
+
 				data = data.map(function(item) {
 					return item.value;
 				});
 
-				callback(err, data);
+				callback(null, data);
 			});
 	}
 
 	module.getSortedSetRange = function(key, start, stop, callback) {
 		getSortedSetRange(key, start, stop, 1, callback);
-	}
+	};
 
 	module.getSortedSetRevRange = function(key, start, stop, callback) {
 		getSortedSetRange(key, start, stop, -1, callback);
-	}
+	};
 
 	module.getSortedSetRevRangeByScore = function(args, callback) {
 
@@ -640,7 +628,7 @@
 
 				callback(err, data);
 			});
-	}
+	};
 
 	module.sortedSetCount = function(key, min, max, callback) {
 		db.collection('objects').count({_key:key, score: {$gte:min, $lte:max}}, function(err, count) {
@@ -653,7 +641,7 @@
 			}
 			callback(null,count);
 		});
-	}
+	};
 
 	module.sortedSetCard = function(key, callback) {
 		db.collection('objects').count({_key:key}, function(err, count) {
@@ -666,7 +654,7 @@
 			}
 			callback(null, count);
 		});
-	}
+	};
 
 	module.sortedSetRank = function(key, value, callback) {
 		if(value !== null && value !== undefined) {
@@ -683,7 +671,7 @@
 
 			callback(null, rank);
 		});
-	}
+	};
 
 	module.sortedSetRevRank = function(key, value, callback) {
 		if(value !== null && value !== undefined) {
@@ -702,7 +690,7 @@
 
 			callback(null, rank);
 		});
-	}
+	};
 
 	module.sortedSetScore = function(key, value, callback) {
 		if(value !== null && value !== undefined) {
@@ -718,13 +706,13 @@
 
 			callback(err, null);
 		});
-	}
+	};
 
 	module.isSortedSetMember = function(key, value, callback) {
 		module.sortedSetScore(key, value, function(err, score) {
 			callback(err, !!score);
 		});
-	}
+	};
 
 	module.sortedSetsScore = function(keys, value, callback) {
 		if(value !== null && value !== undefined) {
@@ -745,13 +733,14 @@
 
 			callback(null, returnData);
 		});
-	}
+	};
 
 	// lists
 	module.listPrepend = function(key, value, callback) {
 		if(value !== null && value !== undefined) {
 			value = value.toString();
 		}
+
 		module.isObjectField(key, 'array', function(err, exists) {
 			if(err) {
 				if(typeof callback === 'function') {
@@ -762,17 +751,16 @@
 			}
 
 			if(exists) {
- 				db.collection('objects').update({_key:key}, {'$set': {'array.-1': value}}, {upsert:true, w:1 }, function(err, result) {
+				db.collection('objects').update({_key:key}, {'$set': {'array.-1': value}}, {upsert:true, w:1 }, function(err, result) {
 					if(typeof callback === 'function') {
 						callback(err, result);
 					}
-	 			});
- 			} else {
- 				module.listAppend(key, value, callback);
- 			}
-
- 		})
-	}
+				});
+			} else {
+				module.listAppend(key, value, callback);
+			}
+		});
+	};
 
 	module.listAppend = function(key, value, callback) {
 		if(value !== null && value !== undefined) {
@@ -783,7 +771,7 @@
 				callback(err, result);
 			}
 		});
-	}
+	};
 
 	module.listRemoveLast = function(key, callback) {
 		module.getListRange(key, -1, 0, function(err, value) {
@@ -808,7 +796,7 @@
 				}
 			});
 		});
-	}
+	};
 
 	module.listRemoveAll = function(key, value, callback) {
 		if(value !== null && value !== undefined) {
@@ -820,7 +808,7 @@
 				callback(err, result);
 			}
 		});
-	}
+	};
 
 	module.getListRange = function(key, start, stop, callback) {
 
@@ -867,8 +855,7 @@
 				callback(null, []);
 			}
 		});
-	}
-
+	};
 
 }(exports));
 
diff --git a/src/database/redis.js b/src/database/redis.js
index 57debf6efa..4655eabb62 100644
--- a/src/database/redis.js
+++ b/src/database/redis.js
@@ -11,7 +11,9 @@
 		redis,
 		connectRedis,
 		reds,
-		redisClient;
+		redisClient,
+		postSearch,
+		topicSearch;
 
 	try {
 		redis = require('redis');
@@ -22,54 +24,52 @@
 		process.exit();
 	}
 
+	module.init = function(callback) {
+		if (redis_socket_or_host && redis_socket_or_host.indexOf('/')>=0) {
+			/* If redis.host contains a path name character, use the unix dom sock connection. ie, /tmp/redis.sock */
+			redisClient = redis.createClient(nconf.get('redis:host'));
+		} else {
+			/* Else, connect over tcp/ip */
+			redisClient = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host'));
+		}
 
-	if (redis_socket_or_host && redis_socket_or_host.indexOf('/')>=0) {
-		/* If redis.host contains a path name character, use the unix dom sock connection. ie, /tmp/redis.sock */
-		redisClient = redis.createClient(nconf.get('redis:host'));
-	} else {
-		/* Else, connect over tcp/ip */
-		redisClient = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host'));
-	}
-
-	if (nconf.get('redis:password')) {
-		redisClient.auth(nconf.get('redis:password'));
-	} else {
-		winston.warn('You have no redis password setup!');
-	}
-
-	redisClient.on('error', function (err) {
-		winston.error(err.message);
-		process.exit();
-	});
+		if (nconf.get('redis:password')) {
+			redisClient.auth(nconf.get('redis:password'));
+		} else {
+			winston.warn('You have no redis password setup!');
+		}
 
+		redisClient.on('error', function (err) {
+			winston.error(err.message);
+			process.exit();
+		});
 
-	module.client = redisClient;
+		module.client = redisClient;
 
-	module.sessionStore = new connectRedis({
-		client: redisClient,
-		ttl: 60 * 60 * 24 * 14
-	});
+		module.sessionStore = new connectRedis({
+			client: redisClient,
+			ttl: 60 * 60 * 24 * 14
+		});
 
-	reds.createClient = function () {
-		return reds.client || (reds.client = redisClient);
-	};
+		reds.createClient = function () {
+			return reds.client || (reds.client = redisClient);
+		};
 
-	var	postSearch = reds.createSearch('nodebbpostsearch'),
+		postSearch = reds.createSearch('nodebbpostsearch'),
 		topicSearch = reds.createSearch('nodebbtopicsearch');
 
-	var db = parseInt(nconf.get('redis:database'), 10);
+		var db = parseInt(nconf.get('redis:database'), 10);
 
-	if (db) {
-		redisClient.select(db, function(error) {
-			if(error) {
-				winston.error("NodeBB could not connect to your Redis database. Redis returned the following error: " + error.message);
-				process.exit();
-			}
-		});
-	}
+		if (db) {
+			redisClient.select(db, function(error) {
+				if(error) {
+					winston.error("NodeBB could not connect to your Redis database. Redis returned the following error: " + error.message);
+					process.exit();
+				}
+			});
+		}
 
-	module.init = function(callback) {
-		callback(null);
+		callback();
 	};
 
 	module.close = function() {
@@ -125,26 +125,6 @@
 		});
 	};
 
-	module.getFileName = function(callback) {
-		var multi = redisClient.multi();
-
-		multi.config('get', 'dir');
-		multi.config('get', 'dbfilename');
-		multi.exec(function (err, results) {
-			if (err) {
-				return callback(err);
-			}
-
-			results = results.reduce(function (memo, config) {
-				memo[config[0]] = config[1];
-				return memo;
-			}, {});
-
-			var dbFile = path.join(results.dir, results.dbfilename);
-			callback(null, dbFile);
-		});
-	};
-
 	module.info = function(callback) {
 		redisClient.info(function (err, data) {
 			if(err) {
diff --git a/src/meta.js b/src/meta.js
index 93f53e04d3..b8c12a0721 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -297,12 +297,6 @@ var fs = require('fs'),
 		}
 	};
 
-	Meta.db = {
-		getFile: function (callback) {
-			db.getFileName(callback);
-		}
-	};
-
 	Meta.css = {
 		cache: undefined
 	};
diff --git a/src/routes/admin.js b/src/routes/admin.js
index 1741f62c6f..d4cd723c21 100644
--- a/src/routes/admin.js
+++ b/src/routes/admin.js
@@ -347,28 +347,6 @@ var nconf = require('nconf'),
 						res.json(data);
 					});
 				});
-
-				// app.get('/export', function (req, res) {
-				// 	meta.db.getFile(function (err, dbFile) {
-				// 		if (!err) {
-				// 			res.download(dbFile, 'redis.rdb', function (err) {
-				// 				console.log(err);
-				// 				res.send(500);
-				// 				if (err) {
-				// 					res.send(500);
-				// 					switch (err.code) {
-				// 					case 'EACCES':
-				// 						res.send(500, 'Require permissions from Redis database file: ', dbFile);
-				// 						break;
-				// 					default:
-				// 						res.send(500);
-				// 						break;
-				// 					}
-				// 				}
-				// 			});
-				// 		} else res.send(500);
-				// 	});
-				// });
 			});
 
 			app.get('/events', function(req, res, next) {

From d012d237bfb11c8621221e1863cd2114ebbcee01 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sat, 1 Mar 2014 17:05:57 -0500
Subject: [PATCH 136/193] added back clearInterval

---
 src/socket.io/modules.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js
index 289a7fa12b..ac60ef9884 100644
--- a/src/socket.io/modules.js
+++ b/src/socket.io/modules.js
@@ -101,6 +101,7 @@ SocketModules.composer.unregister = function(socket, uuid) {
 	var	replyObj = SocketModules.composer.replyHash[uuid];
 	if (uuid && replyObj) {
 		server.in('topic_' + replyObj.tid).emit('event:topic.replyStop', replyObj.uid);
+		clearInterval(replyObj.timer);
 		delete SocketModules.composer.replyHash[replyObj.uuid];
 	}
 };

From 3c6e4ebda1e8f3b3289e0e88ed1e4322c178de86 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 1 Mar 2014 17:11:49 -0500
Subject: [PATCH 137/193] possible fix to #1148

---
 public/src/forum/reset.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/src/forum/reset.js b/public/src/forum/reset.js
index f67e1a28b8..445d8c897f 100644
--- a/public/src/forum/reset.js
+++ b/public/src/forum/reset.js
@@ -7,7 +7,7 @@ define(function() {
 			successEl = $('#success'),
 			errorTextEl = errorEl.find('p');
 
-		$('#reset').onclick = function() {
+		$('#reset').on('click', function() {
 			if (inputEl.val() && inputEl.val().indexOf('@') !== -1) {
 				socket.emit('user.reset.send', {
 					email: inputEl.val()

From f2ffc2b5335d46b98e6ce8257b37d07a4fcedb07 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sat, 1 Mar 2014 17:34:06 -0500
Subject: [PATCH 138/193] properly referencing the tid of the composer instead
 of blindly checking templates.get('topic_id')

---
 public/src/modules/composer.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js
index 8b30a5aacf..07bbee5c88 100644
--- a/public/src/modules/composer.js
+++ b/public/src/modules/composer.js
@@ -390,12 +390,13 @@ define(['taskbar'], function(taskbar) {
 			composer.createNewComposer(post_uuid);
 		}
 
-		var	tid = templates.get('topic_id');
+		var	tid = templates.get('topic_id'),
+			postData = composer.posts[post_uuid];
 		if (tid) {
 			// Replying to a topic
 			socket.emit('modules.composer.register', {
 				uuid: post_uuid,
-				tid: templates.get('topic_id'),
+				tid: postData.tid,
 				uid: app.uid
 			});
 		}

From 42a7c037e6d4efa0df873447a71d5db7607771a7 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 1 Mar 2014 17:36:29 -0500
Subject: [PATCH 139/193] removed dupe i var

---
 src/categories.js | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/categories.js b/src/categories.js
index 7709f33f64..fb8e9b5990 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -104,12 +104,13 @@ var db = require('./database'),
 					});
 				}
 
-				var indices = {};
-				for(var i=0; i<tids.length; ++i) {
+				var indices = {},
+					i = 0;
+				for(i=0; i<tids.length; ++i) {
 					indices[tids[i]] = start + i;
 				}
 
-				for(var i=0; i<topics.length; ++i) {
+				for(i=0; i<topics.length; ++i) {
 					topics[i].index = indices[topics[i].tid];
 				}
 

From a9b78d2600dde260cdb30ed6fbbdbffa8279c27f Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sat, 1 Mar 2014 17:49:39 -0500
Subject: [PATCH 140/193] minimizing the composer should unregister it

---
 public/src/modules/composer.js | 7 ++++---
 src/socket.io/modules.js       | 1 +
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js
index 07bbee5c88..c9457aa38a 100644
--- a/public/src/modules/composer.js
+++ b/public/src/modules/composer.js
@@ -390,9 +390,8 @@ define(['taskbar'], function(taskbar) {
 			composer.createNewComposer(post_uuid);
 		}
 
-		var	tid = templates.get('topic_id'),
-			postData = composer.posts[post_uuid];
-		if (tid) {
+		var	postData = composer.posts[post_uuid];
+		if (postData.tid) {
 			// Replying to a topic
 			socket.emit('modules.composer.register', {
 				uuid: post_uuid,
@@ -843,6 +842,8 @@ define(['taskbar'], function(taskbar) {
 		postContainer.css('visibility', 'hidden');
 		composer.active = undefined;
 		taskbar.minimize('composer', post_uuid);
+
+		socket.emit('modules.composer.unregister', post_uuid);
 	};
 
 	return {
diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js
index ac60ef9884..8566e65786 100644
--- a/src/socket.io/modules.js
+++ b/src/socket.io/modules.js
@@ -78,6 +78,7 @@ SocketModules.composer.renderHelp = function(socket, data, callback) {
 
 SocketModules.composer.register = function(socket, data) {
 	var	now = Date.now();
+
 	server.in('topic_' + data.tid).emit('event:topic.replyStart', data.uid);
 
 	data.socket = socket;

From b6d97281d39c5203eb5304149eac302bd214c37e Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 1 Mar 2014 19:15:18 -0500
Subject: [PATCH 141/193] closes #1015

---
 src/postTools.js       | 124 +++++++++++++++++++++++------------------
 src/posts.js           |  83 +++++++++++++--------------
 src/socket.io/posts.js |  16 +++++-
 3 files changed, 121 insertions(+), 102 deletions(-)

diff --git a/src/postTools.js b/src/postTools.js
index b610e19415..cc97e02532 100644
--- a/src/postTools.js
+++ b/src/postTools.js
@@ -1,3 +1,5 @@
+'use strict';
+
 var winston = require('winston'),
 	async = require('async'),
 	nconf = require('nconf'),
@@ -14,15 +16,20 @@ var winston = require('winston'),
 	meta = require('./meta');
 
 (function(PostTools) {
+
 	PostTools.isMain = function(pid, tid, callback) {
 		db.getSortedSetRange('tid:' + tid + ':posts', 0, 0, function(err, pids) {
 			if(err) {
 				return callback(err);
 			}
 
+			if(!Array.isArray(pids) || !pids.length) {
+				callback(null, false);
+			}
+
 			callback(null, parseInt(pids[0], 10) === parseInt(pid, 10));
 		});
-	}
+	};
 
 	PostTools.privileges = function(pid, uid, callback) {
 		async.parallel({
@@ -48,7 +55,6 @@ var winston = require('winston'),
 					});
 				}
 			}
-			// [getThreadPrivileges, isOwnPost, hasEnoughRep]
 		}, function(err, results) {
 			if(err) {
 				return callback(err);
@@ -61,71 +67,78 @@ var winston = require('winston'),
 				move: results.topicPrivs.admin || results.topicPrivs.moderator
 			});
 		});
-	}
+	};
 
 
-	PostTools.edit = function(uid, pid, title, content, options) {
-		options || (options = {});
+	PostTools.edit = function(uid, pid, title, content, options, callback) {
+		options = options || (options = {});
 
-		var	websockets = require('./socket.io'),
-			success = function() {
-				posts.setPostFields(pid, {
-					edited: Date.now(),
-					editor: uid,
-					content: content
-				});
+		function success(postData) {
+			posts.setPostFields(pid, {
+				edited: Date.now(),
+				editor: uid,
+				content: postData.content
+			});
 
-				events.logPostEdit(uid, pid);
+			events.logPostEdit(uid, pid);
 
-				async.parallel([
-					function(next) {
-						posts.getPostField(pid, 'tid', function(err, tid) {
-							PostTools.isMain(pid, tid, function(err, isMainPost) {
-								if (isMainPost) {
-									title = title.trim();
-									var slug = tid + '/' + utils.slugify(title);
+			async.parallel({
+				topic: function(next) {
+					var tid = postData.tid;
+					PostTools.isMain(pid, tid, function(err, isMainPost) {
+						if (err) {
+							return next(err);
+						}
+
+						if (isMainPost) {
+							title = title.trim();
+							var slug = tid + '/' + utils.slugify(title);
 
-									topics.setTopicField(tid, 'title', title);
-									topics.setTopicField(tid, 'slug', slug);
+							topics.setTopicField(tid, 'title', title);
+							topics.setTopicField(tid, 'slug', slug);
 
-									topics.setTopicField(tid, 'thumb', options.topic_thumb);
+							topics.setTopicField(tid, 'thumb', options.topic_thumb);
 
-									plugins.fireHook('action:topic.edit', tid);
-								}
+							plugins.fireHook('action:topic.edit', tid);
+						}
 
-								posts.getPostData(pid, function(err, postData) {
-									plugins.fireHook('action:post.edit', postData);
-								});
+						plugins.fireHook('action:post.edit', postData);
 
-								next(null, {
-									tid: tid,
-									isMainPost: isMainPost
-								});
-							});
+						next(null, {
+							tid: tid,
+							title: validator.escape(title),
+							isMainPost: isMainPost
 						});
-					},
-					function(next) {
-						PostTools.parse(content, next);
-					}
-				], function(err, results) {
-					websockets.in('topic_' + results[0].tid).emit('event:post_edited', {
-						pid: pid,
-						title: validator.escape(title),
-						isMainPost: results[0].isMainPost,
-						content: results[1]
 					});
-				});
-			};
+
+				},
+				content: function(next) {
+					PostTools.parse(postData.content, next);
+				}
+			}, callback);
+		}
 
 		PostTools.privileges(pid, uid, function(err, privileges) {
-			if (privileges.editable) {
-				plugins.fireHook('filter:post.save', content, function(err, parsedContent) {
-					if (!err) content = parsedContent;
-					success();
-				});
+			if (err || !privileges.editable) {
+				return callback(err || new Error('not-privileges-to-edit'));
 			}
+
+			posts.getPostData(pid, function(err, postData) {
+				if (err) {
+					return callback(err);
+				}
+
+				postData.content = content;
+				plugins.fireHook('filter:post.save', postData, function(err, postData) {
+					if (err) {
+						return callback(err);
+					}
+
+					success(postData);
+				});
+			});
 		});
-	}
+	};
 
 	PostTools.delete = function(uid, pid, callback) {
 		var success = function() {
@@ -183,7 +196,7 @@ var winston = require('winston'),
 				}
 			});
 		});
-	}
+	};
 
 	PostTools.restore = function(uid, pid, callback) {
 		var success = function() {
@@ -238,7 +251,7 @@ var winston = require('winston'),
 				}
 			});
 		});
-	}
+	};
 
 	PostTools.parse = function(raw, callback) {
 		raw = raw || '';
@@ -246,7 +259,7 @@ var winston = require('winston'),
 		plugins.fireHook('filter:post.parse', raw, function(err, parsed) {
 			callback(null, !err ? parsed : raw);
 		});
-	}
+	};
 
 	PostTools.parseSignature = function(raw, callback) {
 		raw = raw || '';
@@ -254,5 +267,6 @@ var winston = require('winston'),
 		plugins.fireHook('filter:post.parseSignature', raw, function(err, parsedSignature) {
 			callback(null, !err ? parsedSignature : raw);
 		});
-	}
+	};
+
 }(exports));
diff --git a/src/posts.js b/src/posts.js
index ac428870d0..0afee2166f 100644
--- a/src/posts.js
+++ b/src/posts.js
@@ -29,9 +29,12 @@ var db = require('./database'),
 			toPid = data.toPid;
 
 		if (uid === null) {
-			return callback(new Error('invalid-user'), null);
+			return callback(new Error('invalid-user'));
 		}
 
+		var timestamp = Date.now(),
+			postData;
+
 		async.waterfall([
 			function(next) {
 				topics.isLocked(tid, next);
@@ -44,46 +47,38 @@ var db = require('./database'),
 				db.incrObjectField('global', 'nextPid', next);
 			},
 			function(pid, next) {
-				plugins.fireHook('filter:post.save', content, function(err, newContent) {
-					next(err, pid, newContent);
-				});
-			},
-			function(pid, newContent, next) {
-				var timestamp = Date.now(),
-					postData = {
-						'pid': pid,
-						'uid': uid,
-						'tid': tid,
-						'content': newContent,
-						'timestamp': timestamp,
-						'reputation': '0',
-						'votes': '0',
-						'editor': '',
-						'edited': 0,
-						'deleted': 0
-					};
+
+				postData = {
+					'pid': pid,
+					'uid': uid,
+					'tid': tid,
+					'content': content,
+					'timestamp': timestamp,
+					'reputation': 0,
+					'votes': 0,
+					'editor': '',
+					'edited': 0,
+					'deleted': 0
+				};
 
 				if (toPid) {
 					postData.toPid = toPid;
 				}
 
-				db.setObject('post:' + pid, postData, function(err) {
-					if(err) {
-						return next(err);
-					}
-
-					db.sortedSetAdd('posts:pid', timestamp, pid);
+				plugins.fireHook('filter:post.save', postData, next);
+			},
+			function(postData, next) {
+				db.setObject('post:' + postData.pid, postData, next);
+			},
+			function(result, next) {
+				db.sortedSetAdd('posts:pid', timestamp, postData.pid);
 
-					db.incrObjectField('global', 'postCount');
+				db.incrObjectField('global', 'postCount');
 
-					topics.onNewPostMade(tid, pid, timestamp);
-					categories.onNewPostMade(uid, tid, pid, timestamp);
-					user.onNewPostMade(uid, tid, pid, timestamp);
+				topics.onNewPostMade(tid, postData.pid, timestamp);
+				categories.onNewPostMade(uid, tid, postData.pid, timestamp);
+				user.onNewPostMade(uid, tid, postData.pid, timestamp);
 
-					next(null, postData);
-				});
-			},
-			function(postData, next) {
 				plugins.fireHook('filter:post.get', postData, next);
 			},
 			function(postData, next) {
@@ -103,36 +98,34 @@ var db = require('./database'),
 	};
 
 	Posts.getPostsByTid = function(tid, start, end, reverse, callback) {
-		if (typeof reverse === 'function') {
-			callback = reverse;
-			reverse = false;
-		}
-
 		db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange']('tid:' + tid + ':posts', start, end, function(err, pids) {
 			if(err) {
 				return callback(err);
 			}
 
-			if(!pids.length) {
+			if(!Array.isArray(pids) || !pids.length) {
 				return callback(null, []);
 			}
 
-			plugins.fireHook('filter:post.getTopic', pids, function(err, posts) {
+			Posts.getPostsByPids(pids, function(err, posts) {
 				if(err) {
 					return callback(err);
 				}
 
-				if(!posts.length) {
+				if(!Array.isArray(posts) || !posts.length) {
 					return callback(null, []);
 				}
 
-
-				Posts.getPostsByPids(pids, function(err, posts) {
+				plugins.fireHook('filter:post.getPosts', {tid: tid, posts: posts}, function(err, data) {
 					if(err) {
 						return callback(err);
 					}
-					plugins.fireHook('action:post.gotTopic', posts);
-					callback(null, posts);
+
+					if(!data || !Array.isArray(data.posts)) {
+						return callback(null, []);
+					}
+
+					callback(null, data.posts);
 				});
 			});
 		});
diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js
index df0ebfeebd..c29df33ad8 100644
--- a/src/socket.io/posts.js
+++ b/src/socket.io/posts.js
@@ -151,8 +151,20 @@ SocketPosts.edit = function(socket, data, callback) {
 		return callback(new Error('content-too-short'));
 	}
 
-	postTools.edit(socket.uid, data.pid, data.title, data.content, {topic_thumb: data.topic_thumb});
-	callback();
+	postTools.edit(socket.uid, data.pid, data.title, data.content, {topic_thumb: data.topic_thumb}, function(err, results) {
+		if(err) {
+			return callback(err);
+		}
+
+		index.server.sockets.in('topic_' + results.topic.tid).emit('event:post_edited', {
+			pid: data.pid,
+			title: results.topic.title,
+			isMainPost: results.topic.isMainPost,
+			content: results.content
+		});
+
+		callback();
+	});
 };
 
 SocketPosts.delete = function(socket, data, callback) {

From 2966cc4a4957091641d4c94d9f7e90d4d39bef4a Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 1 Mar 2014 19:18:15 -0500
Subject: [PATCH 142/193] minor fix

---
 src/postTools.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/postTools.js b/src/postTools.js
index cc97e02532..814ea277a2 100644
--- a/src/postTools.js
+++ b/src/postTools.js
@@ -71,7 +71,7 @@ var winston = require('winston'),
 
 
 	PostTools.edit = function(uid, pid, title, content, options, callback) {
-		options = options || (options = {});
+		options = options || {};
 
 		function success(postData) {
 			posts.setPostFields(pid, {

From 2b178ff76d73b96036f76d0fca8478199c098f83 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sat, 1 Mar 2014 21:31:50 -0500
Subject: [PATCH 143/193] proper tracking of users' reply status when others
 enter the room

---
 public/src/forum/topic.js | 28 +++++++++++++++++--------
 src/socket.io/modules.js  | 43 ++++++++++++++++++++++++++++++++++-----
 2 files changed, 57 insertions(+), 14 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 4ebfb4aae7..6caea47b10 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -618,7 +618,6 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		]);
 
 		socket.on('get_users_in_room', function(data) {
-
 			if(data && data.room.indexOf('topic') !== -1) {
 				var activeEl = $('li.post-bar[data-index="0"] .thread_active_users');
 
@@ -696,6 +695,17 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 						title: title
 					});
 				}
+
+				// Get users who are currently replying to the topic entered
+				socket.emit('modules.composer.getUsersByTid', templates.get('topic_id'), function(err, uids) {
+					var	activeUsersEl = $('.thread_active_users'),
+						x;
+					if (uids && uids.length) {
+						for(var x=0;x<uids.length;x++) {
+							activeUsersEl.find('[data-uid="' + uids[x] + '"]').addClass('replying');
+						}
+					}
+				});
 			}
 
 			app.populateOnlineUsers();
@@ -896,7 +906,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 
 			votes.html(currentVotes).attr('data-votes', currentVotes);
 			reputationElements.html(reputation).attr('data-reputation', reputation);
-		}
+		};
 
 		function adjust_favourites(value, pid, uid) {
 			var favourites = $('li[data-pid="' + pid + '"] .favouriteCount'),
@@ -905,7 +915,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			currentFavourites += value;
 
 			favourites.html(currentFavourites).attr('data-favourites', currentFavourites);
-		}
+		};
 
 		function set_follow_state(state, alert) {
 
@@ -920,7 +930,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					type: 'success'
 				});
 			}
-		}
+		};
 
 		function set_locked_state(locked, alert) {
 			translator.translate('<i class="fa fa-fw fa-' + (locked ? 'un': '') + 'lock"></i> [[topic:thread_tools.' + (locked ? 'un': '') + 'lock]]', function(translated) {
@@ -943,7 +953,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			}
 
 			thread_state.locked = locked ? '1' : '0';
-		}
+		};
 
 		function set_delete_state(deleted) {
 			var threadEl = $('#post-container');
@@ -960,7 +970,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			} else {
 				$('#thread-deleted').remove();
 			}
-		}
+		};
 
 		function set_pinned_state(pinned, alert) {
 			translator.translate('<i class="fa fa-fw fa-thumb-tack"></i> [[topic:thread_tools.' + (pinned ? 'unpin' : 'pin') + ']]', function(translated) {
@@ -977,7 +987,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				}
 				thread_state.pinned = pinned ? '1' : '0';
 			});
-		}
+		};
 
 		function toggle_post_delete_state(pid) {
 			var postEl = $('#post-container li[data-pid="' + pid + '"]');
@@ -989,7 +999,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 
 				updatePostCount();
 			}
-		}
+		};
 
 		function toggle_post_tools(pid, isDeleted) {
 			var postEl = $('#post-container li[data-pid="' + pid + '"]');
@@ -999,7 +1009,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			translator.translate(isDeleted ? ' [[topic:restore]]' : ' [[topic:delete]]', function(translated) {
 				postEl.find('.delete').find('span').html(translated);
 			});
-		}
+		};
 
 		$(window).on('scroll', updateHeader);
 		$(window).trigger('action:topic.loaded');
diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js
index 8566e65786..d931185135 100644
--- a/src/socket.io/modules.js
+++ b/src/socket.io/modules.js
@@ -12,6 +12,7 @@ var	posts = require('../posts'),
 	async = require('async'),
 	S = require('string'),
 	winston = require('winston'),
+	_ = require('underscore'),
 	server = require('./'),
 
 	SocketModules = {};
@@ -22,6 +23,27 @@ SocketModules.composer = {
 	replyHash: {}
 };
 
+var	stopTracking = function(replyObj) {
+		if (isLast(replyObj.uid, replyObj.tid)) {
+			server.in('topic_' + replyObj.tid).emit('event:topic.replyStop', replyObj.uid);
+		}
+
+		clearInterval(replyObj.timer);
+		delete SocketModules.composer.replyHash[replyObj.uuid];
+	},
+	isLast = function(uid, tid) {
+		return _.filter(SocketModules.composer.replyHash, function(replyObj, uuid) {
+			if (
+				parseInt(replyObj.tid, 10) === parseInt(tid, 10) &&
+				parseInt(replyObj.uid, 10) === parseInt(uid, 10)
+			) {
+				return true;
+			} else {
+				return false;
+			}
+		}).length === 1;
+	};
+
 SocketModules.composer.push = function(socket, pid, callback) {
 	if (socket.uid || parseInt(meta.config.allowGuestPosting, 10)) {
 		if (parseInt(pid, 10) > 0) {
@@ -90,8 +112,7 @@ SocketModules.composer.register = function(socket, data) {
 			data.lastPing = Date.now();
 			socket.emit('event:composer.ping', data.uuid);
 		} else {
-			server.in('topic_' + data.tid).emit('event:topic.replyStop', data.uid);
-			delete SocketModules.composer.replyHash[data.uuid];
+			stopTracking(data);
 		}
 	}, 1000*5);	// Every 5 seconds...
 
@@ -101,9 +122,7 @@ SocketModules.composer.register = function(socket, data) {
 SocketModules.composer.unregister = function(socket, uuid) {
 	var	replyObj = SocketModules.composer.replyHash[uuid];
 	if (uuid && replyObj) {
-		server.in('topic_' + replyObj.tid).emit('event:topic.replyStop', replyObj.uid);
-		clearInterval(replyObj.timer);
-		delete SocketModules.composer.replyHash[replyObj.uuid];
+		stopTracking(replyObj);
 	}
 };
 
@@ -114,6 +133,20 @@ SocketModules.composer.pingActive = function(socket, uuid) {
 	}
 };
 
+SocketModules.composer.getUsersByTid = function(socket, tid, callback) {
+	// Return uids with active composers
+	console.log(tid);
+	callback(null, _.filter(SocketModules.composer.replyHash, function(replyObj, uuid) {
+		if (parseInt(replyObj.tid, 10) === parseInt(tid, 10)) {
+			return true;
+		} else {
+			return false;
+		}
+	}).map(function(replyObj) {
+		return replyObj.uid
+	}));
+}
+
 /* Chat */
 
 SocketModules.chats = {};

From c6ff8e1042dfa85ce02a7059d57ff606278e29be Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 1 Mar 2014 21:55:29 -0500
Subject: [PATCH 144/193] #1148

---
 public/src/forum/reset.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/src/forum/reset.js b/public/src/forum/reset.js
index 445d8c897f..b03007c74e 100644
--- a/public/src/forum/reset.js
+++ b/public/src/forum/reset.js
@@ -26,7 +26,7 @@ define(function() {
 				errorEl.removeClass('hide').show();
 				errorTextEl.html('Please enter a valid email');
 			}
-		};
+		});
 	};
 
 	return ResetPassword;

From 1c324f45cffcb041b6f089149aa99dff8b738774 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sat, 1 Mar 2014 21:59:51 -0500
Subject: [PATCH 145/193] tried fixing absentee detection in active users

---
 public/src/forum/topic.js | 29 +++++++++++------------------
 1 file changed, 11 insertions(+), 18 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 6caea47b10..d25d76283e 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -622,17 +622,9 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				var activeEl = $('li.post-bar[data-index="0"] .thread_active_users');
 
 				function createUserIcon(uid, picture, userslug, username) {
-
-					if(!activeEl.find('[href="'+ RELATIVE_PATH +'/user/' + data.users[i].userslug + '"]').length) {
-						var userIcon = $('<img src="'+ picture +'"/>');
-
-						var userLink = $('<a href="' + RELATIVE_PATH + '/user/' + userslug + '"></a>').append(userIcon);
-						userLink.attr('data-uid', uid);
-
-						var div = $('<div class="inline-block"></div>');
-						div.append(userLink);
-
-						userLink.tooltip({
+					if(!activeEl.find('[data-uid="' + uid + '"]').length) {
+						var div = $('<div class="inline-block"><a data-uid="' + uid + '" href="' + RELATIVE_PATH + '/user/' + userslug + '"><img src="'+ picture +'"/></a></div>');
+						div.find('a').tooltip({
 							placement: 'top',
 							title: username
 						});
@@ -642,15 +634,16 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				}
 
 				// remove users that are no longer here
-				activeEl.children().each(function(index, element) {
+				activeEl.find('a').each(function(index, element) {
 					if(element) {
-						var uid = $(element).attr('data-uid');
-						for(var i=0; i<data.users.length; ++i) {
-							if(data.users[i].uid == uid) {
-								return;
-							}
+						var uid = $(element).attr('data-uid'),
+							absent = data.users.every(function(aUid) {
+								return parseInt(aUid, 10) !== parseInt(uid, 10);
+							});
+
+						if (absent) {
+							$(element).remove();
 						}
-						$(element).remove();
 					}
 				});
 

From fb691b23b40405b3fc4a33307c0702140af7056a Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 1 Mar 2014 22:03:21 -0500
Subject: [PATCH 146/193] moved topic locked check to topic.reply

---
 src/posts.js  | 7 -------
 src/topics.js | 8 ++++++++
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/src/posts.js b/src/posts.js
index 0afee2166f..ac4a3d5e5a 100644
--- a/src/posts.js
+++ b/src/posts.js
@@ -37,13 +37,6 @@ var db = require('./database'),
 
 		async.waterfall([
 			function(next) {
-				topics.isLocked(tid, next);
-			},
-			function(locked, next) {
-				if(locked) {
-					return next(new Error('topic-locked'));
-				}
-
 				db.incrObjectField('global', 'nextPid', next);
 			},
 			function(pid, next) {
diff --git a/src/topics.js b/src/topics.js
index e0c56b52f5..590e5deafd 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -171,6 +171,14 @@ var async = require('async'),
 				if (!topicExists) {
 					return next(new Error('topic doesn\'t exist'));
 				}
+
+				Topics.isLocked(tid, next);
+			},
+			function(locked, next) {
+				if (locked) {
+					return next(new Error('topic-locked'));
+				}
+
 				threadTools.privileges(tid, uid, next);
 			},
 			function(privilegesData, next) {

From 1b7f8cc5cb4da8b506bebeb342b7d61c5afb973a Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 1 Mar 2014 22:51:39 -0500
Subject: [PATCH 147/193] active users fix

---
 public/src/forum/topic.js |  7 ++++---
 src/socket.io/index.js    | 17 ++++++++++++-----
 src/socket.io/meta.js     |  2 +-
 src/socket.io/modules.js  |  7 +------
 4 files changed, 18 insertions(+), 15 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index d25d76283e..6a697f5849 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -618,6 +618,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		]);
 
 		socket.on('get_users_in_room', function(data) {
+
 			if(data && data.room.indexOf('topic') !== -1) {
 				var activeEl = $('li.post-bar[data-index="0"] .thread_active_users');
 
@@ -636,9 +637,9 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				// remove users that are no longer here
 				activeEl.find('a').each(function(index, element) {
 					if(element) {
-						var uid = $(element).attr('data-uid'),
-							absent = data.users.every(function(aUid) {
-								return parseInt(aUid, 10) !== parseInt(uid, 10);
+						var uid = $(element).attr('data-uid');
+							absent = data.users.every(function(user) {
+								return parseInt(user.uid, 10) !== parseInt(uid, 10);
 							});
 
 						if (absent) {
diff --git a/src/socket.io/index.js b/src/socket.io/index.js
index 551edac088..bc7748b9ce 100644
--- a/src/socket.io/index.js
+++ b/src/socket.io/index.js
@@ -131,6 +131,7 @@ Sockets.init = function(server) {
 			emitOnlineUserCount();
 
 			for(var roomName in io.sockets.manager.roomClients[socket.id]) {
+				console.log('disconnected from', roomName);
 				updateRoomBrowsingText(roomName.slice(1));
 			}
 		});
@@ -235,7 +236,11 @@ function isUserOnline(uid) {
 Sockets.updateRoomBrowsingText = updateRoomBrowsingText;
 function updateRoomBrowsingText(roomName) {
 
-	function getUidsInRoom(room) {
+	if (!roomName) {
+		return;
+	}
+
+	function getUidsInRoom() {
 		var uids = [];
 		var clients = io.sockets.clients(roomName);
 		for(var i=0; i<clients.length; ++i) {
@@ -246,7 +251,7 @@ function updateRoomBrowsingText(roomName) {
 		return uids;
 	}
 
-	function getAnonymousCount(roomName) {
+	function getAnonymousCount() {
 		var clients = io.sockets.clients(roomName);
 		var anonCount = 0;
 
@@ -258,15 +263,17 @@ function updateRoomBrowsingText(roomName) {
 		return anonCount;
 	}
 
-	var	uids = getUidsInRoom(roomName),
-		anonymousCount = getAnonymousCount(roomName);
+	var	uids = getUidsInRoom(),
+		anonymousCount = getAnonymousCount();
+
+
 
 	user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture', 'status'], function(err, users) {
 		if(!err) {
 			users = users.filter(function(user) {
 				return user.status !== 'offline';
 			});
-
+			console.log('['+roomName+']', users.length, anonymousCount);
 			io.sockets.in(roomName).emit('get_users_in_room', {
 				users: users,
 				anonymousCount: anonymousCount,
diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js
index 18142d5e63..5a394937fa 100644
--- a/src/socket.io/meta.js
+++ b/src/socket.io/meta.js
@@ -85,7 +85,7 @@ SocketMeta.rooms.enter = function(socket, data) {
 
 	socket.join(data.enter);
 
-	if (data.leave) {
+	if (data.leave && data.leave !== data.enter) {
 		module.parent.exports.updateRoomBrowsingText(data.leave);
 	}
 
diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js
index d931185135..7148347fc6 100644
--- a/src/socket.io/modules.js
+++ b/src/socket.io/modules.js
@@ -135,13 +135,8 @@ SocketModules.composer.pingActive = function(socket, uuid) {
 
 SocketModules.composer.getUsersByTid = function(socket, tid, callback) {
 	// Return uids with active composers
-	console.log(tid);
 	callback(null, _.filter(SocketModules.composer.replyHash, function(replyObj, uuid) {
-		if (parseInt(replyObj.tid, 10) === parseInt(tid, 10)) {
-			return true;
-		} else {
-			return false;
-		}
+		return parseInt(replyObj.tid, 10) === parseInt(tid, 10);
 	}).map(function(replyObj) {
 		return replyObj.uid
 	}));

From feeb220514952abc1cb7e831c3c9b1b381ee3df5 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 1 Mar 2014 22:52:30 -0500
Subject: [PATCH 148/193] removed console.log

---
 src/socket.io/index.js | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/socket.io/index.js b/src/socket.io/index.js
index bc7748b9ce..f135245009 100644
--- a/src/socket.io/index.js
+++ b/src/socket.io/index.js
@@ -131,7 +131,6 @@ Sockets.init = function(server) {
 			emitOnlineUserCount();
 
 			for(var roomName in io.sockets.manager.roomClients[socket.id]) {
-				console.log('disconnected from', roomName);
 				updateRoomBrowsingText(roomName.slice(1));
 			}
 		});
@@ -273,7 +272,7 @@ function updateRoomBrowsingText(roomName) {
 			users = users.filter(function(user) {
 				return user.status !== 'offline';
 			});
-			console.log('['+roomName+']', users.length, anonymousCount);
+
 			io.sockets.in(roomName).emit('get_users_in_room', {
 				users: users,
 				anonymousCount: anonymousCount,

From 57329940970a67248e1c4c35d43afc72a1aa84b4 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sat, 1 Mar 2014 23:18:05 -0500
Subject: [PATCH 149/193] reset_code click fix

---
 public/src/forum/reset_code.js | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/public/src/forum/reset_code.js b/public/src/forum/reset_code.js
index 0afd3fc266..36af29de16 100644
--- a/public/src/forum/reset_code.js
+++ b/public/src/forum/reset_code.js
@@ -33,9 +33,8 @@ define(function() {
 					$('#success').removeClass('hide').addClass('show').show();
 				});
 			}
-		}, false);
+		});
 
-		// Enable the form if the code is valid
 		socket.emit('user.reset.valid', {
 			code: reset_code
 		}, function(err, valid) {
@@ -44,7 +43,7 @@ define(function() {
 			}
 
 			if (valid) {
-				resetEl.disabled = false;
+				resetEl.prop('disabled', false);
 			} else {
 				var formEl = $('#reset-form');
 				// Show error message

From 033c5d572647986948c3b6be530afc27f04696e5 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 2 Mar 2014 11:06:21 -0500
Subject: [PATCH 150/193] es, fr, nb, sv, zh_CN translations

---
 public/language/es/global.json        |  4 +--
 public/language/es/pages.json         |  2 +-
 public/language/es/topic.json         | 22 ++++++++--------
 public/language/fr/pages.json         |  2 +-
 public/language/fr/topic.json         | 22 ++++++++--------
 public/language/nb/global.json        |  2 +-
 public/language/nb/pages.json         |  2 +-
 public/language/nb/topic.json         | 20 +++++++--------
 public/language/sv/category.json      |  2 +-
 public/language/sv/global.json        | 12 ++++-----
 public/language/sv/notifications.json |  2 +-
 public/language/sv/pages.json         |  4 +--
 public/language/sv/topic.json         | 36 +++++++++++++--------------
 public/language/sv/user.json          | 16 ++++++------
 public/language/zh_CN/global.json     |  2 +-
 public/language/zh_CN/pages.json      |  2 +-
 public/language/zh_CN/topic.json      | 20 +++++++--------
 17 files changed, 86 insertions(+), 86 deletions(-)

diff --git a/public/language/es/global.json b/public/language/es/global.json
index 7bb6ec6d41..21c502619a 100644
--- a/public/language/es/global.json
+++ b/public/language/es/global.json
@@ -44,12 +44,12 @@
     "alert.banned.message": "Estás baneado, serás desconectado!",
     "alert.unfollow": "Ya no estás siguiendo a %1!",
     "alert.follow": "Estás siguiendo a %1!",
-    "posts": "Publicaciones",
+    "posts": "Posts",
     "views": "Visitas",
     "posted": "publicado",
     "in": "en",
     "recentposts": "Publicaciones Recientes",
-    "recentips": "Recently Logged In IPs",
+    "recentips": "Conexions recientes de estas IP's",
     "online": "Conectado",
     "away": "No disponible",
     "dnd": "No molestar",
diff --git a/public/language/es/pages.json b/public/language/es/pages.json
index 603c8ace7a..fab5dc5001 100644
--- a/public/language/es/pages.json
+++ b/public/language/es/pages.json
@@ -8,7 +8,7 @@
     "user.edit": "Editando \"%1\"",
     "user.following": "Gente que sigue %1 ",
     "user.followers": "Seguidores de %1",
-    "user.posts": "Posts made by %1",
+    "user.posts": "Posteos de %1",
     "user.favourites": "Publicaciones favoritas de %1 ",
     "user.settings": "Preferencias del Usuario"
 }
\ No newline at end of file
diff --git a/public/language/es/topic.json b/public/language/es/topic.json
index ce34ffa2a7..85e1488472 100644
--- a/public/language/es/topic.json
+++ b/public/language/es/topic.json
@@ -11,7 +11,7 @@
     "reply": "Responder",
     "edit": "Editar",
     "delete": "Borrar",
-    "restore": "Restore",
+    "restore": "Restaurar",
     "move": "Mover",
     "fork": "Bifurcar",
     "banned": "baneado",
@@ -19,15 +19,15 @@
     "share": "Compartir",
     "tools": "Herramientas",
     "flag": "Reportar",
-    "bookmark_instructions": "Click here to return to your last position or close to discard.",
+    "bookmark_instructions": "Click aqui para restablecer la ultima posicion del post o cierralo para descartar cambios.",
     "flag_title": "Reportar esta publicación a los moderadores",
     "deleted_message": "Este tema ha sido borrado. Solo los miembros con privilegios pueden verlo.",
     "following_topic.title": "Siguendo tema",
     "following_topic.message": "Ahora recibiras notificaciones cuando alguien publique en este tema.",
     "not_following_topic.title": "No sigues este tema",
     "not_following_topic.message": "No recibiras notificaciones de este tema.",
-    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
-    "markAsUnreadForAll.success": "Topic marked as unread for all.",
+    "login_to_subscribe": "Por favor, conectate para subscribirte a este tema.",
+    "markAsUnreadForAll.success": "Marcar todo como leeido.",
     "watch": "Seguir",
     "share_this_post": "Compartir este post",
     "thread_tools.title": "Herramientas del Tema",
@@ -66,17 +66,17 @@
     "composer.title_placeholder": "Ingresa el titulo de tu tema",
     "composer.write": "Escribe",
     "composer.preview": "Previsualización",
-    "composer.help": "Help",
+    "composer.help": "Ayuda",
     "composer.discard": "Descartar",
     "composer.submit": "Enviar",
     "composer.replying_to": "Respondiendo a",
     "composer.new_topic": "Nuevo Tema",
-    "composer.uploading": "uploading...",
-    "composer.thumb_url_label": "Paste a topic thumbnail URL",
-    "composer.thumb_title": "Add a thumbnail to this topic",
-    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
-    "composer.thumb_file_label": "Or upload a file",
-    "composer.thumb_remove": "Clear fields",
+    "composer.uploading": "cargando...",
+    "composer.thumb_url_label": "Agregar imagen destacada a este tema.",
+    "composer.thumb_title": "Agregar miniatura a este tema.",
+    "composer.thumb_url_placeholder": "http://ejemplo.com/mini.png",
+    "composer.thumb_file_label": "Cargar una foto",
+    "composer.thumb_remove": "Limpiar campos.",
     "composer.drag_and_drop_images": "Arrastra las imagenes aqui",
     "composer.upload_instructions": "Carga tus imagenes con solo arrastrarlas aqui."
 }
\ No newline at end of file
diff --git a/public/language/fr/pages.json b/public/language/fr/pages.json
index e20ff0444c..b30a1c004a 100644
--- a/public/language/fr/pages.json
+++ b/public/language/fr/pages.json
@@ -8,7 +8,7 @@
     "user.edit": "Edite \"%1\"",
     "user.following": "Personnes que %1 suit",
     "user.followers": "Personnes qui suivent  %1",
-    "user.posts": "Posts made by %1",
+    "user.posts": "Message écrit par %1",
     "user.favourites": "Messages favoris de %1",
     "user.settings": "Préférences Utilisateur"
 }
\ No newline at end of file
diff --git a/public/language/fr/topic.json b/public/language/fr/topic.json
index cbe1843ca5..17b3be252d 100644
--- a/public/language/fr/topic.json
+++ b/public/language/fr/topic.json
@@ -11,7 +11,7 @@
     "reply": "Répondre",
     "edit": "Editer",
     "delete": "Supprimer",
-    "restore": "Restore",
+    "restore": "Restaurer",
     "move": "Déplacer",
     "fork": "Scinder",
     "banned": "bannis",
@@ -19,15 +19,15 @@
     "share": "Partager",
     "tools": "Outils",
     "flag": "Signaler",
-    "bookmark_instructions": "Click here to return to your last position or close to discard.",
+    "bookmark_instructions": "Cliquer ici pour retourner à votre dernière position ou fermer pour ignorer.",
     "flag_title": "Signaler ce post pour modération",
     "deleted_message": "Ce sujet a été supprimé. Seuls les utilsateurs avec les droits d'administration peuvent le voir.",
     "following_topic.title": "Sujet suivi",
     "following_topic.message": "Vous recevrez désormais des notifications lorsque quelqu'un postera dans ce sujet.",
     "not_following_topic.title": "Sujet non suivi",
     "not_following_topic.message": "Vous ne recevrez plus de notifications pour ce sujet.",
-    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
-    "markAsUnreadForAll.success": "Topic marked as unread for all.",
+    "login_to_subscribe": "Veuillez vous enregistrer ou vous connecter afin de souscrire à ce sujet.",
+    "markAsUnreadForAll.success": "Sujet marqué comme non lu pour tout le monde.",
     "watch": "Suivre",
     "share_this_post": "Partager ce message",
     "thread_tools.title": "Outils du Fil",
@@ -66,17 +66,17 @@
     "composer.title_placeholder": "Entrer le titre du sujet ici...",
     "composer.write": "Ecriture",
     "composer.preview": "Aperçu",
-    "composer.help": "Help",
+    "composer.help": "Aide",
     "composer.discard": "Abandon",
     "composer.submit": "Envoi",
     "composer.replying_to": "Répondre à",
     "composer.new_topic": "Nouveau Sujet",
-    "composer.uploading": "uploading...",
-    "composer.thumb_url_label": "Paste a topic thumbnail URL",
-    "composer.thumb_title": "Add a thumbnail to this topic",
-    "composer.thumb_url_placeholder": "http://example.com/thumb.png",
-    "composer.thumb_file_label": "Or upload a file",
-    "composer.thumb_remove": "Clear fields",
+    "composer.uploading": "téléchargement...",
+    "composer.thumb_url_label": "Coller une URL de vignette du sujet",
+    "composer.thumb_title": "Ajouter une vignette à ce sujet",
+    "composer.thumb_url_placeholder": "http://exemple.com/vignette.png",
+    "composer.thumb_file_label": "Ou télécharger un fichier",
+    "composer.thumb_remove": "Effacer les champs",
     "composer.drag_and_drop_images": "Glisser-déposer ici les images",
     "composer.upload_instructions": "Uploader des images par glisser-déposer."
 }
\ No newline at end of file
diff --git a/public/language/nb/global.json b/public/language/nb/global.json
index d1773e9388..bd4292c02b 100644
--- a/public/language/nb/global.json
+++ b/public/language/nb/global.json
@@ -49,7 +49,7 @@
     "posted": "skapt",
     "in": "i",
     "recentposts": "Seneste innlegg",
-    "recentips": "Recently Logged In IPs",
+    "recentips": "Seneste innloggede IP-er",
     "online": "Online",
     "away": "Borte",
     "dnd": "Ikke forsturr",
diff --git a/public/language/nb/pages.json b/public/language/nb/pages.json
index 1d7a489f5e..6a23fa2104 100644
--- a/public/language/nb/pages.json
+++ b/public/language/nb/pages.json
@@ -8,7 +8,7 @@
     "user.edit": "Endrer \"%1\"",
     "user.following": "Personer %1 følger",
     "user.followers": "Personer som følger %1",
-    "user.posts": "Posts made by %1",
+    "user.posts": "Innlegg laget av %1",
     "user.favourites": "%1 sine favoritt-innlegg",
     "user.settings": "Brukerinnstillinger"
 }
\ No newline at end of file
diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json
index 320f0b5002..4641564876 100644
--- a/public/language/nb/topic.json
+++ b/public/language/nb/topic.json
@@ -11,7 +11,7 @@
     "reply": "Svar",
     "edit": "Endre",
     "delete": "Slett",
-    "restore": "Restore",
+    "restore": "Gjenopprett",
     "move": "Flytt",
     "fork": "Del",
     "banned": "utestengt",
@@ -19,15 +19,15 @@
     "share": "Del",
     "tools": "Verktøy",
     "flag": "Rapporter",
-    "bookmark_instructions": "Click here to return to your last position or close to discard.",
+    "bookmark_instructions": "Klikk her for å returnere til din siste posisjon eller lukk for å forkaste.",
     "flag_title": "Rapporter dette innlegget for granskning",
     "deleted_message": "Denne tråden har blitt slettet. Bare brukere med trådhåndterings-privilegier kan se den.",
     "following_topic.title": "Følger tråd",
     "following_topic.message": "Du vil nå motta varsler når noen skriver i denne tråden.",
     "not_following_topic.title": "Følger ikke tråd",
     "not_following_topic.message": "Du vil ikke lenger motta varsler fra denne tråden.",
-    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
-    "markAsUnreadForAll.success": "Topic marked as unread for all.",
+    "login_to_subscribe": "Vennligst registrer deg eller logg inn for å abonnere på denne tråden.",
+    "markAsUnreadForAll.success": "Tråd markert som ulest for alle.",
     "watch": "Overvåk",
     "share_this_post": "Del ditt innlegg",
     "thread_tools.title": "Trådverktøy",
@@ -66,17 +66,17 @@
     "composer.title_placeholder": "Skriv din tråd-tittel her",
     "composer.write": "Skriv",
     "composer.preview": "Forhåndsvis",
-    "composer.help": "Help",
+    "composer.help": "Hjelp",
     "composer.discard": "Forkast",
     "composer.submit": "Send",
     "composer.replying_to": "Svarer til",
     "composer.new_topic": "Ny tråd",
-    "composer.uploading": "uploading...",
-    "composer.thumb_url_label": "Paste a topic thumbnail URL",
-    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.uploading": "laster opp...",
+    "composer.thumb_url_label": "Lim inn som tråd-minatyr URL",
+    "composer.thumb_title": "Legg til minatyr til denne tråden",
     "composer.thumb_url_placeholder": "http://example.com/thumb.png",
-    "composer.thumb_file_label": "Or upload a file",
-    "composer.thumb_remove": "Clear fields",
+    "composer.thumb_file_label": "Eller last opp en fil",
+    "composer.thumb_remove": "Tøm felter",
     "composer.drag_and_drop_images": "Dra og slipp bilder her",
     "composer.upload_instructions": "Last opp bilder ved å dra og slippe dem."
 }
\ No newline at end of file
diff --git a/public/language/sv/category.json b/public/language/sv/category.json
index 629829d720..33340fc2c2 100644
--- a/public/language/sv/category.json
+++ b/public/language/sv/category.json
@@ -2,7 +2,7 @@
     "new_topic_button": "Nytt ämne",
     "no_topics": "<strong>Det finns inga ämnen i denna kategori.</strong><br />Varför inte skapa ett?",
     "posts": "inlägg",
-    "views": "tittningar",
+    "views": "visningar",
     "posted": "skapad",
     "browsing": "läser",
     "no_replies": "Ingen har svarat",
diff --git a/public/language/sv/global.json b/public/language/sv/global.json
index 5896cf7d5c..a92a4b8d8f 100644
--- a/public/language/sv/global.json
+++ b/public/language/sv/global.json
@@ -10,16 +10,16 @@
     "500.message": "Hoppsan! Verkar som att något gått snett!",
     "register": "Registrera",
     "login": "Logga in",
-    "please_log_in": "Please Log In",
-    "posting_restriction_info": "Posting is currently restricted to registered members only, click here to log in.",
-    "welcome_back": "Welcome Back ",
-    "you_have_successfully_logged_in": "You have successfully logged in",
+    "please_log_in": "Var god logga in",
+    "posting_restriction_info": "Man måste vara inloggad för att kunna skapa inlägg, klicka här för att logga in.",
+    "welcome_back": "Välkommen tillbaka",
+    "you_have_successfully_logged_in": "Inloggningen lyckades",
     "logout": "Logga ut",
     "logout.title": "Du är nu utloggad.",
     "logout.message": "Du är nu utloggad från NodeBB.",
     "save_changes": "Spara ändringar",
     "close": "Stäng",
-    "pagination": "Pagination",
+    "pagination": "Siduppdelning",
     "header.admin": "Admin",
     "header.recent": "Senaste",
     "header.unread": "Olästa",
@@ -49,7 +49,7 @@
     "posted": "svarade",
     "in": "i",
     "recentposts": "Senaste ämnena",
-    "recentips": "Recently Logged In IPs",
+    "recentips": "Nyligen inloggade IPn",
     "online": "Online",
     "away": "Borta",
     "dnd": "Stör ej",
diff --git a/public/language/sv/notifications.json b/public/language/sv/notifications.json
index b97f8f5ae9..a480c7b77e 100644
--- a/public/language/sv/notifications.json
+++ b/public/language/sv/notifications.json
@@ -1,6 +1,6 @@
 {
     "title": "Notiser",
-    "no_notifs": "You have no new notifications",
+    "no_notifs": "Du har inga nya notiser",
     "see_all": "Visa alla notiser",
     "back_to_home": "Tillbaka till NodeBB",
     "outgoing_link": "Utgående länk",
diff --git a/public/language/sv/pages.json b/public/language/sv/pages.json
index 39899ade4d..815fc336dd 100644
--- a/public/language/sv/pages.json
+++ b/public/language/sv/pages.json
@@ -1,14 +1,14 @@
 {
     "home": "Hem",
     "unread": "Olästa ämnen",
-    "popular": "Popular Topics",
+    "popular": "Populära ämnen",
     "recent": "Senaste ämnena",
     "users": "Registrerade användare",
     "notifications": "Notiser",
     "user.edit": "Ändrar \"%1\"",
     "user.following": "Personer %1 Följer",
     "user.followers": "Personer som följer %1",
-    "user.posts": "Posts made by %1",
+    "user.posts": "Inlägg skapat av %1",
     "user.favourites": "%1's favorit-inlägg",
     "user.settings": "Avnändarinställningar"
 }
\ No newline at end of file
diff --git a/public/language/sv/topic.json b/public/language/sv/topic.json
index 67e81513be..2f62fe568e 100644
--- a/public/language/sv/topic.json
+++ b/public/language/sv/topic.json
@@ -11,7 +11,7 @@
     "reply": "Svara",
     "edit": "Ändra",
     "delete": "Ta bort",
-    "restore": "Restore",
+    "restore": "Återställ",
     "move": "Flytta",
     "fork": "Grena",
     "banned": "bannad",
@@ -19,17 +19,17 @@
     "share": "Dela",
     "tools": "Verktyg",
     "flag": "Rapportera",
-    "bookmark_instructions": "Click here to return to your last position or close to discard.",
+    "bookmark_instructions": "Klicka här för att återgå till den senaste positionen eller stäng för att kasta.",
     "flag_title": "Rapportera detta inlägg för granskning",
     "deleted_message": "Denna tråd har tagits bort. Endast användare med administrations-rättigheter kan se den.",
-    "following_topic.title": "Following Topic",
-    "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
-    "not_following_topic.title": "Not Following Topic",
-    "not_following_topic.message": "You will no longer receive notifications from this topic.",
-    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
-    "markAsUnreadForAll.success": "Topic marked as unread for all.",
-    "watch": "Watch",
-    "share_this_post": "Share this Post",
+    "following_topic.title": "Följer ämne",
+    "following_topic.message": "Du kommer nu få notiser när någon gör inlägg i detta ämne.",
+    "not_following_topic.title": "Du följer inte ämnet",
+    "not_following_topic.message": "Du kommer inte längre få notiser från detta ämne.",
+    "login_to_subscribe": "Var god registrera eller logga in för att kunna prenumerera på detta ämne.",
+    "markAsUnreadForAll.success": "Ämne markerat som oläst av alla.",
+    "watch": "Följ",
+    "share_this_post": "Dela detta inlägg",
     "thread_tools.title": "Trådverktyg",
     "thread_tools.markAsUnreadForAll": "Markera som oläst",
     "thread_tools.pin": "Fäst ämne",
@@ -66,17 +66,17 @@
     "composer.title_placeholder": "Skriv in ämnets titel här...",
     "composer.write": "Skriv",
     "composer.preview": "Förhandsgranska",
-    "composer.help": "Help",
+    "composer.help": "Hjälp",
     "composer.discard": "Avbryt",
     "composer.submit": "Spara",
     "composer.replying_to": "Svarar till",
     "composer.new_topic": "Nytt ämne",
-    "composer.uploading": "uploading...",
-    "composer.thumb_url_label": "Paste a topic thumbnail URL",
-    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.uploading": "laddar upp...",
+    "composer.thumb_url_label": "Klistra in URL till tumnagel för ämnet",
+    "composer.thumb_title": "Lägg till tumnagel för detta ämne",
     "composer.thumb_url_placeholder": "http://example.com/thumb.png",
-    "composer.thumb_file_label": "Or upload a file",
-    "composer.thumb_remove": "Clear fields",
-    "composer.drag_and_drop_images": "Drag and Drop Images Here",
-    "composer.upload_instructions": "Upload images by dragging & dropping them."
+    "composer.thumb_file_label": "Eller ladda upp en fil",
+    "composer.thumb_remove": "Töm fält",
+    "composer.drag_and_drop_images": "Dra och släpp bilder här",
+    "composer.upload_instructions": "Ladda upp bilder genom att dra och släpp dem."
 }
\ No newline at end of file
diff --git a/public/language/sv/user.json b/public/language/sv/user.json
index 13e77e3d00..a876a6f76d 100644
--- a/public/language/sv/user.json
+++ b/public/language/sv/user.json
@@ -19,20 +19,20 @@
     "signature": "Signatur",
     "gravatar": "Gravatar",
     "birthday": "Födelsedag",
-    "chat": "Chat",
-    "follow": "Follow",
-    "unfollow": "Unfollow",
+    "chat": "Chatta",
+    "follow": "Följ",
+    "unfollow": "Sluta följ",
     "change_picture": "Ändra bild",
     "edit": "Ändra",
     "uploaded_picture": "Uppladdad bild",
     "upload_new_picture": "Ladda upp ny bild",
-    "current_password": "Current Password",
+    "current_password": "Nuvarande lösenord",
     "change_password": "Ändra lösenord",
     "confirm_password": "Bekräfta lösenord",
     "password": "Lösenord",
     "upload_picture": "Ladda upp bild",
     "upload_a_picture": "Ladda upp en bild",
-    "image_spec": "You may only upload PNG, JPG, or GIF files",
+    "image_spec": "Du får bara ladda upp PNG, JPG eller GIF-filer",
     "max": "max.",
     "settings": "Inställningar",
     "show_email": "Visa min epost",
@@ -41,7 +41,7 @@
     "has_no_posts": "Denna användare har inte gjort några inlägg än.",
     "email_hidden": "Epost dold",
     "hidden": "dold",
-    "paginate_description": "Paginate topics and posts instead of using infinite scroll.",
-    "topics_per_page": "Topics per Page",
-    "posts_per_page": "Posts per Page"
+    "paginate_description": "Gör så att ämnen och inlägg visas som sidor istället för oändlig scroll.",
+    "topics_per_page": "Ämnen per sida",
+    "posts_per_page": "Inlägg per sida"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/global.json b/public/language/zh_CN/global.json
index 174386fd13..a56b080bc7 100644
--- a/public/language/zh_CN/global.json
+++ b/public/language/zh_CN/global.json
@@ -49,7 +49,7 @@
     "posted": "发布",
     "in": "在",
     "recentposts": "最新发表",
-    "recentips": "Recently Logged In IPs",
+    "recentips": "最近登录ip",
     "online": " 在线",
     "away": "离开",
     "dnd": "不打扰",
diff --git a/public/language/zh_CN/pages.json b/public/language/zh_CN/pages.json
index 712bf602f1..59cfe38f1f 100644
--- a/public/language/zh_CN/pages.json
+++ b/public/language/zh_CN/pages.json
@@ -8,7 +8,7 @@
     "user.edit": "编辑 \"%1\"",
     "user.following": "%1的人关注",
     "user.followers": "%1关注的人",
-    "user.posts": "Posts made by %1",
+    "user.posts": "%1 发表",
     "user.favourites": "%1 喜爱的帖子",
     "user.settings": "用户设置"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/topic.json b/public/language/zh_CN/topic.json
index 4992fa4719..f3857aeadc 100644
--- a/public/language/zh_CN/topic.json
+++ b/public/language/zh_CN/topic.json
@@ -11,7 +11,7 @@
     "reply": "回复",
     "edit": "编辑",
     "delete": "删除",
-    "restore": "Restore",
+    "restore": "恢复",
     "move": "移动",
     "fork": "作为主题",
     "banned": "禁止",
@@ -19,15 +19,15 @@
     "share": "分享",
     "tools": "工具",
     "flag": "标志",
-    "bookmark_instructions": "Click here to return to your last position or close to discard.",
+    "bookmark_instructions": "点击这里返回你最初的位置或退出。",
     "flag_title": "标志受限的帖子",
     "deleted_message": "这个帖子已经删除,只有帖子的拥有者才有权限去查看。",
     "following_topic.title": "关注该主题",
     "following_topic.message": "当有回复提交的时候你将会收到通知。",
     "not_following_topic.title": "非关注主题",
     "not_following_topic.message": "你将不再接受来自该帖子的通知。",
-    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
-    "markAsUnreadForAll.success": "Topic marked as unread for all.",
+    "login_to_subscribe": "请注册或登录以订阅该主题。",
+    "markAsUnreadForAll.success": "标记所有未读主题",
     "watch": "查看",
     "share_this_post": "分享帖子",
     "thread_tools.title": "管理工具",
@@ -66,17 +66,17 @@
     "composer.title_placeholder": "在这里输入你的主题标题...",
     "composer.write": "书写",
     "composer.preview": "预览",
-    "composer.help": "Help",
+    "composer.help": "帮助",
     "composer.discard": "丢弃",
     "composer.submit": "提交",
     "composer.replying_to": "回复",
     "composer.new_topic": "新主题",
-    "composer.uploading": "uploading...",
-    "composer.thumb_url_label": "Paste a topic thumbnail URL",
-    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.uploading": "上传中...",
+    "composer.thumb_url_label": "粘贴一个主题缩略图URL地址",
+    "composer.thumb_title": "为主题添加一个缩略图",
     "composer.thumb_url_placeholder": "http://example.com/thumb.png",
-    "composer.thumb_file_label": "Or upload a file",
-    "composer.thumb_remove": "Clear fields",
+    "composer.thumb_file_label": "或上传一个文件",
+    "composer.thumb_remove": "清除字段",
     "composer.drag_and_drop_images": "把图像拖到此处",
     "composer.upload_instructions": "拖拽图片以上传"
 }
\ No newline at end of file

From 14d7453a2326f61c344076e34fd31353225b08b1 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 2 Mar 2014 13:28:09 -0500
Subject: [PATCH 151/193] bundling socket.io client library into minfile,
 minfile always used from this point forward, even in development mode.

Development mode will not compress the scripts, but will just concatenate.
---
 public/templates/header.tpl |  1 -
 src/meta.js                 | 34 +++++++++++++++++++++++++++-------
 src/plugins.js              |  1 +
 src/routes/meta.js          | 18 ++++++++++++++----
 4 files changed, 42 insertions(+), 12 deletions(-)

diff --git a/public/templates/header.tpl b/public/templates/header.tpl
index decbae43da..56bfc48638 100644
--- a/public/templates/header.tpl
+++ b/public/templates/header.tpl
@@ -28,7 +28,6 @@
 	<script>
 		var RELATIVE_PATH = "{relative_path}";
 	</script>
-	<script src="{relative_path}/socket.io/socket.io.js"></script>
 	<!-- BEGIN clientScripts -->
 	<script src="{relative_path}/{clientScripts.script}?{cache-buster}"></script>
 	<!-- END clientScripts -->
diff --git a/src/meta.js b/src/meta.js
index b8c12a0721..2108cc277c 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -250,6 +250,7 @@ var fs = require('fs'),
 					jsPaths = scripts.map(function (jsPath) {
 						jsPath = path.normalize(jsPath);
 
+						// The filter:scripts.get plugin will be deprecated as of v0.5.0, specify scripts in plugin.json instead
 						if (jsPath.substring(0, 7) === 'plugins') {
 							var	matches = _.map(plugins.staticDirs, function(realPath, mappedPath) {
 								if (jsPath.match(mappedPath)) {
@@ -271,15 +272,18 @@ var fs = require('fs'),
 						}
 					});
 
+				// Remove scripts that could not be found (remove this line at v0.5.0)
 				Meta.js.scripts = jsPaths.filter(function(path) { return path !== null });
 
-				if (process.env.NODE_ENV !== 'development') {
-					callback(null, [
-						Meta.js.minFile
-					]);
-				} else {
-					callback(null, scripts);
-				}
+				// Add socket.io client library
+				Meta.js.scripts.push(path.join(__dirname, '../node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js'));
+
+				// Add plugin scripts
+				Meta.js.scripts = Meta.js.scripts.concat(plugins.clientScripts);
+
+				callback(null, [
+					Meta.js.minFile
+				]);
 			});
 		},
 		minify: function (callback) {
@@ -294,6 +298,22 @@ var fs = require('fs'),
 			minified = uglifyjs.minify(jsPaths);
 			this.cache = minified.code;
 			callback();
+		},
+		compress: function(callback) {
+			var uglifyjs = require('uglify-js'),
+				jsPaths = this.scripts,
+				compressed;
+
+			if (process.env.NODE_ENV === 'development') {
+				winston.info('Compressing client-side libraries into one file');
+			}
+
+			minified = uglifyjs.minify(jsPaths, {
+				mangle: false,
+				compress: false
+			});
+			this.cache = minified.code;
+			callback();
 		}
 	};
 
diff --git a/src/plugins.js b/src/plugins.js
index f334d8b0da..c704049641 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -17,6 +17,7 @@ var fs = require('fs'),
 	Plugins.staticDirs = {};
 	Plugins.cssFiles = [];
 	Plugins.lessFiles = [];
+	Plugins.clientScripts = [];
 
 	Plugins.initialized = false;
 
diff --git a/src/routes/meta.js b/src/routes/meta.js
index 0900ca07d8..0a09074b2c 100644
--- a/src/routes/meta.js
+++ b/src/routes/meta.js
@@ -45,12 +45,22 @@ var path = require('path'),
 		});
 
 		app.get('/nodebb.min.js', function(req, res) {
+			var	sendCached = function() {
+				return res.type('text/javascript').send(meta.js.cache);
+			}
 			if (meta.js.cache) {
-				res.type('text/javascript').send(meta.js.cache);
+				sendCached();
 			} else {
-				meta.js.minify(function() {
-					res.type('text/javascript').send(meta.js.cache);
-				});
+				if (app.enabled('minification')) {
+					meta.js.minify(function() {
+						sendCached();
+					});
+				} else {
+					// Compress only
+					meta.js.compress(function() {
+						sendCached();
+					});
+				}
 			}
 		});
 	};

From 3860abdc24ec02cb5be49a6b055f9511b5b2c291 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 2 Mar 2014 13:31:13 -0500
Subject: [PATCH 152/193] plugins can now pass in scripts in plugin.json, and
 they will be bundled into nodebb.min.js

---
 src/plugins.js | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/src/plugins.js b/src/plugins.js
index c704049641..35d05fe5e3 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -223,6 +223,20 @@ var fs = require('fs'),
 						}));
 					}
 
+					next();
+				},
+				function(next) {
+					// Client-side scripts
+					if (pluginData.scripts && pluginData.scripts instanceof Array) {
+						if (global.env === 'development') {
+							winston.info('[plugins] Found ' + pluginData.scripts.length + ' js file(s) for plugin ' + pluginData.id);
+						}
+
+						Plugins.clientScripts = Plugins.clientScripts.concat(pluginData.scripts.map(function(file) {
+							return path.join(pluginData.id, file);
+						}));
+					}
+
 					next();
 				}
 			], function(err) {

From 4c2a6953f188a2ae151e5b2151ba0044e7fed330 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 2 Mar 2014 15:34:12 -0500
Subject: [PATCH 153/193] concatenating the client scripts, instead of
 compressing, in development mode

---
 src/meta.js        | 25 ++++++++++++++-----------
 src/routes/meta.js |  2 +-
 2 files changed, 15 insertions(+), 12 deletions(-)

diff --git a/src/meta.js b/src/meta.js
index 2108cc277c..2c3b6c66f3 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -299,21 +299,24 @@ var fs = require('fs'),
 			this.cache = minified.code;
 			callback();
 		},
-		compress: function(callback) {
-			var uglifyjs = require('uglify-js'),
-				jsPaths = this.scripts,
-				compressed;
-
+		concatenate: function(callback) {
 			if (process.env.NODE_ENV === 'development') {
-				winston.info('Compressing client-side libraries into one file');
+				winston.info('Concatenating client-side libraries into one file');
 			}
 
-			minified = uglifyjs.minify(jsPaths, {
-				mangle: false,
-				compress: false
+			async.map(this.scripts, function(path, next) {
+				fs.readFile(path, { encoding: 'utf-8' }, next);
+			}, function(err, contents) {
+				if (err) {
+					winston.error('[meta.js.concatenate] Could not minify javascript! Error: ' + err.message);
+					process.exit();
+				}
+
+				Meta.js.cache = contents.reduce(function(output, src) {
+					return output.length ? output + ';\n' + src : src;
+				}, '');
+				callback();
 			});
-			this.cache = minified.code;
-			callback();
 		}
 	};
 
diff --git a/src/routes/meta.js b/src/routes/meta.js
index 0a09074b2c..cc5683b924 100644
--- a/src/routes/meta.js
+++ b/src/routes/meta.js
@@ -57,7 +57,7 @@ var path = require('path'),
 					});
 				} else {
 					// Compress only
-					meta.js.compress(function() {
+					meta.js.concatenate(function() {
 						sendCached();
 					});
 				}

From 5553e07bbd9e686f4f7ee99cb3cfd2f6294ed9ce Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 2 Mar 2014 16:29:21 -0500
Subject: [PATCH 154/193] moving socket.IO client lib to top of file, just in
 case

---
 src/meta.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/meta.js b/src/meta.js
index 2c3b6c66f3..5520cc4a58 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -276,7 +276,7 @@ var fs = require('fs'),
 				Meta.js.scripts = jsPaths.filter(function(path) { return path !== null });
 
 				// Add socket.io client library
-				Meta.js.scripts.push(path.join(__dirname, '../node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js'));
+				Meta.js.scripts.unshift(path.join(__dirname, '../node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js'));
 
 				// Add plugin scripts
 				Meta.js.scripts = Meta.js.scripts.concat(plugins.clientScripts);

From e70bc9f163a3b2e653694194268a8aeb19ccafca Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 2 Mar 2014 16:33:45 -0500
Subject: [PATCH 155/193] added deprecation warning for plugins using
 filter:scripts.get

---
 src/meta.js | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/meta.js b/src/meta.js
index 5520cc4a58..07e9255065 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -261,7 +261,10 @@ var fs = require('fs'),
 							}).filter(function(a) { return a; });
 
 							if (matches.length) {
-								var	relPath = jsPath.slice(new String('plugins/' + matches[0]).length);
+								var	relPath = jsPath.slice(new String('plugins/' + matches[0]).length),
+									pluginId = matches[0].split(path.sep)[0];
+
+								winston.warn('[meta.scripts.get (' + pluginId + ')] filter:scripts.get is deprecated, consider using "scripts" in plugin.json');
 								return plugins.staticDirs[matches[0]] + relPath;
 							} else {
 								winston.warn('[meta.scripts.get] Could not resolve mapped path: ' + jsPath + '. Are you sure it is defined by a plugin?');

From a8d2b46911c0f5ff45dec95cb3541492ab9addc7 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 2 Mar 2014 16:44:41 -0500
Subject: [PATCH 156/193] fixed incorrect path in plugin script inclusion

---
 src/plugins.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/plugins.js b/src/plugins.js
index 35d05fe5e3..688184f81a 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -233,7 +233,7 @@ var fs = require('fs'),
 						}
 
 						Plugins.clientScripts = Plugins.clientScripts.concat(pluginData.scripts.map(function(file) {
-							return path.join(pluginData.id, file);
+							return path.join(__dirname, '../node_modules/', pluginData.id, file);
 						}));
 					}
 

From e4b6d0e1ff0bd2559dd26fe73a956e592fba961a Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sun, 2 Mar 2014 16:58:49 -0500
Subject: [PATCH 157/193] closes #1096

---
 public/src/forum/pagination.js |  8 ++++---
 public/src/forum/topic.js      | 39 +++++++++++++++++++++-------------
 2 files changed, 29 insertions(+), 18 deletions(-)

diff --git a/public/src/forum/pagination.js b/public/src/forum/pagination.js
index 3b19fce4c1..afd4bf3fb8 100644
--- a/public/src/forum/pagination.js
+++ b/public/src/forum/pagination.js
@@ -17,8 +17,6 @@ define(function() {
 				return pagination.loadPage(pagination.currentPage - 1);
 			}).on('click', '.next', function() {
 				return pagination.loadPage(pagination.currentPage + 1);
-			}).on('click', '.page', function() {
-				return pagination.loadPage($(this).attr('data-page'));
 			}).on('click', '.select_page', function(e) {
 				e.preventDefault();
 				bootbox.prompt('Enter page number:', function(pageNum) {
@@ -77,7 +75,11 @@ define(function() {
 			return false;
 		}
 
-		ajaxify.go(window.location.pathname.slice(1) + '?page=' + page);
+		ajaxify.go(window.location.pathname.slice(1) + '?page=' + page, function() {
+			if (typeof callback === 'function') {
+				callback();
+			}
+		});
 		return true;
 	}
 
diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 6a697f5849..a74c3d18fe 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -309,7 +309,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			var bookmark = localStorage.getItem('topic:' + tid + ':bookmark');
 			if (window.location.hash) {
 				Topic.scrollToPost(window.location.hash.substr(1), true);
-			} else if (bookmark) {
+			} else if (bookmark && (!config.usePagination || (config.usePagination && pagination.currentPage === 1))) {
 				app.alert({
 					alert_id: 'bookmark',
 					message: '[[topic:bookmark_instructions]]',
@@ -324,7 +324,9 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				});
 			}
 
-			updateHeader();
+			if (!window.location.hash && !config.usePagination) {
+				updateHeader();
+			}
 
 			$('#post-container').on('mouseenter', '.favourite-tooltip', function(e) {
 				if (!$(this).data('users-loaded')) {
@@ -1037,26 +1039,29 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				if(index > Topic.postCount) {
 					index = Topic.postCount;
 				}
+
 				$('#pagination').html(index + ' out of ' + Topic.postCount);
 				$('.progress-bar').width((index / Topic.postCount * 100) + '%');
 
-				if(!parseInt(el.attr('data-index'), 10)) {
-					localStorage.removeItem('topic:' + templates.get('topic_id') + ':bookmark');
-				} else {
+				var currentBookmark = localStorage.getItem('topic:' + templates.get('topic_id') + ':bookmark');
+				if (!currentBookmark || parseInt(el.attr('data-pid'), 10) > parseInt(currentBookmark, 10)) {
 					localStorage.setItem('topic:' + templates.get('topic_id') + ':bookmark', el.attr('data-pid'));
+				}
 
-					if (!scrollingToPost) {
-						var newUrl = window.location.protocol + '//' + window.location.host + window.location.pathname + '#' + el.attr('data-pid')
-						if (newUrl !== currentUrl) {
-							if (history.replaceState) {
-								history.replaceState({
-									url: window.location.pathname.slice(1) + '#' + el.attr('data-pid')
-								}, null, newUrl);
-							}
-							currentUrl = newUrl;
+				if (!scrollingToPost) {
+
+					var newUrl = window.location.href.replace(window.location.hash, '') + '#' + el.attr('data-pid');
+
+					if (newUrl !== currentUrl) {
+						if (history.replaceState) {
+							history.replaceState({
+								url: window.location.pathname.slice(1) + (window.location.search ? window.location.search : '' ) + '#' + el.attr('data-pid')
+							}, null, newUrl);
 						}
+						currentUrl = newUrl;
 					}
 				}
+
 				return false;
 			}
 		});
@@ -1090,7 +1095,9 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					return;
 				}
 				if(parseInt(page, 10) !== pagination.currentPage) {
-					pagination.loadPage(page);
+					pagination.loadPage(page, function() {
+						scrollToPid(pid);
+					});
 				} else {
 					scrollToPid(pid);
 				}
@@ -1106,6 +1113,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				if(after < 0) {
 					after = 0;
 				}
+
 				loadMorePosts(tid, after, function() {
 					scrollToPid(pid);
 				});
@@ -1123,6 +1131,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					scrollTop: (scrollTo.offset().top - $('#header-menu').height() - offset) + "px"
 				}, duration !== undefined ? duration : 400, function() {
 					scrollingToPost = false;
+					updateHeader();
 					if (highlight) {
 						scrollTo.parent().find('.topic-item').addClass('highlight');
 						setTimeout(function() {

From 98fa8c419db5a4b7ea72fe5e9b53ef318f09c82e Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sun, 2 Mar 2014 19:55:26 -0500
Subject: [PATCH 158/193] closes #1152

---
 public/src/forum/admin/groups.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/src/forum/admin/groups.js b/public/src/forum/admin/groups.js
index c5b69e8743..970822fa73 100644
--- a/public/src/forum/admin/groups.js
+++ b/public/src/forum/admin/groups.js
@@ -127,7 +127,7 @@ define(function() {
 								.append($('<img />').attr('src', results.users[x].picture))
 								.append($('<span />').html(results.users[x].username));
 
-							resultsEl.appendChild(foundUser);
+							resultsEl.append(foundUser);
 						}
 					} else {
 						resultsEl.html('<li>No Users Found</li>');

From 1837a8443c2a7d59b259b68cf1dc00be1c2109d6 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 2 Mar 2014 19:55:50 -0500
Subject: [PATCH 159/193] shifting socket.io back to the end :\

---
 src/meta.js | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/meta.js b/src/meta.js
index 07e9255065..385e89e054 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -279,7 +279,7 @@ var fs = require('fs'),
 				Meta.js.scripts = jsPaths.filter(function(path) { return path !== null });
 
 				// Add socket.io client library
-				Meta.js.scripts.unshift(path.join(__dirname, '../node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js'));
+				Meta.js.scripts.push(path.join(__dirname, '../node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js'));
 
 				// Add plugin scripts
 				Meta.js.scripts = Meta.js.scripts.concat(plugins.clientScripts);
@@ -291,14 +291,13 @@ var fs = require('fs'),
 		},
 		minify: function (callback) {
 			var uglifyjs = require('uglify-js'),
-				jsPaths = this.scripts,
 				minified;
 
 			if (process.env.NODE_ENV === 'development') {
 				winston.info('Minifying client-side libraries');
 			}
 
-			minified = uglifyjs.minify(jsPaths);
+			minified = uglifyjs.minify(this.scripts);
 			this.cache = minified.code;
 			callback();
 		},

From 016642bc16be74d8a87d242c79a42ec0d7fda01c Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Sun, 2 Mar 2014 20:00:54 -0500
Subject: [PATCH 160/193] show 1 ip per line

---
 public/templates/account.tpl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/templates/account.tpl b/public/templates/account.tpl
index d099d68767..7b660a185d 100644
--- a/public/templates/account.tpl
+++ b/public/templates/account.tpl
@@ -115,7 +115,7 @@
 				</div>
 				<div class="panel-body">
 				<!-- BEGIN ips -->
-					{ips.ip}
+					<div>{ips.ip}</div>
 				<!-- END ips -->
 				</div>
 			</div>

From 28832a2540521a542bbffa1e33b3ae8f522b0785 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 2 Mar 2014 20:37:57 -0500
Subject: [PATCH 161/193] fixing bug where sometimes a pidfile was left over,
 and nodebb would refuse to start a daemon again.

---
 loader.js | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/loader.js b/loader.js
index 2effb4ffa0..2ab9be2715 100644
--- a/loader.js
+++ b/loader.js
@@ -44,11 +44,18 @@ var	nconf = require('nconf'),
 
 nconf.argv();
 
+// Start the daemon!
 if (nconf.get('d')) {
 	// Check for a still-active NodeBB process
 	if (fs.existsSync(pidFilePath)) {
-		console.log('\n  Error: Another NodeBB is already running!');
-		process.exit();
+		try {
+			var	pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' });
+			process.kill(pid, 0);
+			console.log('\n  Error: Another NodeBB is already running!');
+			process.exit();
+		} catch (e) {
+			fs.unlinkSync(pidFilePath);
+		}
 	}
 
 	// Initialise logging streams

From 044347ebca2db18b6d1b527e164c332899325979 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 2 Mar 2014 22:02:55 -0500
Subject: [PATCH 162/193] hotfix for vanilla missing socket.io lib

---
 public/templates/header.tpl | 1 +
 1 file changed, 1 insertion(+)

diff --git a/public/templates/header.tpl b/public/templates/header.tpl
index 56bfc48638..decbae43da 100644
--- a/public/templates/header.tpl
+++ b/public/templates/header.tpl
@@ -28,6 +28,7 @@
 	<script>
 		var RELATIVE_PATH = "{relative_path}";
 	</script>
+	<script src="{relative_path}/socket.io/socket.io.js"></script>
 	<!-- BEGIN clientScripts -->
 	<script src="{relative_path}/{clientScripts.script}?{cache-buster}"></script>
 	<!-- END clientScripts -->

From fc53385ede4e24e0960d7c5eba2a38a134bd4e04 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Sun, 2 Mar 2014 22:34:57 -0500
Subject: [PATCH 163/193] removing socket.io library from minfile

---
 src/meta.js | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/meta.js b/src/meta.js
index 385e89e054..ce1cd70990 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -278,9 +278,6 @@ var fs = require('fs'),
 				// Remove scripts that could not be found (remove this line at v0.5.0)
 				Meta.js.scripts = jsPaths.filter(function(path) { return path !== null });
 
-				// Add socket.io client library
-				Meta.js.scripts.push(path.join(__dirname, '../node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js'));
-
 				// Add plugin scripts
 				Meta.js.scripts = Meta.js.scripts.concat(plugins.clientScripts);
 

From 30e83fdabeb85f5ecbc4f86e463a5e0b598e6904 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Mon, 3 Mar 2014 10:17:11 -0500
Subject: [PATCH 164/193] fixing upgrade script if order is not set

---
 src/upgrade.js | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/upgrade.js b/src/upgrade.js
index 6aad843f05..0876aa7113 100644
--- a/src/upgrade.js
+++ b/src/upgrade.js
@@ -864,6 +864,12 @@ Upgrade.upgrade = function(callback) {
 									if(err) {
 										return next(err);
 									}
+
+									// If there was no order present, put it at the end
+									if (!order) {
+										order = cids.length;
+									}
+
 									db.sortedSetAdd('categories:cid', order, cid, next);
 								});
 							}, function(err) {

From 5a8fa9b1f7bd53c4ae8c2b9f466621855ea04897 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Mon, 3 Mar 2014 11:07:37 -0500
Subject: [PATCH 165/193] fixed regression in groups management modal

---
 public/src/forum/admin/groups.js | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/public/src/forum/admin/groups.js b/public/src/forum/admin/groups.js
index 970822fa73..811bc3c17b 100644
--- a/public/src/forum/admin/groups.js
+++ b/public/src/forum/admin/groups.js
@@ -24,9 +24,9 @@ define(function() {
 
 		createSubmitBtn.on('click', function() {
 			var submitObj = {
-				name: createNameEl.val(),
-				description: $('#create-group-desc').val()
-			},
+					name: createNameEl.val(),
+					description: $('#create-group-desc').val()
+				},
 				errorEl = $('#create-modal-error'),
 				errorText;
 
@@ -91,8 +91,9 @@ define(function() {
 							groupMembersEl.empty();
 							for (x = 0; x < numMembers; x++) {
 								var memberIcon = $('<li />')
+									.attr('data-uid', groupObj.members[x].uid)
 									.append($('<img />').attr('src', groupObj.members[x].picture))
-									.append($('<span />').attr('data-uid', groupObj.members[x].uid).html(groupObj.members[x].username));
+									.append($('<span />').html(groupObj.members[x].username));
 								groupMembersEl.append(memberIcon);
 							}
 						}
@@ -152,7 +153,7 @@ define(function() {
 					uid: uid
 				}, function(err, data) {
 					if (!err) {
-						groupMembersEl.append(userLabel.cloneNode(true));
+						groupMembersEl.append(userLabel.clone(true));
 					}
 				});
 			}
@@ -164,10 +165,6 @@ define(function() {
 
 			socket.emit('admin.groups.get', gid, function(err, groupObj){
 				if (!err){
-					if (groupObj.name == 'Administrators' && uid == yourid){
-						bootbox.alert('You cannot remove yourself from the Administrator Group');
-						return;
-					}
 					bootbox.confirm('Are you sure you want to remove this user?', function(confirm) {
 						if (confirm){
 							socket.emit('admin.groups.leave', {

From 64ee792013d4e4eb65448c78ff737fdd6ea0f1cf Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Mon, 3 Mar 2014 11:12:28 -0500
Subject: [PATCH 166/193] fixed #1154

---
 public/src/forum/admin/categories.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js
index 2bcc541cd8..455bc17d79 100644
--- a/public/src/forum/admin/categories.js
+++ b/public/src/forum/admin/categories.js
@@ -303,7 +303,9 @@ define(['uploader'], function(uploader) {
 			}
 			var numResults = results.length,
 				trEl,
-			    resultObj;
+				resultObj;
+
+			groupsResultsEl.empty();
 
 			for(var x = 0; x < numResults; x++) {
 				resultObj = results[x];

From dc8839b63c5244cf48c194b5150ff588990b84d8 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Mon, 3 Mar 2014 12:47:40 -0500
Subject: [PATCH 167/193] updating validator to 3.4.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b3e7350f72..083db7f7a0 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
     "rss": "~0.2.0",
     "prompt": "~0.2.11",
     "uglify-js": "~2.4.0",
-    "validator": "~3.2.1",
+    "validator": "^3.4.0",
     "cron": "~1.0.1",
     "semver": "~2.2.1",
     "string": "~1.7.0",

From 70299ea5c2a9ae0ed6cfe7b0d56775bd41e13ada Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 3 Mar 2014 14:46:50 -0500
Subject: [PATCH 168/193] better search result page

---
 public/src/forum/search.js  |  6 +++---
 public/templates/search.tpl | 36 +++++++++++++-----------------------
 2 files changed, 16 insertions(+), 26 deletions(-)

diff --git a/public/src/forum/search.js b/public/src/forum/search.js
index 71ed19b69f..3d3c72389e 100644
--- a/public/src/forum/search.js
+++ b/public/src/forum/search.js
@@ -3,14 +3,14 @@ define(function() {
 
 	Search.init = function() {
 		var searchQuery = $('#topic-results').attr('data-search-query');
-		$('.search-result-text').children().each(function() {
-			var text = $(this).text();
+
+		$('.search-result-text').each(function() {
+			var text = $(this).html();
 			var regex = new RegExp(searchQuery, 'gi');
 			text = text.replace(regex, '<span class="label label-success">' + searchQuery + '</span>');
 			$(this).html(text);
 		});
 
-
 		$('#search-form input').val(searchQuery);
 
 		$('#mobile-search-form').off('submit').on('submit', function() {
diff --git a/public/templates/search.tpl b/public/templates/search.tpl
index 55a3b57443..393a6d7e04 100644
--- a/public/templates/search.tpl
+++ b/public/templates/search.tpl
@@ -13,7 +13,7 @@
 	</div>
 </form>
 
-<div class="search favourites">
+<div class="search">
 	<div class="{show_results} row">
 
 		<div id="topic-results" class="col-md-12" data-search-query="{search_query}">
@@ -28,25 +28,19 @@
 			<!-- BEGIN topics -->
 			<div class="topic-row panel panel-default clearfix">
 				<div class="panel-body">
-					<a href="../../user/{topics.userslug}">
-						<img title="{topics.username}" class="img-rounded user-img" src="{topics.picture}">
-					</a>
 
-					<a href="../../user/{topics.userslug}">
-						<strong><span>{topics.username}</span></strong>
+
+					<a href="../../topic/{topics.slug}" class="search-result-text">
+						{topics.title}
 					</a>
-					<span class="search-result-text">
-					<p>{topics.title}</p>
-					</span>
 
 					<div>
 						<small>
 							<span class="pull-right">
-								<a href="../../topic/{topics.slug}">posted</a>
-								in
-								<a href="../../category/{topics.categorySlug}">
-									<i class="fa {topics.categoryIcon}"></i> {topics.categoryName}
-								</a>
+								<a href="../../user/{topics.userslug}"><img title="{topics.username}" class="img-rounded user-img" src="{topics.picture}"></a>
+								<a href="../../topic/{topics.slug}"> [[global:posted]]</a>
+								[[global:in]]
+								<a href="../../category/{topics.category.slug}"><i class="fa {topics.category.icon}"></i> {topics.category.name}</a>
 								<span class="timeago" title="{topics.relativeTime}"></span>
 							</span>
 						</small>
@@ -67,20 +61,16 @@
 			<!-- BEGIN posts -->
 			<div class="topic-row panel panel-default clearfix">
 				<div class="panel-body">
-					<a href="../../user/{posts.userslug}">
-						<img title="{posts.username}" class="img-rounded user-img" src="{posts.picture}">
+					<a href="../../topic/{posts.topicSlug}#{posts.pid}" class="search-result-text">
+						{posts.content}
 					</a>
 
-					<a href="../../user/{posts.userslug}">
-						<strong><span>{posts.username}</span></strong>
-					</a>
-					<span class="search-result-text">
-					{posts.content}
-					</span>
-
 					<div>
 						<small>
 							<span class="pull-right">
+								<a href="../../user/{posts.userslug}">
+									<img title="{posts.username}" class="img-rounded user-img" src="{posts.picture}">
+								</a>
 								<a href="../../topic/{posts.topicSlug}#{posts.pid}">posted</a>
 								in
 								<a href="../../category/{posts.categorySlug}">

From be70b3de57a10ff79dbfc6c9d00e2f377084aa44 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 3 Mar 2014 15:26:15 -0500
Subject: [PATCH 169/193] closes #1090

---
 public/src/forum/unread.js  | 11 ++++++++++-
 public/templates/unread.tpl |  2 +-
 src/socket.io/topics.js     | 14 +++++++++++---
 src/topics.js               | 28 ++++++++++------------------
 4 files changed, 32 insertions(+), 23 deletions(-)

diff --git a/public/src/forum/unread.js b/public/src/forum/unread.js
index 9829ca7a25..ac9b275a68 100644
--- a/public/src/forum/unread.js
+++ b/public/src/forum/unread.js
@@ -12,8 +12,17 @@ define(['forum/recent'], function(recent) {
 		recent.watchForNewPosts();
 
 		$('#mark-allread-btn').on('click', function() {
+			function getUnreadTids() {
+				var tids = [];
+				$('#topics-container .category-item[data-tid]').each(function() {
+					tids.push($(this).attr('data-tid'));
+				});
+				return tids;
+			}
+
 			var btn = $(this);
-			socket.emit('topics.markAllRead', function(err) {
+
+			socket.emit('topics.markAllRead', getUnreadTids(), function(err) {
 				if(err) {
 					return app.alertError('There was an error marking topics read!');
 				}
diff --git a/public/templates/unread.tpl b/public/templates/unread.tpl
index c1aa0f87d1..58cd9eab1e 100644
--- a/public/templates/unread.tpl
+++ b/public/templates/unread.tpl
@@ -19,7 +19,7 @@
 		<div class="col-md-12">
 			<ul id="topics-container" data-nextstart="{nextStart}">
 			<!-- BEGIN topics -->
-			<li class="category-item <!-- IF topics.deleted --> deleted<!-- ENDIF topics.deleted -->">
+			<li class="category-item<!-- IF topics.deleted --> deleted<!-- ENDIF topics.deleted -->" data-tid="{topics.tid}">
 				<div class="col-md-12 col-xs-12 panel panel-default topic-row">
 					<a href="{relative_path}/user/{topics.userslug}" class="pull-left">
 						<img class="img-rounded user-img" src="{topics.picture}" title="{topics.username}" />
diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js
index ec19101cd2..80139d2f65 100644
--- a/src/socket.io/topics.js
+++ b/src/socket.io/topics.js
@@ -1,3 +1,6 @@
+
+'use strict';
+
 var topics = require('../topics'),
 	categories = require('../categories'),
 	threadTools = require('../threadTools'),
@@ -68,7 +71,7 @@ SocketTopics.post = function(socket, data, callback) {
 				type: 'success',
 				timeout: 2000
 			});
-			callback(null);
+			callback();
 		}
 	});
 };
@@ -88,14 +91,19 @@ SocketTopics.markAsRead = function(socket, data) {
 };
 
 SocketTopics.markAllRead = function(socket, data, callback) {
-	topics.markAllRead(socket.uid, function(err) {
+
+	if (!Array.isArray(data)) {
+		return callback(new Error('invalid-data'));
+	}
+
+	topics.markAllRead(socket.uid, data, function(err) {
 		if(err) {
 			return callback(err);
 		}
 
 		index.server.sockets.in('uid_' + socket.uid).emit('event:unread.updateCount', null, []);
 
-		callback(null);
+		callback();
 	});
 };
 
diff --git a/src/topics.js b/src/topics.js
index 590e5deafd..7be77d3458 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -797,24 +797,6 @@ var async = require('async'),
 		});
 	};
 
-	Topics.markAllRead = function(uid, callback) {
-		Topics.getLatestTids(0, -1, 'month', function(err, tids) {
-			if (err) {
-				return callback(err);
-			}
-
-			if(!tids || !tids.length) {
-				return callback();
-			}
-
-			function markRead(tid, next) {
-				Topics.markAsRead(tid, uid, next);
-			}
-
-			async.each(tids, markRead, callback);
-		});
-	};
-
 	Topics.getTitleByPid = function(pid, callback) {
 		Topics.getTopicFieldByPid('title', pid, callback);
 	};
@@ -862,6 +844,16 @@ var async = require('async'),
 		});
 	};
 
+	Topics.markAllRead = function(uid, tids, callback) {
+		if(!tids || !tids.length) {
+			return callback();
+		}
+
+		async.each(tids, function (tid, next) {
+			Topics.markAsRead(tid, uid, next);
+		}, callback);
+	};
+
 	Topics.markAsRead = function(tid, uid, callback) {
 
 		db.setAdd('tid:' + tid + ':read_by_uid', uid, function(err) {

From 37aeda14c49a7930eeb482c41537809b9fa9235d Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 3 Mar 2014 16:16:41 -0500
Subject: [PATCH 170/193] fixed email retrieval in mongo

---
 src/database/mongo.js | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/src/database/mongo.js b/src/database/mongo.js
index e6b3050b08..f568f3d514 100644
--- a/src/database/mongo.js
+++ b/src/database/mongo.js
@@ -294,6 +294,10 @@
 	};
 
 	module.getObjectField = function(key, field, callback) {
+		if(typeof field !== 'string') {
+			field = field.toString();
+		}
+		field = field.replace(/\./g, '\uff0E');
 		module.getObjectFields(key, [field], function(err, data) {
 			if(err) {
 				return callback(err);
@@ -307,11 +311,12 @@
 
 		var _fields = {};
 		for(var i=0; i<fields.length; ++i) {
-			if(typeof fields[i] !== 'string') {
-				_fields[fields[i].toString().replace(/\./g, '\uff0E')] = 1;
-			} else {
-				_fields[fields[i].replace(/\./g, '\uff0E')] = 1;
+			if (typeof fields[i] !== 'string') {
+				fields[i] = fields[i].toString();
 			}
+
+			fields[i] = fields[i].replace(/\./g, '\uff0E');
+			_fields[fields[i]] = 1;
 		}
 
 		db.collection('objects').findOne({_key:key}, _fields, function(err, item) {

From 8846f7fb9b92d02c7bdf3909b32cbe3dcf31b52a Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Mon, 3 Mar 2014 16:31:50 -0500
Subject: [PATCH 171/193] fixing mappedPath in windows

---
 src/plugins.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/plugins.js b/src/plugins.js
index 688184f81a..40aa6b996e 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -175,7 +175,7 @@ var fs = require('fs'),
 									(function(staticDir) {
 										fs.exists(staticDir, function(exists) {
 											if (exists) {
-												Plugins.staticDirs[path.join(pluginData.id, mappedPath)] = staticDir;
+												Plugins.staticDirs[pluginData.id + '/' + mappedPath] = staticDir;
 											} else {
 												winston.warn('[plugins/' + pluginData.id + '] Mapped path \'' + mappedPath + ' => ' + staticDir + '\' not found.');
 											}

From 57d0273c13ae082d6cc38dd16b4def5856196606 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Mon, 3 Mar 2014 16:40:57 -0500
Subject: [PATCH 172/193] dutch translations

---
 public/language/nl/global.json        | 12 +++++-----
 public/language/nl/notifications.json |  2 +-
 public/language/nl/pages.json         |  4 ++--
 public/language/nl/topic.json         | 34 +++++++++++++--------------
 public/language/nl/user.json          | 14 +++++------
 5 files changed, 33 insertions(+), 33 deletions(-)

diff --git a/public/language/nl/global.json b/public/language/nl/global.json
index cf6a683825..d44cdf7098 100644
--- a/public/language/nl/global.json
+++ b/public/language/nl/global.json
@@ -10,16 +10,16 @@
     "500.message": "Oeps! Het lijkt erop dat iets is fout gegaan!",
     "register": "Registeren",
     "login": "Inloggen",
-    "please_log_in": "Please Log In",
-    "posting_restriction_info": "Posting is currently restricted to registered members only, click here to log in.",
-    "welcome_back": "Welcome Back ",
-    "you_have_successfully_logged_in": "You have successfully logged in",
+    "please_log_in": "Log a.u.b. In",
+    "posting_restriction_info": "Reageren is momenteel beperkt tot geregistreerde leden, klik hier om in te loggen.",
+    "welcome_back": "Welkom Terug!",
+    "you_have_successfully_logged_in": "Je bent succesvol ingelogd",
     "logout": "Uitloggen",
     "logout.title": "Je bent nu uitgelogd.",
     "logout.message": "Je bent met succes uitgelogd van NodeBB",
     "save_changes": "Aanpassingen Opslaan",
     "close": "Sluiten",
-    "pagination": "Pagination",
+    "pagination": "Paginering",
     "header.admin": "Admin",
     "header.recent": "Recent",
     "header.unread": "Ongelezen",
@@ -49,7 +49,7 @@
     "posted": "geplaatst",
     "in": "in",
     "recentposts": "Recente Berichten",
-    "recentips": "Recently Logged In IPs",
+    "recentips": "Recente Ingelogde IPs",
     "online": "Online",
     "away": "Afwezig",
     "dnd": "Niet Storen",
diff --git a/public/language/nl/notifications.json b/public/language/nl/notifications.json
index 1b1b4bbf8c..6be3973ba7 100644
--- a/public/language/nl/notifications.json
+++ b/public/language/nl/notifications.json
@@ -1,6 +1,6 @@
 {
     "title": "Notificaties",
-    "no_notifs": "You have no new notifications",
+    "no_notifs": "Je hebt geen nieuwe notificaties",
     "see_all": "Bekijk alle Notificaties",
     "back_to_home": "Terug naar NodeBB",
     "outgoing_link": "Uitgaande Link",
diff --git a/public/language/nl/pages.json b/public/language/nl/pages.json
index 15deb0556f..70897ad6f4 100644
--- a/public/language/nl/pages.json
+++ b/public/language/nl/pages.json
@@ -1,14 +1,14 @@
 {
     "home": "Home",
     "unread": "Ongelezen Onderwerpen",
-    "popular": "Popular Topics",
+    "popular": "Populaire Onderwerpen",
     "recent": "Recente Onderwerpen",
     "users": "Geregistreerde Gebruikers",
     "notifications": "Notificaties",
     "user.edit": "\"%1\" aanpassen",
     "user.following": "Mensen %1 Volgt",
     "user.followers": "Mensen die %1 Volgen",
-    "user.posts": "Posts made by %1",
+    "user.posts": "Berichten geplaatst door %1",
     "user.favourites": "%1's Favoriete Berichten",
     "user.settings": "Gebruikersinstellingen"
 }
\ No newline at end of file
diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json
index 3331b7fcf1..033ac94a11 100644
--- a/public/language/nl/topic.json
+++ b/public/language/nl/topic.json
@@ -11,7 +11,7 @@
     "reply": "Reageren",
     "edit": "Aanpassen",
     "delete": "Verwijderen",
-    "restore": "Restore",
+    "restore": "Herstellen",
     "move": "Verplaatsen",
     "fork": "Fork",
     "banned": "verbannen",
@@ -19,17 +19,17 @@
     "share": "Delen",
     "tools": "Gereedschap",
     "flag": "Markeren",
-    "bookmark_instructions": "Click here to return to your last position or close to discard.",
+    "bookmark_instructions": "Klik hier om terug te gaan naar je laatste positie of sluiten om te annuleren.",
     "flag_title": "Dit bericht markeren voor moderatie",
     "deleted_message": "Dit onderwerp is verwijderd. Alleen gebruikers met onderwerp management privileges kunnen dit onderwerp zien.",
-    "following_topic.title": "Following Topic",
-    "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
-    "not_following_topic.title": "Not Following Topic",
-    "not_following_topic.message": "You will no longer receive notifications from this topic.",
-    "login_to_subscribe": "Please register or log in in order to subscribe to this topic.",
-    "markAsUnreadForAll.success": "Topic marked as unread for all.",
-    "watch": "Watch",
-    "share_this_post": "Share this Post",
+    "following_topic.title": "Volg Onderwerp",
+    "following_topic.message": "Je zult nu notificaties ontvangen wanneer iemand reageert op dit onderwerp.",
+    "not_following_topic.title": "Volgt Onderwerp Niet",
+    "not_following_topic.message": "Je zult niet langer notificaties ontvangen van dit onderwerp.",
+    "login_to_subscribe": "Log a.u.b. in om op dit onderwerp te abonneren.",
+    "markAsUnreadForAll.success": "Onderwerp gemarkeerd als gelezen voor iedereen.",
+    "watch": "Volgen",
+    "share_this_post": "Deel dit Bericht",
     "thread_tools.title": "Thread Gereedschap",
     "thread_tools.markAsUnreadForAll": "Ongelezen Markeren",
     "thread_tools.pin": "Onderwerp Vastmaken",
@@ -71,12 +71,12 @@
     "composer.submit": "Opslaan",
     "composer.replying_to": "Reageren op",
     "composer.new_topic": "Nieuw Onderwerp",
-    "composer.uploading": "uploading...",
-    "composer.thumb_url_label": "Paste a topic thumbnail URL",
-    "composer.thumb_title": "Add a thumbnail to this topic",
+    "composer.uploading": "uploaden...",
+    "composer.thumb_url_label": "Plak een onderwerp thumbnail URL",
+    "composer.thumb_title": "Voeg een thumbnail toe aan dit onderwerp",
     "composer.thumb_url_placeholder": "http://example.com/thumb.png",
-    "composer.thumb_file_label": "Or upload a file",
-    "composer.thumb_remove": "Clear fields",
-    "composer.drag_and_drop_images": "Drag and Drop Images Here",
-    "composer.upload_instructions": "Upload images by dragging & dropping them."
+    "composer.thumb_file_label": "Of upload een bestand",
+    "composer.thumb_remove": "Velden leegmaken",
+    "composer.drag_and_drop_images": "Sleep en Zet Afbeeldingen Hier",
+    "composer.upload_instructions": "Upload afbeeldingen door ze te slepen."
 }
\ No newline at end of file
diff --git a/public/language/nl/user.json b/public/language/nl/user.json
index ec931aee06..05e0c6f67c 100644
--- a/public/language/nl/user.json
+++ b/public/language/nl/user.json
@@ -20,19 +20,19 @@
     "gravatar": "Gravatar",
     "birthday": "Verjaardag",
     "chat": "Chat",
-    "follow": "Follow",
-    "unfollow": "Unfollow",
+    "follow": "Volgen",
+    "unfollow": "Ontvolgen",
     "change_picture": "Afbeelding Aanpassen",
     "edit": "Aanpassen",
     "uploaded_picture": "Afbeelding Uploaden",
     "upload_new_picture": "Nieuwe Afbeelding Uploaden",
-    "current_password": "Current Password",
+    "current_password": "Huidige Wachtwoord",
     "change_password": "Wachtwoord Aanpassen",
     "confirm_password": "Bevestig Wachtwoord",
     "password": "Wachtwoord",
     "upload_picture": "Afbeelding Uploaden",
     "upload_a_picture": "Upload een afbeelding",
-    "image_spec": "You may only upload PNG, JPG, or GIF files",
+    "image_spec": "Je mag alleen PNG, JPG, of GIF bestanden uploaden.",
     "max": "max.",
     "settings": "Instellingen",
     "show_email": "Laat Mijn Email Zien",
@@ -41,7 +41,7 @@
     "has_no_posts": "Deze gebruiker heeft nog geen berichten geplaatst",
     "email_hidden": "Email Verborgen",
     "hidden": "verborgen",
-    "paginate_description": "Paginate topics and posts instead of using infinite scroll.",
-    "topics_per_page": "Topics per Page",
-    "posts_per_page": "Posts per Page"
+    "paginate_description": "Blader door onderwerpen en berichten in plaats van oneindig scrollen.",
+    "topics_per_page": "Onderwerpen per Pagina",
+    "posts_per_page": "Berichten per Pagina"
 }
\ No newline at end of file

From 50f83abf75eb16531711e8e8c1f9c9a6e1c8ee24 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 3 Mar 2014 17:24:38 -0500
Subject: [PATCH 173/193] closes 1150

---
 app.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/app.js b/app.js
index 665944644b..877b08c79c 100644
--- a/app.js
+++ b/app.js
@@ -106,7 +106,8 @@ function start() {
 	winston.info('Time: ' + new Date());
 	winston.info('Initializing NodeBB v' + pkg.version);
 	winston.info('* using configuration stored in: ' + configFile);
-	winston.info('* using ' + nconf.get('database') +' store at ' + nconf.get(nconf.get('database') + ':host') + ':' + nconf.get(nconf.get('database') + ':port'));
+	var host = nconf.get(nconf.get('database') + ':host');
+	winston.info('* using ' + nconf.get('database') +' store at ' + host + (host.indexOf('/') === -1 ? ':' + nconf.get(nconf.get('database') + ':port') : ''));
 	winston.info('* using themes stored in: ' + nconf.get('themes_path'));
 
 	if (process.env.NODE_ENV === 'development') {

From 4b64b9dcdc9fa6a675acd56cc326238faaa4c0a3 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 3 Mar 2014 17:46:54 -0500
Subject: [PATCH 174/193] closes #951

---
 public/templates/admin/index.tpl | 17 +++++++++++++++--
 src/routes/admin.js              |  3 +++
 2 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/public/templates/admin/index.tpl b/public/templates/admin/index.tpl
index 3fac2b0917..95c5290940 100644
--- a/public/templates/admin/index.tpl
+++ b/public/templates/admin/index.tpl
@@ -30,9 +30,14 @@
 	</div>
 	<div class="col-sm-6">
 		<div class="panel panel-default">
-			<div class="panel-heading">Active Users <small><span class="badge" id="connections"></span> socket connections</small></div>
+			<div class="panel-heading">Notices</div>
 			<div class="panel-body">
-				<div id="active_users"></div>
+				<div>
+					Emailer Installed <!-- IF emailerInstalled --><i class="fa fa-check alert-success"></i><!-- ELSE --><i class="fa fa-times alert-danger"></i><!-- ENDIF emailerInstalled -->
+				</div>
+				<div>
+					Search Plugin Installed <!-- IF searchInstalled --><i class="fa fa-check alert-success"></i><!-- ELSE --><i class="fa fa-times alert-danger"></i><!-- ENDIF searchInstalled -->
+				</div>
 			</div>
 		</div>
 	</div>
@@ -61,4 +66,12 @@
 			</div>
 		</div>
 	</div>
+	<div class="col-sm-6">
+		<div class="panel panel-default">
+			<div class="panel-heading">Active Users <small><span class="badge" id="connections"></span> socket connections</small></div>
+			<div class="panel-body">
+				<div id="active_users"></div>
+			</div>
+		</div>
+	</div>
 </div>
\ No newline at end of file
diff --git a/src/routes/admin.js b/src/routes/admin.js
index d4cd723c21..d51e4f61d3 100644
--- a/src/routes/admin.js
+++ b/src/routes/admin.js
@@ -252,8 +252,11 @@ var nconf = require('nconf'),
 		app.namespace('/api/admin', function () {
 
 			app.get('/index', function (req, res) {
+
 				res.json({
 					version: pkg.version,
+					emailerInstalled: plugins.hasListeners('action:email.send'),
+					searchInstalled: plugins.hasListeners('filter:search.query')
 				});
 			});
 

From 814db2e1d87436a59d3777d86f1d14afd82ef41b Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 3 Mar 2014 17:52:00 -0500
Subject: [PATCH 175/193] icon first

---
 public/templates/admin/index.tpl | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/public/templates/admin/index.tpl b/public/templates/admin/index.tpl
index 95c5290940..f7800cb74c 100644
--- a/public/templates/admin/index.tpl
+++ b/public/templates/admin/index.tpl
@@ -33,10 +33,10 @@
 			<div class="panel-heading">Notices</div>
 			<div class="panel-body">
 				<div>
-					Emailer Installed <!-- IF emailerInstalled --><i class="fa fa-check alert-success"></i><!-- ELSE --><i class="fa fa-times alert-danger"></i><!-- ENDIF emailerInstalled -->
+					<!-- IF emailerInstalled --><i class="fa fa-check alert-success"></i><!-- ELSE --><i class="fa fa-times alert-danger"></i><!-- ENDIF emailerInstalled --> Emailer Installed
 				</div>
 				<div>
-					Search Plugin Installed <!-- IF searchInstalled --><i class="fa fa-check alert-success"></i><!-- ELSE --><i class="fa fa-times alert-danger"></i><!-- ENDIF searchInstalled -->
+					<!-- IF searchInstalled --><i class="fa fa-check alert-success"></i><!-- ELSE --><i class="fa fa-times alert-danger"></i><!-- ENDIF searchInstalled --> Search Plugin Installed
 				</div>
 			</div>
 		</div>

From 7bb3766ebd31890f904217db94d8b10761dd8c96 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 3 Mar 2014 22:05:25 -0500
Subject: [PATCH 176/193] 2 column layout for admin index

---
 public/templates/admin/index.tpl | 44 +++++++++++++++++---------------
 1 file changed, 23 insertions(+), 21 deletions(-)

diff --git a/public/templates/admin/index.tpl b/public/templates/admin/index.tpl
index f7800cb74c..21e1578cd3 100644
--- a/public/templates/admin/index.tpl
+++ b/public/templates/admin/index.tpl
@@ -1,4 +1,5 @@
 <div class="home">
+
 	<div class="col-sm-6">
 		<div class="panel panel-default">
 			<div class="panel-heading">Welcome to NodeBB</div>
@@ -11,24 +12,7 @@
 				</p>
 			</div>
 		</div>
-	</div>
-	<div class="col-sm-6 pull-right">
-		<div class="panel panel-default">
-			<div class="panel-heading">Updates</div>
-			<div class="panel-body">
-				<div class="alert alert-info version-check">
-					<p>You are running <strong>NodeBB v<span id="version">{version}</span></strong>.</p>
-				</div>
-				<p>
-					Always make sure that your <strong>NodeBB</strong> is up to date for the latest security patches and bug fixes.
-				</p>
-				<p class="pull-right">
-					<button class="btn btn-warning restart">Restart NodeBB</button>
-				</p>
-			</div>
-		</div>
-	</div>
-	<div class="col-sm-6">
+
 		<div class="panel panel-default">
 			<div class="panel-heading">Notices</div>
 			<div class="panel-body">
@@ -40,8 +24,7 @@
 				</div>
 			</div>
 		</div>
-	</div>
-	<div class="col-sm-6">
+
 		<div class="panel panel-default">
 			<div class="panel-heading">Unique Visitors</div>
 			<div class="panel-body">
@@ -65,13 +48,32 @@
 				</div>
 			</div>
 		</div>
+
 	</div>
-	<div class="col-sm-6">
+
+
+	<div class="col-sm-6 pull-right">
+		<div class="panel panel-default">
+			<div class="panel-heading">Updates</div>
+			<div class="panel-body">
+				<div class="alert alert-info version-check">
+					<p>You are running <strong>NodeBB v<span id="version">{version}</span></strong>.</p>
+				</div>
+				<p>
+					Always make sure that your <strong>NodeBB</strong> is up to date for the latest security patches and bug fixes.
+				</p>
+				<p class="pull-right">
+					<button class="btn btn-warning restart">Restart NodeBB</button>
+				</p>
+			</div>
+		</div>
+
 		<div class="panel panel-default">
 			<div class="panel-heading">Active Users <small><span class="badge" id="connections"></span> socket connections</small></div>
 			<div class="panel-body">
 				<div id="active_users"></div>
 			</div>
 		</div>
+
 	</div>
 </div>
\ No newline at end of file

From dbb814fe4edd1959bc4616b47c21f79a1405796a Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 4 Mar 2014 12:54:25 -0500
Subject: [PATCH 177/193] category permission page fixes

---
 public/src/forum/admin/categories.js | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js
index 455bc17d79..144adfd26a 100644
--- a/public/src/forum/admin/categories.js
+++ b/public/src/forum/admin/categories.js
@@ -252,6 +252,7 @@ define(['uploader'], function(uploader) {
 
 					var	numResults = results.length,
 						resultObj;
+					resultsEl.html('');
 					for(var x = 0; x < numResults; x++) {
 						resultObj = results[x];
 						liEl = $('<li />')
@@ -348,12 +349,14 @@ define(['uploader'], function(uploader) {
 			readMembers = modalEl.find('#category-permissions-read'),
 			writeMembers = modalEl.find('#category-permissions-write'),
 			moderatorsEl = modalEl.find('#category-permissions-mods');
+
 		socket.emit('admin.categories.getPrivilegeSettings', cid, function(err, privilegeList) {
 			var	readLength = privilegeList['+r'].length,
 				writeLength = privilegeList['+w'].length,
 				modLength = privilegeList['mods'].length,
 				liEl, x, userObj;
 
+			readMembers.html('');
 			if (readLength > 0) {
 				for(x = 0; x < readLength; x++) {
 					userObj = privilegeList['+r'][x];
@@ -365,10 +368,11 @@ define(['uploader'], function(uploader) {
 				readMembers.append(liEl);
 			}
 
+			writeMembers.html('');
 			if (writeLength > 0) {
 				for(x=0;x<writeLength;x++) {
 					userObj = privilegeList['+w'][x];
-					$('<li />').attr('data-uid', userObj.uid).html('<img src="' + userObj.picture + '" title="' + userObj.username + '" />');
+					liEl = $('<li />').attr('data-uid', userObj.uid).html('<img src="' + userObj.picture + '" title="' + userObj.username + '" />');
 					writeMembers.append(liEl);
 				}
 			} else {
@@ -376,6 +380,7 @@ define(['uploader'], function(uploader) {
 				writeMembers.append(liEl);
 			}
 
+			moderatorsEl.html('');
 			if (modLength > 0) {
 				for(x = 0;x < modLength; x++) {
 					userObj = privilegeList['mods'][x];
@@ -384,7 +389,7 @@ define(['uploader'], function(uploader) {
 				}
 			} else {
 				liEl = $('<li />').addClass('empty').html('No moderators');
-				moderatorsEl.appendChild(liEl);
+				moderatorsEl.append(liEl);
 			}
 		});
 	};

From 3c97ef6829e86686f469c17bec62bc939beda96c Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 4 Mar 2014 13:09:58 -0500
Subject: [PATCH 178/193] closes #1156

---
 public/language/en_GB/reset_password.json | 1 +
 public/templates/reset.tpl                | 4 ++--
 public/templates/reset_code.tpl           | 4 ++--
 3 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/public/language/en_GB/reset_password.json b/public/language/en_GB/reset_password.json
index 468bfb0254..27537ffdf2 100644
--- a/public/language/en_GB/reset_password.json
+++ b/public/language/en_GB/reset_password.json
@@ -8,6 +8,7 @@
 	"new_password": "New Password",
 	"repeat_password": "Confirm Password",
 	"enter_email": "Please enter your <strong>email address</strong> and we will send you an email with instructions on how to reset your account.",
+	"enter_email_address": "Enter Email Address",
 	"password_reset_sent": "Password Reset Sent",
 	"invalid_email": "Invalid Email / Email does not exist!"
 }
diff --git a/public/templates/reset.tpl b/public/templates/reset.tpl
index bcc4d4d931..c7107315f0 100644
--- a/public/templates/reset.tpl
+++ b/public/templates/reset.tpl
@@ -8,7 +8,7 @@
 </ol>
 
 <div class="alert alert-info">
-	[[reset_password:reset_password:enter_email]]
+	[[reset_password:enter_email]]
 </div>
 
 <div class="well">
@@ -21,7 +21,7 @@
 		<strong>[[reset_password:invalid_email]]</strong>
 	</div>
 	<form onsubmit="return false;">
-		<input type="text" class="form-control input-block input-lg" placeholder="Enter Email Address" id="email" />
+		<input type="text" class="form-control input-block input-lg" placeholder="[[reset_password:enter_email_address]]" id="email" />
 
 		<br />
 		<button class="btn btn-primary btn-block btn-lg" id="reset" type="submit">[[reset_password:reset_password]]</button>
diff --git a/public/templates/reset_code.tpl b/public/templates/reset_code.tpl
index c3590746d4..19bce3ad1c 100644
--- a/public/templates/reset_code.tpl
+++ b/public/templates/reset_code.tpl
@@ -27,9 +27,9 @@
 	</div>
 	<form onsubmit="return false;" id="reset-form">
 		<label for="password">[[reset_password:new_password]]</label>
-		<input class="form-control input-lg" type="password" placeholder="A new password" id="password" /><br />
+		<input class="form-control input-lg" type="password" placeholder="[[reset_password:new_password]]" id="password" /><br />
 		<label for="repeat">[[reset_password:repeat_password]]</label>
-		<input class="form-control input-lg" type="password" placeholder="The same password" id="repeat" /><br />
+		<input class="form-control input-lg" type="password" placeholder="[[reset_password:repeat_password]]" id="repeat" /><br />
 		<button class="btn btn-primary btn-lg btn-block" id="reset" type="submit">[[reset_password:reset_password]]</button>
 	</form>
 </div>

From b28e4846a42d0c54ac9978df6be34bc4595ffe80 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 4 Mar 2014 13:28:24 -0500
Subject: [PATCH 179/193] fixed disconnect check

---
 src/socket.io/index.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/socket.io/index.js b/src/socket.io/index.js
index f135245009..c62fd2f9c6 100644
--- a/src/socket.io/index.js
+++ b/src/socket.io/index.js
@@ -116,7 +116,7 @@ Sockets.init = function(server) {
 
 		socket.on('disconnect', function() {
 
-			if (uid && !Sockets.getUserSockets(uid).length <= 1) {
+			if (uid && Sockets.getUserSockets(uid).length <= 1) {
 				db.sortedSetRemove('users:online', uid, function(err) {
 					socketUser.isOnline(socket, uid, function(err, data) {
 						socket.broadcast.emit('user.isOnline', err, data);

From 76b53478ce169f2fdb18b1261c053165e4a06061 Mon Sep 17 00:00:00 2001
From: medwards20x6 <medwards20x6@gmail.com>
Date: Tue, 4 Mar 2014 10:30:44 -0800
Subject: [PATCH 180/193] Don't fire click when closing an alert

---
 public/src/app.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/public/src/app.js b/public/src/app.js
index e33a0f545d..2eac126888 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -231,8 +231,10 @@ var socket,
 			}
 
 			if (typeof params.clickfn === 'function') {
-				alert.on('click', function () {
-					params.clickfn();
+				alert.on('click', function (e) {
+					if(!$(e.target).is('.close')) {
+						params.clickfn();
+					}
 					fadeOut();
 				});
 			}

From 9b53dd10145e64a6498604e35bae3f9dc9d72322 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Tue, 4 Mar 2014 13:35:20 -0500
Subject: [PATCH 181/193] english fallback for 'reset_password' translations

---
 public/language/ar/reset_password.json    | 1 +
 public/language/cs/reset_password.json    | 1 +
 public/language/de/reset_password.json    | 1 +
 public/language/es/reset_password.json    | 1 +
 public/language/fi/reset_password.json    | 1 +
 public/language/fr/reset_password.json    | 1 +
 public/language/he/reset_password.json    | 1 +
 public/language/hu/reset_password.json    | 1 +
 public/language/it/reset_password.json    | 1 +
 public/language/nb/reset_password.json    | 1 +
 public/language/nl/reset_password.json    | 1 +
 public/language/pl/reset_password.json    | 1 +
 public/language/pt_BR/reset_password.json | 1 +
 public/language/ru/reset_password.json    | 1 +
 public/language/sk/reset_password.json    | 1 +
 public/language/sv/reset_password.json    | 1 +
 public/language/tr/reset_password.json    | 1 +
 public/language/zh_CN/reset_password.json | 1 +
 public/language/zh_TW/reset_password.json | 1 +
 19 files changed, 19 insertions(+)

diff --git a/public/language/ar/reset_password.json b/public/language/ar/reset_password.json
index a4eadc9ea4..e1562f605d 100644
--- a/public/language/ar/reset_password.json
+++ b/public/language/ar/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "كلمة السر الجديدة",
     "repeat_password": "تأكيد كلمة السر",
     "enter_email": "يرجى إدخال <strong>عنوان البريد الإلكتروني</strong> الخاص بك وسوف نرسل لك رسالة بالبريد الالكتروني مع تعليمات حول كيفية إستعادة حسابك.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "إعادة تعيين كلمة السر أرسلت",
     "invalid_email": "بريد إلكتروني غير صالح أو غير موجود"
 }
\ No newline at end of file
diff --git a/public/language/cs/reset_password.json b/public/language/cs/reset_password.json
index 1121c9ac98..82114efc95 100644
--- a/public/language/cs/reset_password.json
+++ b/public/language/cs/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Nové heslo",
     "repeat_password": "Potvrzení hesla",
     "enter_email": "Zadejte svou <strong>emailovou adresu</strong> a my Vám pošleme informace, jak můžete obnovit své heslo.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Obnova hesla odeslána",
     "invalid_email": "Špatný email / Email neexistuje!"
 }
\ No newline at end of file
diff --git a/public/language/de/reset_password.json b/public/language/de/reset_password.json
index 0675d796b9..aaa98d402a 100644
--- a/public/language/de/reset_password.json
+++ b/public/language/de/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Neues Passwort",
     "repeat_password": "Wiederhole das Passwort",
     "enter_email": "Bitte gib Deine <strong>E-Mail Adresse</strong> ein und wir senden Dir eine Anleitung, wie Du Dein Passwort zurücksetzen kannst.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Passwortzrücksetzung beantragt.",
     "invalid_email": "Ungültige E-Mail / Adresse existiert nicht!"
 }
\ No newline at end of file
diff --git a/public/language/es/reset_password.json b/public/language/es/reset_password.json
index 99ca4a0f0c..7fdd6ab7bd 100644
--- a/public/language/es/reset_password.json
+++ b/public/language/es/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Nueva Contraseña",
     "repeat_password": "Confirmar Contraseña",
     "enter_email": "Por favor ingresa tu <strong>correo electrónico</strong> y te enviaremos un correo con indicaciones para inicializar tu cuenta.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Reinicio de contraseña enviado",
     "invalid_email": "Correo Electrónico no válido o inexistente!"
 }
\ No newline at end of file
diff --git a/public/language/fi/reset_password.json b/public/language/fi/reset_password.json
index 864817f60d..eb767012bc 100644
--- a/public/language/fi/reset_password.json
+++ b/public/language/fi/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Uusi salasana",
     "repeat_password": "Vahvista salasana",
     "enter_email": "Syötä <strong>sähköpostiosoitteesi</strong>, niin me lähetämme sinulle sähköpostilla ohjeet käyttäjätilisi palauttamiseksi.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Salasanan palautuskoodi lähetetty",
     "invalid_email": "Virheellinen sähköpostiosoite / Sähköpostiosoitetta ei ole olemassa!"
 }
\ No newline at end of file
diff --git a/public/language/fr/reset_password.json b/public/language/fr/reset_password.json
index 8610f7c172..c44345a4e0 100644
--- a/public/language/fr/reset_password.json
+++ b/public/language/fr/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Nouveau Mot de passe",
     "repeat_password": "Confirmer le Mot de passe",
     "enter_email": "Veuillez entrer votre <strong>adresse email</strong> et vous recevrez un email avec les instruction pour réinitialiser votre compte.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Réinitialisation de Mot de Passe Envoyée",
     "invalid_email": "Email Invalide / L'Email n'existe pas!"
 }
\ No newline at end of file
diff --git a/public/language/he/reset_password.json b/public/language/he/reset_password.json
index 92dbb1757e..b44e5f4ddc 100644
--- a/public/language/he/reset_password.json
+++ b/public/language/he/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "סיסמה חדשה",
     "repeat_password": "אמת סיסמה",
     "enter_email": "אנא הקלד את <strong>כתובת האימייל שלך</strong> ואנו נשלח לך הוראות כיצד לאפס את חשבונך",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "קוד איפוס סיסמה נשלח",
     "invalid_email": "מייל שגוי / כתובת מייל לא נמצאה"
 }
\ No newline at end of file
diff --git a/public/language/hu/reset_password.json b/public/language/hu/reset_password.json
index c3274238d6..e611a2688f 100644
--- a/public/language/hu/reset_password.json
+++ b/public/language/hu/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Új jelszó",
     "repeat_password": "Jelszó megerősítése",
     "enter_email": "Kérlek add meg az <strong>e-mail címedet</strong>, ahová elküldjük a további teendőket a jelszavad visszaállításával kapcsolatban.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Jelszó-visszaállítás elküldve",
     "invalid_email": "Helytelen E-mail cím / Nem létező E-mail cím!"
 }
\ No newline at end of file
diff --git a/public/language/it/reset_password.json b/public/language/it/reset_password.json
index a8dc6250f4..95e17b4d18 100644
--- a/public/language/it/reset_password.json
+++ b/public/language/it/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Nuova Password",
     "repeat_password": "Conferma la Password",
     "enter_email": "Inserisci il tuo <strong>indirizzo email</strong> e ti invieremo un'email con le istruzioni per resettare il tuo account.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Password Reset Inviata",
     "invalid_email": "Email invalida / L'email non esiste!"
 }
\ No newline at end of file
diff --git a/public/language/nb/reset_password.json b/public/language/nb/reset_password.json
index 5584ef61ab..5f442cefb0 100644
--- a/public/language/nb/reset_password.json
+++ b/public/language/nb/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Nytt passord",
     "repeat_password": "Bekreft passord",
     "enter_email": "Vennligst skriv din <strong>e-post-adresse</strong> og vi vil sende den en e-post med instruksjoner om hvordan du tilbakestiller din konto.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Passord-tilbakestilling sendt",
     "invalid_email": "Ugyldig e-post / e-post eksisterer ikke"
 }
\ No newline at end of file
diff --git a/public/language/nl/reset_password.json b/public/language/nl/reset_password.json
index 823816168c..fc49520840 100644
--- a/public/language/nl/reset_password.json
+++ b/public/language/nl/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Nieuw Wachtwoord",
     "repeat_password": "Bevestig Wachtwoord",
     "enter_email": "Vul a.u.b. je <strong>email address</strong> in en we versturen je een email met de stappen hoe je je account reset.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Wachtwoord Reset Verzonden",
     "invalid_email": "Fout Email Adres / Email Adres bestaat niet!"
 }
\ No newline at end of file
diff --git a/public/language/pl/reset_password.json b/public/language/pl/reset_password.json
index d701ef653e..a7a71f04fd 100644
--- a/public/language/pl/reset_password.json
+++ b/public/language/pl/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Nowe hasło",
     "repeat_password": "Powtórz hasło",
     "enter_email": "Podaj swój <strong>adres e-mail</strong> i wyślemy ci wiadomość z instrukcjami jak zresetować hasło.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Instrukcje zostały wysłane",
     "invalid_email": "Niepoprawny adres e-mail."
 }
\ No newline at end of file
diff --git a/public/language/pt_BR/reset_password.json b/public/language/pt_BR/reset_password.json
index 203e0430d3..d692ec79b7 100644
--- a/public/language/pt_BR/reset_password.json
+++ b/public/language/pt_BR/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Nova Senha",
     "repeat_password": "Confirmar Senha",
     "enter_email": "Por digite seu <strong>email</strong> nós enviaremos para você as instruções de como resetar sua senha.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Reset de Senha Enviado",
     "invalid_email": "Email inválido!"
 }
\ No newline at end of file
diff --git a/public/language/ru/reset_password.json b/public/language/ru/reset_password.json
index 62cc1ab610..127a0951cd 100644
--- a/public/language/ru/reset_password.json
+++ b/public/language/ru/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Новый Пароль",
     "repeat_password": "Подтвердите Пароль",
     "enter_email": "Пожалуйста введите ваш <strong>email адрес</strong> и мы отправим Вам письмо с инструкцией восстановления пароля.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Пароль Отправлен",
     "invalid_email": "Неверный Email / Email не существует!"
 }
\ No newline at end of file
diff --git a/public/language/sk/reset_password.json b/public/language/sk/reset_password.json
index 8ffd9eb4ca..b4123f8818 100644
--- a/public/language/sk/reset_password.json
+++ b/public/language/sk/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Nové heslo",
     "repeat_password": "Potvrdenie hesla",
     "enter_email": "Zadajte svoju <strong>emailovú adresu</strong> a my Vám pošleme informácie, ako môžete obnoviť svoje heslo.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Obnova hesla odoslaná",
     "invalid_email": "Zlý email / Email neexistuje!"
 }
\ No newline at end of file
diff --git a/public/language/sv/reset_password.json b/public/language/sv/reset_password.json
index f1a8b4d644..4b1b359450 100644
--- a/public/language/sv/reset_password.json
+++ b/public/language/sv/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Nytt lösenord",
     "repeat_password": "Bekräfta lösenord",
     "enter_email": "Var god fyll i din <strong>epost-adress</strong> så får du snart en epost med instruktioner hur du återsätller ditt konto.",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Lösenordsåterställning skickad",
     "invalid_email": "Felaktig epost / Epost finns inte!"
 }
\ No newline at end of file
diff --git a/public/language/tr/reset_password.json b/public/language/tr/reset_password.json
index d8aa05f826..3b7e92dba7 100644
--- a/public/language/tr/reset_password.json
+++ b/public/language/tr/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "Yeni Şifre",
     "repeat_password": "Şifreyi Onayla",
     "enter_email": "Lütfen <strong>e-posta adresinizi</strong> girin , size hesabınızı nasıl sıfırlayacağınızı anlatan bir e-posta gönderelim",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "Şifre Yenilemesi Gönderildi",
     "invalid_email": "Geçersiz E-posta / E-posta mevcut değil!"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/reset_password.json b/public/language/zh_CN/reset_password.json
index c8721eb686..ffd0ff528d 100644
--- a/public/language/zh_CN/reset_password.json
+++ b/public/language/zh_CN/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "新的密码",
     "repeat_password": "确认密码",
     "enter_email": "请输入您的<strong>Email地址</strong>,我们会发送邮件告诉您如何重置密码。",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "密码重置邮件已发送。",
     "invalid_email": "非法的邮箱地址/邮箱不存在!"
 }
\ No newline at end of file
diff --git a/public/language/zh_TW/reset_password.json b/public/language/zh_TW/reset_password.json
index 2192bda2c4..4ee1b9394c 100644
--- a/public/language/zh_TW/reset_password.json
+++ b/public/language/zh_TW/reset_password.json
@@ -8,6 +8,7 @@
     "new_password": "新的密碼",
     "repeat_password": "確認密碼",
     "enter_email": "請輸入您的<strong>Email地址</strong>,我們會發送郵件告訴您如何重置密碼。",
+    "enter_email_address": "Enter Email Address",
     "password_reset_sent": "密碼重置郵件已發送。",
     "invalid_email": "非法的郵箱地址/郵箱不存在!"
 }
\ No newline at end of file

From 8c11299197dec38fc733c0a87b5373bf907ed550 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 4 Mar 2014 14:56:05 -0500
Subject: [PATCH 182/193] template language fix

---
 app.js                            | 4 ++--
 public/src/forum/admin/index.js   | 1 +
 public/templates/accountposts.tpl | 6 +++---
 3 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/app.js b/app.js
index 877b08c79c..ec15115694 100644
--- a/app.js
+++ b/app.js
@@ -80,7 +80,7 @@ if (!nconf.get('help') && !nconf.get('setup') && !nconf.get('install') && !nconf
 	reset();
 } else {
 	displayHelp();
-};
+}
 
 function loadConfig() {
 	nconf.file({
@@ -153,7 +153,7 @@ function start() {
 					winston.warn('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:');
 					winston.warn('    node app --upgrade');
 					winston.warn('To ignore this error (not recommended):');
-					winston.warn('    node app --no-check-schema')
+					winston.warn('    node app --no-check-schema');
 					process.exit();
 				}
 			});
diff --git a/public/src/forum/admin/index.js b/public/src/forum/admin/index.js
index 7c9f6db3bd..aa63a15568 100644
--- a/public/src/forum/admin/index.js
+++ b/public/src/forum/admin/index.js
@@ -45,6 +45,7 @@ define(function() {
 			for(var key in data) {
 				uniqueVisitors.find('#' + key).text(data[key]);
 			}
+
 		});
 	};
 
diff --git a/public/templates/accountposts.tpl b/public/templates/accountposts.tpl
index 19744217d1..710b693ba6 100644
--- a/public/templates/accountposts.tpl
+++ b/public/templates/accountposts.tpl
@@ -5,7 +5,7 @@
 <div class="favourites">
 
 	<!-- IF !posts.length -->
-		<div class="alert alert-warning">[[topic:favourites.has_no_favourites]]</div>
+		<div class="alert alert-warning">[[user:has_no_posts]]</div>
 	<!-- ENDIF !posts.length -->
 
 	<div class="row">
@@ -25,8 +25,8 @@
 					<div>
 						<small>
 							<span class="pull-right">
-								<a href="../../topic/{posts.tid}/#{posts.pid}">posted</a>
-								in
+								<a href="../../topic/{posts.tid}/#{posts.pid}">[[global:posted]]</a>
+								[[global:in]]
 								<a href="../../category/{posts.categorySlug}">
 									<i class="fa {posts.categoryIcon}"></i> {posts.categoryName}
 								</a>

From d100a41ce7ec0db89ad25d6d64ffe9b769636e10 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 4 Mar 2014 15:23:26 -0500
Subject: [PATCH 183/193] fixes topic follow

---
 public/src/forum/topic.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index a74c3d18fe..a5bdc4f6c1 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -286,7 +286,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				set_follow_state(state, false);
 			});
 
-			$('.posts .follow').on('click', function() {
+			$('.posts').on('click', '.follow', function() {
 				socket.emit('topics.follow', tid, function(err, state) {
 					if(err) {
 						return app.alert({

From 76d8d09f9b1ebe00d1c3220ed1e1ca813492ea95 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Tue, 4 Mar 2014 16:48:07 -0500
Subject: [PATCH 184/193] closes #1141

---
 public/src/forum/topic.js   | 12 ++++++------
 public/templates/header.tpl | 22 +++++++++++++---------
 2 files changed, 19 insertions(+), 15 deletions(-)

diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index a5bdc4f6c1..6383cac823 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -12,8 +12,8 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 
 	$(window).on('action:ajaxify.start', function(ev, data) {
 		if(data.url.indexOf('topic') !== 0) {
-			$('.pagination-block').addClass('hide');
-			$('#header-topic-title').html('').hide();
+			$('.pagination-block').addClass('hidden');
+			$('.header-topic-title').find('span').text('').hide();
 			app.removeAlert('bookmark');
 		}
 	});
@@ -342,7 +342,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 
 		function enableInfiniteLoading() {
 			if(!config.usePagination) {
-				$('.pagination-block').removeClass('hide');
+				$('.pagination-block').removeClass('hidden');
 
 				app.enableInfiniteLoading(function(direction) {
 
@@ -372,7 +372,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					}
 				});
 			} else {
-				$('.pagination-block').addClass('hide');
+				$('.pagination-block').addClass('hidden');
 
 				pagination.init(currentPage, pageCount);
 			}
@@ -1026,9 +1026,9 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		});
 
 		if($(window).scrollTop() > 50) {
-			$('#header-topic-title').text(templates.get('topic_name')).show();
+			$('.header-topic-title').find('span').text(templates.get('topic_name')).show();
 		} else {
-			$('#header-topic-title').text('').hide();
+			$('.header-topic-title').find('span').text('').hide();
 		}
 
 		$($('.posts > .post-row').get().reverse()).each(function() {
diff --git a/public/templates/header.tpl b/public/templates/header.tpl
index decbae43da..9682271b8a 100644
--- a/public/templates/header.tpl
+++ b/public/templates/header.tpl
@@ -60,11 +60,16 @@
 					<a href="{relative_path}/">
 						<h1 class="navbar-brand forum-title">{title}</h1>
 					</a>
+
+					<div class="header-topic-title visible-xs">
+						<span></span>
+					</div>
+
 				</div>
 			</div>
 
 			<div class="navbar-collapse collapse navbar-ex1-collapse">
-				<ul id="main-nav" class="nav navbar-nav">
+				<ul id="main-nav" class="nav navbar-nav pull-left">
 					<li class="nodebb-loggedin">
 						<a href="{relative_path}/unread"><i id="unread-count" class="fa fa-fw fa-inbox" data-content="0" title="[[global:header.unread]]"></i><span class="visible-xs-inline"> [[global:header.unread]]</span></a>
 					</li>
@@ -104,7 +109,7 @@
 					<!-- END navigation -->
 				</ul>
 
-				<ul id="logged-in-menu" class="nav navbar-nav navbar-right hide">
+				<ul id="logged-in-menu" class="nav navbar-nav navbar-right hide pull-right">
 					<li>
 						<a href="#" id="reconnect" class="hide" title="Connection to {title} has been lost, attempting to reconnect..."><i class="fa fa-check"></i></a>
 					</li>
@@ -121,8 +126,8 @@
 						<a href="{relative_path}/notifications"><i class="fa fa-exclamation-triangle fa-fw" title="[[notifications:title]]"></i> [[notifications:title]]</a>
 					</li>
 
-					<li class="chats dropdown text-center hidden-xs">
-						<a class="dropdown-toggle" data-toggle="dropdown" href="#" id="chat_dropdown"><i class="fa fa-comment-o" title="[[global:header.chats]]"></i></a>
+					<li class="chats dropdown">
+						<a class="dropdown-toggle" data-toggle="dropdown" href="#" id="chat_dropdown"><i class="fa fa-comment-o fa-fw" title="[[global:header.chats]]"></i> <span class="visible-xs-inline">[[global:header.chats]]</span></a>
 						<ul id="chat-list" class="dropdown-menu" aria-labelledby="chat_dropdown">
 							<li>
 								<a href="#"><i class="fa fa-refresh fa-spin"></i> [[global:chats.loading]]</a>
@@ -156,10 +161,9 @@
 							</li>
 						</ul>
 					</li>
-
 				</ul>
 
-				<ul id="logged-out-menu" class="nav navbar-nav navbar-right">
+				<ul id="logged-out-menu" class="nav navbar-nav navbar-right pull-right">
 					<!-- IF allowRegistration -->
 					<li>
 						<a href="{relative_path}/register">
@@ -192,7 +196,7 @@
 				</ul>
 				<!-- ENDIF searchEnabled -->
 
-				<ul class="nav navbar-nav navbar-right pagination-block hide">
+				<ul class="nav navbar-nav navbar-right pagination-block hidden visible-lg visible-md">
 					<li class="active">
 						<a href="#">
 							<i class="fa fa-chevron-up pointer"></i>
@@ -205,8 +209,8 @@
 					</li>
 				</ul>
 
-				<div class="header-topic-title pull-right hidden-md-inline">
-					<span id="header-topic-title"></span>
+				<div class="header-topic-title pull-right hidden-xs">
+					<span></span>
 				</div>
 			</div>
 		</div>

From 976744480f945cc32b085fd05720a16f6537b9e3 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 5 Mar 2014 14:52:32 -0500
Subject: [PATCH 185/193] post summary change

---
 public/templates/account.tpl      |  4 ++--
 public/templates/accountposts.tpl |  6 +++---
 public/templates/favourites.tpl   |  8 ++++----
 public/templates/search.tpl       | 10 +++++-----
 src/posts.js                      |  8 +++-----
 5 files changed, 17 insertions(+), 19 deletions(-)

diff --git a/public/templates/account.tpl b/public/templates/account.tpl
index 7b660a185d..24fc37573b 100644
--- a/public/templates/account.tpl
+++ b/public/templates/account.tpl
@@ -140,8 +140,8 @@
 							<span class="pull-right">
 								<a href="../../topic/{posts.tid}/#{posts.pid}">[[global:posted]]</a>
 								[[global:in]]
-								<a href="../../category/{posts.categorySlug}">
-									<i class="fa {posts.categoryIcon}"></i> {posts.categoryName}
+								<a href="../../category/{posts.category.slug}">
+									<i class="fa {posts.category.icon}"></i> {posts.category.name}
 								</a>
 								<span class="timeago" title="{posts.relativeTime}"></span>
 							</span>
diff --git a/public/templates/accountposts.tpl b/public/templates/accountposts.tpl
index 710b693ba6..a407030ea3 100644
--- a/public/templates/accountposts.tpl
+++ b/public/templates/accountposts.tpl
@@ -25,10 +25,10 @@
 					<div>
 						<small>
 							<span class="pull-right">
-								<a href="../../topic/{posts.tid}/#{posts.pid}">[[global:posted]]</a>
+								<a href="../../topic/{posts.topic.slug}#{posts.pid}">[[global:posted]]</a>
 								[[global:in]]
-								<a href="../../category/{posts.categorySlug}">
-									<i class="fa {posts.categoryIcon}"></i> {posts.categoryName}
+								<a href="../../category/{posts.category.slug}">
+									<i class="fa {posts.category.icon}"></i> {posts.category.name}
 								</a>
 								<span class="timeago" title="{posts.relativeTime}"></span>
 							</span>
diff --git a/public/templates/favourites.tpl b/public/templates/favourites.tpl
index 273b88cfc5..e793d0475b 100644
--- a/public/templates/favourites.tpl
+++ b/public/templates/favourites.tpl
@@ -25,10 +25,10 @@
 					<div>
 						<small>
 							<span class="pull-right">
-								<a href="../../topic/{posts.tid}/#{posts.pid}">posted</a>
-								in
-								<a href="../../category/{posts.categorySlug}">
-									<i class="fa {posts.categoryIcon}"></i> {posts.categoryName}
+								<a href="../../topic/{posts.topic.slug}#{posts.pid}">[[global:posted]]</a>
+								[[global:in]]
+								<a href="../../category/{posts.category.slug}">
+									<i class="fa {posts.category.icon}"></i> {posts.category.name}
 								</a>
 								<span class="timeago" title="{posts.relativeTime}"></span>
 							</span>
diff --git a/public/templates/search.tpl b/public/templates/search.tpl
index 393a6d7e04..ceb1082404 100644
--- a/public/templates/search.tpl
+++ b/public/templates/search.tpl
@@ -61,7 +61,7 @@
 			<!-- BEGIN posts -->
 			<div class="topic-row panel panel-default clearfix">
 				<div class="panel-body">
-					<a href="../../topic/{posts.topicSlug}#{posts.pid}" class="search-result-text">
+					<a href="../../topic/{posts.topic.slug}#{posts.pid}" class="search-result-text">
 						{posts.content}
 					</a>
 
@@ -71,10 +71,10 @@
 								<a href="../../user/{posts.userslug}">
 									<img title="{posts.username}" class="img-rounded user-img" src="{posts.picture}">
 								</a>
-								<a href="../../topic/{posts.topicSlug}#{posts.pid}">posted</a>
-								in
-								<a href="../../category/{posts.categorySlug}">
-									<i class="fa {posts.categoryIcon}"></i> {posts.categoryName}
+								<a href="../../topic/{posts.topic.slug}#{posts.pid}"> [[global:posted]]</a>
+								[[global:in]]
+								<a href="../../category/{posts.category.slug}">
+									<i class="fa {posts.category.icon}"></i> {posts.category.name}
 								</a>
 								<span class="timeago" title="{posts.relativeTime}"></span>
 							</span>
diff --git a/src/posts.js b/src/posts.js
index ac4a3d5e5a..812f60f78e 100644
--- a/src/posts.js
+++ b/src/posts.js
@@ -281,11 +281,9 @@ var db = require('./database'),
 							return callback(null);
 						}
 						categories.getCategoryFields(topicData.cid, ['name', 'icon', 'slug'], function(err, categoryData) {
-							postData.categoryName = categoryData.name;
-							postData.categoryIcon = categoryData.icon;
-							postData.categorySlug = categoryData.slug;
-							postData.title = validator.escape(topicData.title);
-							postData.topicSlug = topicData.slug;
+							postData.category = categoryData;
+							topicData.title = validator.escape(topicData.title);
+							postData.topic = topicData;
 							next(null, postData);
 						});
 					});

From 33a5a2177e361e76a55620355348138379076f37 Mon Sep 17 00:00:00 2001
From: MrWaffle <mrwafflewaffle@aim.com>
Date: Wed, 5 Mar 2014 20:57:32 +0100
Subject: [PATCH 186/193] Send the callback to the retry

---
 public/src/forum/admin/settings.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/src/forum/admin/settings.js b/public/src/forum/admin/settings.js
index 6f164115f4..d33a3f720e 100644
--- a/public/src/forum/admin/settings.js
+++ b/public/src/forum/admin/settings.js
@@ -9,7 +9,7 @@ define(['uploader'], function(uploader) {
 		// Come back in 125ms if the config isn't ready yet
 		if (!app.config) {
 			setTimeout(function() {
-				Settings.prepare();
+				Settings.prepare(callback);
 			}, 125);
 			return;
 		}

From c9c25bd1743601cb6c3444ba0a2325e033ed498b Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 5 Mar 2014 15:38:54 -0500
Subject: [PATCH 187/193] added getRecentPost

---
 src/posts.js | 35 ++++++++++++++++++++++++++++++++---
 1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/src/posts.js b/src/posts.js
index 812f60f78e..565f4b867f 100644
--- a/src/posts.js
+++ b/src/posts.js
@@ -197,6 +197,35 @@ var db = require('./database'),
 		});
 	};
 
+	Posts.getRecentPosts = function(uid, start, stop, term, callback) {
+		var terms = {
+			day: 86400000,
+			week: 604800000,
+			month: 2592000000
+		};
+
+		var since = terms.day;
+		if (terms[term]) {
+			since = terms[term];
+		}
+
+		var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1;
+
+		db.getSortedSetRevRangeByScore(['posts:pid', '+inf', Date.now() - since, 'LIMIT', start, count], function(err, pids) {
+			if(err) {
+				return callback(err);
+			}
+
+			async.filter(pids, function(pid, next) {
+				postTools.privileges(pid, uid, function(err, privileges) {
+					next(!err && privileges.read);
+				});
+			}, function(pids) {
+				Posts.getPostSummaryByPids(pids, true, callback);
+			});
+		});
+	};
+
 	Posts.addUserInfoToPost = function(post, callback) {
 		user.getUserFields(post.uid, ['username', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned'], function(err, userData) {
 			if (err) {
@@ -262,10 +291,10 @@ var db = require('./database'),
 
 						if (parseInt(postData.deleted, 10) === 1) {
 							return callback(null);
-						} else {
-							postData.relativeTime = utils.toISOString(postData.timestamp);
-							next(null, postData);
 						}
+
+						postData.relativeTime = utils.toISOString(postData.timestamp);
+						next(null, postData);
 					});
 				},
 				function(postData, next) {

From c028761857b3311a23ac8964573d0ef49f2c9766 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 5 Mar 2014 16:49:42 -0500
Subject: [PATCH 188/193] new route to get recent posts

---
 public/language/en_GB/global.json |  2 ++
 src/routes/api.js                 | 11 +++++++++++
 2 files changed, 13 insertions(+)

diff --git a/public/language/en_GB/global.json b/public/language/en_GB/global.json
index b41f69b915..48e8109bd7 100644
--- a/public/language/en_GB/global.json
+++ b/public/language/en_GB/global.json
@@ -64,6 +64,8 @@
 	"posted": "posted",
 	"in": "in",
 
+	"norecentposts": "No Recent Posts",
+	"norecenttopics": "No Recent Topics",
 	"recentposts": "Recent Posts",
 	"recentips": "Recently Logged In IPs",
 
diff --git a/src/routes/api.js b/src/routes/api.js
index 3274b4c584..1242e4b04a 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -276,6 +276,17 @@ var path = require('path'),
 				});
 			});
 
+			app.get('/recent/posts/:term?', function (req, res, next) {
+				var uid = (req.user) ? req.user.uid : 0;
+				posts.getRecentPosts(uid, 0, 19, req.params.term, function (err, data) {
+					if(err) {
+						return next(err);
+					}
+
+					res.json(data);
+				});
+			});
+
 			app.get('/recent/:term?', function (req, res, next) {
 				var uid = (req.user) ? req.user.uid : 0;
 				topics.getLatestTopics(uid, 0, 19, req.params.term, function (err, data) {

From 64aa89f5eee9cd1bef57ea91a5f7bd01885a717f Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Wed, 5 Mar 2014 19:33:10 -0500
Subject: [PATCH 189/193] updating upgrade script to use UTC timestamps
 *facepalm*. God, I hope I didn't break anything...

(first pass #1155)
---
 src/upgrade.js | 46 +++++++++++++++++++++++-----------------------
 1 file changed, 23 insertions(+), 23 deletions(-)

diff --git a/src/upgrade.js b/src/upgrade.js
index 0876aa7113..08b5594ab1 100644
--- a/src/upgrade.js
+++ b/src/upgrade.js
@@ -15,11 +15,11 @@ var db = require('./database'),
 
 	Upgrade = {},
 
-	minSchemaDate = new Date(2014, 0, 4).getTime(),		// This value gets updated every new MINOR version
+	minSchemaDate = Date.UTC(2014, 0, 4),		// This value gets updated every new MINOR version
 	schemaDate, thisSchemaDate,
 
 	// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema
-	latestSchema = new Date(2014, 1, 22).getTime();
+	latestSchema = Date.UTC(2014, 1, 22);
 
 Upgrade.check = function(callback) {
 	db.get('schemaDate', function(err, value) {
@@ -64,7 +64,7 @@ Upgrade.upgrade = function(callback) {
 			});
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 0, 5).getTime();
+			thisSchemaDate = Date.UTC(2014, 0, 5);
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
 
@@ -108,7 +108,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 0, 5, 14, 6).getTime();
+			thisSchemaDate = Date.UTC(2014, 0, 5, 14, 6);
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
 
@@ -257,7 +257,7 @@ Upgrade.upgrade = function(callback) {
 				});
 			}
 
-			thisSchemaDate = new Date(2014, 0, 7).getTime();
+			thisSchemaDate = Date.UTC(2014, 0, 7);
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
 
@@ -277,7 +277,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 0, 13, 12, 0).getTime();
+			thisSchemaDate = Date.UTC(2014, 0, 13, 12, 0);
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
 
@@ -300,7 +300,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 0, 19, 22, 19).getTime();
+			thisSchemaDate = Date.UTC(2014, 0, 19, 22, 19);
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
 
@@ -318,7 +318,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 0, 23, 16, 5).getTime();
+			thisSchemaDate = Date.UTC(2014, 0, 23, 16, 5);
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
 
@@ -342,7 +342,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 0, 25, 0, 0).getTime();
+			thisSchemaDate = Date.UTC(2014, 0, 25, 0, 0);
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
 
@@ -377,7 +377,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 0, 27, 12, 35).getTime();
+			thisSchemaDate = Date.UTC(2014, 0, 27, 12, 35);
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
 
@@ -421,7 +421,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 0, 30, 15, 0).getTime();
+			thisSchemaDate = Date.UTC(2014, 0, 30, 15, 0);
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
 
@@ -446,7 +446,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 0, 30, 16, 0).getTime();
+			thisSchemaDate = Date.UTC(2014, 0, 30, 16, 0);
 
 			function updateTopic(tid, next) {
 				Topics.getTopicFields(tid, ['postcount', 'viewcount'], function(err, topicData) {
@@ -494,7 +494,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 1, 2, 16, 0).getTime();
+			thisSchemaDate = Date.UTC(2014, 1, 2, 16, 0);
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
 
@@ -532,7 +532,7 @@ Upgrade.upgrade = function(callback) {
 		},
 		function(next) {
 
-			thisSchemaDate = new Date(2014, 1, 7, 16, 0).getTime();
+			thisSchemaDate = Date.UTC(2014, 1, 7, 16, 0);
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
 
@@ -577,7 +577,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 1, 9, 20, 50).getTime();
+			thisSchemaDate = Date.UTC(2014, 1, 9, 20, 50);
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
 
@@ -596,7 +596,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 1, 14, 20, 50).getTime();
+			thisSchemaDate = Date.UTC(2014, 1, 14, 20, 50);
 
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
@@ -646,7 +646,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 1, 14, 21, 50).getTime();
+			thisSchemaDate = Date.UTC(2014, 1, 14, 21, 50);
 
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
@@ -702,7 +702,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 1, 19, 18, 15).getTime();
+			thisSchemaDate = Date.UTC(2014, 1, 19, 18, 15);
 
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
@@ -728,7 +728,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 1, 20, 15, 30).getTime();
+			thisSchemaDate = Date.UTC(2014, 1, 20, 15, 30);
 
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
@@ -767,7 +767,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 1, 20, 16, 15).getTime();
+			thisSchemaDate = Date.UTC(2014, 1, 20, 16, 15);
 
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
@@ -787,7 +787,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 1, 20, 19, 45).getTime();
+			thisSchemaDate = Date.UTC(2014, 1, 20, 19, 45);
 
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
@@ -813,7 +813,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 1, 20, 20, 25).getTime();
+			thisSchemaDate = Date.UTC(2014, 1, 20, 20, 25);
 
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;
@@ -830,7 +830,7 @@ Upgrade.upgrade = function(callback) {
 			}
 		},
 		function(next) {
-			thisSchemaDate = new Date(2014, 1, 22).getTime();
+			thisSchemaDate = Date.UTC(2014, 1, 22);
 
 			if (schemaDate < thisSchemaDate) {
 				updatesMade = true;

From 5f52ef5f01c7003088c645acdc6d6cebf9365dd5 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Wed, 5 Mar 2014 19:41:16 -0500
Subject: [PATCH 190/193] removed all 0.3.x related upgrade scripts, and
 updated minSchemaDate

---
 src/upgrade.js | 640 +------------------------------------------------
 1 file changed, 1 insertion(+), 639 deletions(-)

diff --git a/src/upgrade.js b/src/upgrade.js
index 08b5594ab1..51f75232d8 100644
--- a/src/upgrade.js
+++ b/src/upgrade.js
@@ -15,7 +15,7 @@ var db = require('./database'),
 
 	Upgrade = {},
 
-	minSchemaDate = Date.UTC(2014, 0, 4),		// This value gets updated every new MINOR version
+	minSchemaDate = Date.UTC(2014, 1, 14, 21, 50),		// This value gets updated every new MINOR version
 	schemaDate, thisSchemaDate,
 
 	// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema
@@ -63,644 +63,6 @@ Upgrade.upgrade = function(callback) {
 				}
 			});
 		},
-		function(next) {
-			thisSchemaDate = Date.UTC(2014, 0, 5);
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				db.getListRange('categories:cid', 0, -1, function(err, cids) {
-					if(err) {
-						return next(err);
-					}
-
-					var timestamp = Date.now();
-
-					function upgradeCategory(cid, next) {
-						db.getSetMembers('cid:' + cid + ':active_users', function(err, uids) {
-							if(err) {
-								return next(err);
-							}
-
-							db.delete('cid:' + cid + ':active_users', function(err) {
-								if(err) {
-									return next(err);
-								}
-
-								for(var i=0; i<uids.length; ++i) {
-									db.sortedSetAdd('cid:' + cid + ':active_users', timestamp, uids[i]);
-								}
-								next();
-							});
-						});
-					}
-
-					async.each(cids, upgradeCategory, function(err) {
-						if(err) {
-							return next(err);
-						}
-						winston.info('[2014/1/5] Upgraded categories active users');
-						next();
-					});
-				});
-			} else {
-				winston.info('[2014/1/5] categories active users skipped');
-				next();
-			}
-		},
-		function(next) {
-			thisSchemaDate = Date.UTC(2014, 0, 5, 14, 6);
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				// Re-slugify all users
-				db.delete('userslug:uid', function(err) {
-					if (!err) {
-						db.getObjectValues('username:uid', function(err, uids) {
-							var	newUserSlug;
-
-							async.each(uids, function(uid, next) {
-								User.getUserField(uid, 'username', function(err, username) {
-									if(err) {
-										return next(err);
-									}
-									if(username) {
-										newUserSlug = Utils.slugify(username);
-										async.parallel([
-											function(next) {
-												User.setUserField(uid, 'userslug', newUserSlug, next);
-											},
-											function(next) {
-												db.setObjectField('userslug:uid', newUserSlug, uid, next);
-											}
-										], next);
-									} else {
-										winston.warn('uid '+ uid + ' doesn\'t have a valid username (' + username + '), skipping');
-										next(null);
-									}
-								});
-							}, function(err) {
-								winston.info('[2014/1/5] Re-slugify usernames (again)');
-								next(err);
-							});
-						});
-					}
-				});
-			} else {
-				winston.info('[2014/1/5] Re-slugify usernames (again) skipped');
-				next();
-			}
-		},
-		function(next) {
-			function upgradeUserPostsTopics(next) {
-
-				function upgradeUser(uid, next) {
-
-					function upgradeUserPosts(next) {
-
-						function addPostToUser(pid) {
-							Posts.getPostField(pid, 'timestamp', function(err, timestamp) {
-								db.sortedSetAdd('uid:' + uid + ':posts', timestamp, pid);
-							});
-						}
-
-						db.getListRange('uid:' + uid + ':posts', 0, -1, function(err, pids) {
-							if(err) {
-								return next(err);
-							}
-
-							if(!pids || !pids.length) {
-								return next();
-							}
-
-							db.delete('uid:' + uid + ':posts', function(err) {
-								for(var i = 0; i< pids.length; ++i)	{
-									addPostToUser(pids[i]);
-								}
-								next();
-							});
-						});
-					}
-
-					function upgradeUserTopics(next) {
-
-						function addTopicToUser(tid) {
-							Topics.getTopicField(tid, 'timestamp', function(err, timestamp) {
-								db.sortedSetAdd('uid:' + uid + ':topics', timestamp, tid);
-							});
-						}
-
-						db.getListRange('uid:' + uid + ':topics', 0, -1, function(err, tids) {
-							if(err) {
-								return next(err);
-							}
-
-							if(!tids || !tids.length) {
-								return next();
-							}
-
-							db.delete('uid:' + uid + ':topics', function(err) {
-								for(var i = 0; i< tids.length; ++i)	{
-									addTopicToUser(tids[i]);
-								}
-								next();
-							});
-						});
-					}
-
-					async.series([upgradeUserPosts, upgradeUserTopics], function(err, result) {
-						next(err);
-					});
-				}
-
-
-				db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) {
-					if(err) {
-						return next(err);
-					}
-
-					async.each(uids, upgradeUser, function(err, result) {
-						next(err);
-					});
-				});
-			}
-
-			function upgradeTopicPosts(next) {
-				function upgradeTopic(tid, next) {
-					function addPostToTopic(pid) {
-						Posts.getPostField(pid, 'timestamp', function(err, timestamp) {
-							db.sortedSetAdd('tid:' + tid + ':posts', timestamp, pid);
-						});
-					}
-
-					db.getListRange('tid:' + tid + ':posts', 0, -1, function(err, pids) {
-						if(err) {
-							return next(err);
-						}
-
-						if(!pids || !pids.length) {
-							return next();
-						}
-
-						db.delete('tid:' + tid + ':posts', function(err) {
-							for(var i = 0; i< pids.length; ++i) {
-								addPostToTopic(pids[i]);
-							}
-							next();
-						});
-					});
-				}
-
-				db.getSetMembers('topics:tid', function(err, tids) {
-					async.each(tids, upgradeTopic, function(err, results) {
-						next(err);
-					});
-				});
-			}
-
-			thisSchemaDate = Date.UTC(2014, 0, 7);
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				async.series([upgradeUserPostsTopics, upgradeTopicPosts], function(err, results) {
-					if(err) {
-						winston.err('Error upgrading '+ err.message);
-						return next(err);
-					}
-
-					winston.info('[2014/1/7] Updated topic and user posts to sorted set');
-					next();
-				});
-
-			} else {
-				winston.info('[2014/1/7] Update to topic and user posts to sorted set skipped');
-				next();
-			}
-		},
-		function(next) {
-			thisSchemaDate = Date.UTC(2014, 0, 13, 12, 0);
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				db.getObjectValues('username:uid', function(err, uids) {
-					async.eachSeries(uids, function(uid, next) {
-						Groups.joinByGroupName('registered-users', uid, next);
-					}, function(err) {
-						if(err) {
-							winston.err('Error upgrading '+ err.message);
-							process.exit();
-						} else {
-							winston.info('[2014/1/13] Set up "Registered Users" user group');
-							next();
-						}
-					});
-				});
-			} else {
-				winston.info('[2014/1/13] Set up "Registered Users" user group - skipped');
-				next();
-			}
-		},
-		function(next) {
-			thisSchemaDate = Date.UTC(2014, 0, 19, 22, 19);
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				db.getObjectValues('username:uid', function(err, uids) {
-					async.each(uids, function(uid, next) {
-						db.searchRemove('user', uid, next);
-					}, function(err) {
-						winston.info('[2014/1/19] Remove user search from Reds');
-						next();
-					});
-				});
-			} else {
-				winston.info('[2014/1/19] Remove user search from Reds -- skipped');
-				next();
-			}
-		},
-		function(next) {
-			thisSchemaDate = Date.UTC(2014, 0, 23, 16, 5);
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				Groups.getByGroupName('Administrators', {}, function(err, groupObj) {
-					if (err && err.message === 'gid-not-found') {
-						winston.info('[2014/1/23] Updating Administrators Group -- skipped');
-						return next();
-					}
-
-					Groups.update(groupObj.gid, {
-						name: 'administrators',
-						hidden: '1'
-					}, function() {
-						winston.info('[2014/1/23] Updating Administrators Group');
-						next();
-					});
-				});
-			} else {
-				winston.info('[2014/1/23] Updating Administrators Group -- skipped');
-				next();
-			}
-		},
-		function(next) {
-			thisSchemaDate = Date.UTC(2014, 0, 25, 0, 0);
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) {
-					if(err) {
-						return next(err);
-					}
-
-					if(!uids || !uids.length) {
-						winston.info('[2014/1/25] Updating User Gravatars to HTTPS  -- skipped');
-						return next();
-					}
-
-					var gravatar = require('gravatar');
-
-					function updateGravatar(uid, next) {
-						User.getUserFields(uid, ['email', 'picture', 'gravatarpicture'], function(err, userData) {
-							var gravatarPicture = User.createGravatarURLFromEmail(userData.email);
-							if(userData.picture === userData.gravatarpicture) {
-								User.setUserField(uid, 'picture', gravatarPicture);
-							}
-							User.setUserField(uid, 'gravatarpicture', gravatarPicture, next);
-						});
-					}
-
-					winston.info('[2014/1/25] Updating User Gravatars to HTTPS');
-					async.each(uids, updateGravatar, next);
-				});
-			} else {
-				winston.info('[2014/1/25] Updating User Gravatars to HTTPS -- skipped');
-				next();
-			}
-		},
-		function(next) {
-			thisSchemaDate = Date.UTC(2014, 0, 27, 12, 35);
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				var	activations = [];
-
-				if (Meta.config['social:facebook:secret'] && Meta.config['social:facebook:app_id']) {
-					activations.push(function(next) {
-						Plugins.toggleActive('nodebb-plugin-sso-facebook', function(result) {
-							winston.info('[2014/1/25] Activating Facebook SSO Plugin');
-							next();
-						});
-					});
-				}
-				if (Meta.config['social:twitter:key'] && Meta.config['social:twitter:secret']) {
-					activations.push(function(next) {
-						Plugins.toggleActive('nodebb-plugin-sso-twitter', function(result) {
-							winston.info('[2014/1/25] Activating Twitter SSO Plugin');
-							next();
-						});
-					});
-				}
-				if (Meta.config['social:google:secret'] && Meta.config['social:google:id']) {
-					activations.push(function(next) {
-						Plugins.toggleActive('nodebb-plugin-sso-google', function(result) {
-							winston.info('[2014/1/25] Activating Google SSO Plugin');
-							next();
-						});
-					});
-				}
-
-				async.parallel(activations, function(err) {
-					if (!err) {
-						winston.info('[2014/1/25] Done activating SSO plugins');
-					}
-
-					next(err);
-				});
-			} else {
-				winston.info('[2014/1/25] Activating SSO plugins, if set up -- skipped');
-				next();
-			}
-		},
-		function(next) {
-			thisSchemaDate = Date.UTC(2014, 0, 30, 15, 0);
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				if (Meta.config.defaultLang === 'en') {
-					Meta.configs.set('defaultLang', 'en_GB', next);
-				} else if (Meta.config.defaultLang === 'pt_br') {
-					Meta.configs.set('defaultLang', 'pt_BR', next);
-				} else if (Meta.config.defaultLang === 'zh_cn') {
-					Meta.configs.set('defaultLang', 'zh_CN', next);
-				} else if (Meta.config.defaultLang === 'zh_tw') {
-					Meta.configs.set('defaultLang', 'zh_TW', next);
-				} else {
-					winston.info('[2014/1/30] Fixing language settings -- skipped');
-					return next();
-				}
-
-				winston.info('[2014/1/30] Fixing language settings');
-				next();
-			} else {
-				winston.info('[2014/1/30] Fixing language settings -- skipped');
-				next();
-			}
-		},
-		function(next) {
-			thisSchemaDate = Date.UTC(2014, 0, 30, 16, 0);
-
-			function updateTopic(tid, next) {
-				Topics.getTopicFields(tid, ['postcount', 'viewcount'], function(err, topicData) {
-					if(err) {
-						next(err);
-					}
-
-					if(topicData) {
-						if(!topicData.postcount) {
-							topicData.postcount = 0;
-						}
-
-						if(!topicData.viewcount) {
-							topicData.viewcount = 0;
-						}
-
-						db.sortedSetAdd('topics:posts', topicData.postcount, tid);
-						db.sortedSetAdd('topics:views', topicData.viewcount, tid);
-					}
-
-					next();
-				});
-			}
-
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-
-
-				winston.info('[2014/1/30] Adding new topic sets');
-				db.getSortedSetRange('topics:recent', 0, -1, function(err, tids) {
-					if(err) {
-						return next(err);
-					}
-
-					async.each(tids, updateTopic, function(err) {
-						next(err);
-					});
-				});
-
-
-			} else {
-				winston.info('[2014/1/30] Adding new topic sets -- skipped');
-				next();
-			}
-		},
-		function(next) {
-			thisSchemaDate = Date.UTC(2014, 1, 2, 16, 0);
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				winston.info('[2014/2/6] Upvoting all favourited posts for each user');
-
-				User.getUsers('users:joindate', 0, -1, function (err, users) {
-					function getFavourites(user, next) {
-						function upvote(post, next) {
-							var pid = post.pid,
-								uid = user.uid;
-
-							if (post.uid !== uid) {
-								db.setAdd('pid:' + pid + ':upvote', uid);
-								db.sortedSetAdd('uid:' + uid + ':upvote', post.timestamp, pid);
-								db.incrObjectField('post:' + pid, 'votes');
-							}
-
-							next();
-						}
-
-						Posts.getFavourites(user.uid, 0, -1, function(err, posts) {
-							async.each(posts.posts, upvote, function(err) {
-								next(err);
-							});
-						});
-					}
-					async.each(users, getFavourites, function(err) {
-						next(err);
-					});
-				});
-			} else {
-				winston.info('[2014/2/6] Upvoting all favourited posts for each user -- skipped');
-				next();
-			}
-		},
-		function(next) {
-
-			thisSchemaDate = Date.UTC(2014, 1, 7, 16, 0);
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				winston.info('[2014/2/7] Updating category recent replies');
-				db.getListRange('categories:cid', 0, -1, function(err, cids) {
-
-					function updateCategory(cid, next) {
-						db.getSortedSetRevRange('categories:recent_posts:cid:' + cid, 0, - 1, function(err, pids) {
-							function updatePid(pid, next) {
-								Posts.getCidByPid(pid, function(err, realCid) {
-									if(err) {
-										return next(err);
-									}
-
-									if(parseInt(realCid, 10) !== parseInt(cid, 10)) {
-										Posts.getPostField(pid, 'timestamp', function(err, timestamp) {
-											db.sortedSetRemove('categories:recent_posts:cid:' + cid, pid);
-											db.sortedSetAdd('categories:recent_posts:cid:' + realCid, timestamp, pid);
-											next();
-										});
-									} else {
-										next();
-									}
-								});
-							}
-
-							async.each(pids, updatePid, next);
-						});
-					}
-
-					if(err) {
-						return next(err);
-					}
-
-					async.each(cids, updateCategory, next);
-				});
-
-
-			} else {
-				winston.info('[2014/2/7] Updating category recent replies -- skipped');
-				next();
-			}
-		},
-		function(next) {
-			thisSchemaDate = Date.UTC(2014, 1, 9, 20, 50);
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				db.delete('tid:lastFeedUpdate', function(err) {
-					if(err) {
-						winston.err('Error upgrading '+ err.message);
-						process.exit();
-					} else {
-						winston.info('[2014/2/9] Remove Topic LastFeedUpdate value, as feeds are now on-demand');
-						next();
-					}
-				});
-			} else {
-				winston.info('[2014/2/9] Remove Topic LastFeedUpdate value, as feeds are now on-demand - skipped');
-				next();
-			}
-		},
-		function(next) {
-			thisSchemaDate = Date.UTC(2014, 1, 14, 20, 50);
-
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				db.exists('topics:tid', function(err, exists) {
-					if(err) {
-						return next(err);
-					}
-
-					if(!exists) {
-						winston.info('[2014/2/14] Upgraded topics to sorted set - skipped');
-						return next();
-					}
-
-					db.getSetMembers('topics:tid', function(err, tids) {
-						if(err) {
-							return next(err);
-						}
-
-						if(!Array.isArray(tids)) {
-							winston.info('[2014/2/14] Upgraded topics to sorted set - skipped (cant find any tids)');
-							return next();
-						}
-
-						db.rename('topics:tid', 'topics:tid:old', function(err) {
-							if(err) {
-								return next(err);
-							}
-
-							async.each(tids, function(tid, next) {
-								Topics.getTopicField(tid, 'timestamp', function(err, timestamp) {
-									db.sortedSetAdd('topics:tid', timestamp, tid, next);
-								});
-							}, function(err) {
-								if(err) {
-									return next(err);
-								}
-								winston.info('[2014/2/14] Upgraded topics to sorted set');
-								db.delete('topics:tid:old', next);
-							});
-						});
-					});
-				});
-			} else {
-				winston.info('[2014/2/14] Upgrade topics to sorted set - skipped');
-				next();
-			}
-		},
-		function(next) {
-			thisSchemaDate = Date.UTC(2014, 1, 14, 21, 50);
-
-			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
-				db.exists('users:joindate', function(err, exists) {
-					if(err) {
-						return next(err);
-					}
-					if(!exists) {
-						winston.info('[2014/2/14] Added posts to sorted set - skipped');
-						return next();
-					}
-
-					db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) {
-						if(err) {
-							return next(err);
-						}
-
-						if(!Array.isArray(uids)) {
-							winston.info('[2014/2/14] Add posts to sorted set - skipped (cant find any uids)');
-							return next();
-						}
-
-						async.each(uids, function(uid, next) {
-							User.getPostIds(uid, 0, -1, function(err, pids) {
-								if(err) {
-									return next(err);
-								}
-
-								async.each(pids, function(pid, next) {
-									Posts.getPostField(pid, 'timestamp', function(err, timestamp) {
-										if(err) {
-											return next(err);
-										}
-										db.sortedSetAdd('posts:pid', timestamp, pid, next);
-									});
-								}, next);
-							});
-						}, function(err) {
-							if(err) {
-								return next(err);
-							}
-
-							winston.info('[2014/2/14] Added posts to sorted set');
-							next();
-						});
-					});
-				});
-
-			} else {
-				winston.info('[2014/2/14] Added posts to sorted set - skipped');
-				next();
-			}
-		},
 		function(next) {
 			thisSchemaDate = Date.UTC(2014, 1, 19, 18, 15);
 

From b404b0197f4707c5682647778f20236dfdfad37e Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Wed, 5 Mar 2014 19:51:16 -0500
Subject: [PATCH 191/193] final pass, resolved #1155

---
 src/upgrade.js | 54 +++++++++++++++++++++++++++++++++-----------------
 1 file changed, 36 insertions(+), 18 deletions(-)

diff --git a/src/upgrade.js b/src/upgrade.js
index 51f75232d8..9221d29cd0 100644
--- a/src/upgrade.js
+++ b/src/upgrade.js
@@ -38,6 +38,10 @@ Upgrade.check = function(callback) {
 	});
 };
 
+Upgrade.update = function(schemaDate, callback) {
+	db.set('schemaDate', schemaDate, callback);
+};
+
 Upgrade.upgrade = function(callback) {
 	var updatesMade = false;
 
@@ -67,8 +71,6 @@ Upgrade.upgrade = function(callback) {
 			thisSchemaDate = Date.UTC(2014, 1, 19, 18, 15);
 
 			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
 				db.setObjectField('widgets:home.tpl', 'motd', JSON.stringify([
 					{
 						"widget": "html",
@@ -82,7 +84,12 @@ Upgrade.upgrade = function(callback) {
 					Meta.configs.remove('show_motd');
 
 					winston.info('[2014/2/19] Updated MOTD to use the HTML widget.');
-					next(err);
+
+					if (err) {
+						next(err);
+					} else {
+						Upgrade.update(thisSchemaDate, next);
+					}
 				});
 			} else {
 				winston.info('[2014/2/19] Updating MOTD to use the HTML widget - skipped');
@@ -93,8 +100,6 @@ Upgrade.upgrade = function(callback) {
 			thisSchemaDate = Date.UTC(2014, 1, 20, 15, 30);
 
 			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
 				var container = '<div class="panel panel-default"><div class="panel-heading">{title}</div><div class="panel-body">{body}</div></div>';
 
 				db.setObjectField('widgets:category.tpl', 'sidebar', JSON.stringify([
@@ -121,7 +126,12 @@ Upgrade.upgrade = function(callback) {
 					}
 				]), function(err) {
 					winston.info('[2014/2/20] Adding Recent Replies, Active Users, and Moderator widgets to category sidebar.');
-					next(err);
+
+					if (err) {
+						next(err);
+					} else {
+						Upgrade.update(thisSchemaDate, next);
+					}
 				});
 			} else {
 				winston.info('[2014/2/20] Adding Recent Replies, Active Users, and Moderator widgets to category sidebar - skipped');
@@ -132,8 +142,6 @@ Upgrade.upgrade = function(callback) {
 			thisSchemaDate = Date.UTC(2014, 1, 20, 16, 15);
 
 			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
 				db.setObjectField('widgets:home.tpl', 'footer', JSON.stringify([
 					{
 						"widget": "forumstats",
@@ -141,7 +149,12 @@ Upgrade.upgrade = function(callback) {
 					}
 				]), function(err) {
 					winston.info('[2014/2/20] Adding Forum Stats Widget to the Homepage Footer.');
-					next(err);
+
+					if (err) {
+						next(err);
+					} else {
+						Upgrade.update(thisSchemaDate, next);
+					}
 				});
 			} else {
 				winston.info('[2014/2/20] Adding Forum Stats Widget to the Homepage Footer - skipped');
@@ -152,8 +165,6 @@ Upgrade.upgrade = function(callback) {
 			thisSchemaDate = Date.UTC(2014, 1, 20, 19, 45);
 
 			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
 				var container = '<div class="panel panel-default"><div class="panel-heading">{title}</div><div class="panel-body">{body}</div></div>';
 
 				db.setObjectField('widgets:home.tpl', 'sidebar', JSON.stringify([
@@ -167,7 +178,12 @@ Upgrade.upgrade = function(callback) {
 					}
 				]), function(err) {
 					winston.info('[2014/2/20] Updating Lavender MOTD');
-					next(err);
+
+					if (err) {
+						next(err);
+					} else {
+						Upgrade.update(thisSchemaDate, next);
+					}
 				});
 			} else {
 				winston.info('[2014/2/20] Updating Lavender MOTD - skipped');
@@ -178,8 +194,6 @@ Upgrade.upgrade = function(callback) {
 			thisSchemaDate = Date.UTC(2014, 1, 20, 20, 25);
 
 			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
 				db.setAdd('plugins:active', 'nodebb-widget-essentials', function(err) {
 					winston.info('[2014/2/20] Activating NodeBB Essential Widgets');
 					Plugins.reload(function() {
@@ -188,15 +202,18 @@ Upgrade.upgrade = function(callback) {
 				});
 			} else {
 				winston.info('[2014/2/20] Activating NodeBB Essential Widgets - skipped');
-				next();
+
+				if (err) {
+					next(err);
+				} else {
+					Upgrade.update(thisSchemaDate, next);
+				}
 			}
 		},
 		function(next) {
 			thisSchemaDate = Date.UTC(2014, 1, 22);
 
 			if (schemaDate < thisSchemaDate) {
-				updatesMade = true;
-
 				db.exists('categories:cid', function(err, exists) {
 					if(err) {
 						return next(err);
@@ -239,7 +256,8 @@ Upgrade.upgrade = function(callback) {
 									return next(err);
 								}
 								winston.info('[2014/2/22] Added categories to sorted set');
-								db.delete('categories:cid:old', next);
+								db.delete('categories:cid:old');
+								Upgrade.update(thisSchemaDate, next);
 							});
 						});
 					});

From 58cb51bb028d279d2338f00cb6dcdafaed8ad16e Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 5 Mar 2014 20:37:31 -0500
Subject: [PATCH 192/193] removed unused imagemagick require

---
 src/routes/user.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/routes/user.js b/src/routes/user.js
index 136473ce1b..641404f8a6 100644
--- a/src/routes/user.js
+++ b/src/routes/user.js
@@ -3,7 +3,6 @@ var fs = require('fs'),
 	winston = require('winston'),
 	nconf = require('nconf'),
 	async= require('async'),
-	imagemagick = require('node-imagemagick'),
 
 	user = require('./../user'),
 	posts = require('./../posts'),

From 096f352c82d107a0b137e48cdca8c32a3415bcff Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Wed, 5 Mar 2014 23:00:27 -0500
Subject: [PATCH 193/193] closes #1130

---
 package.json       |  2 +-
 src/image.js       | 36 ++++++++++++++----------------------
 src/routes/user.js |  4 ++++
 3 files changed, 19 insertions(+), 23 deletions(-)

diff --git a/package.json b/package.json
index 083db7f7a0..79d2d26c33 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
     "less-middleware": "0.1.12",
     "marked": "0.2.8",
     "async": "~0.2.8",
-    "node-imagemagick": "0.1.8",
+    "gm": "1.14.2",
     "gravatar": "1.0.6",
     "nconf": "~0.6.7",
     "sitemap": "~0.7.1",
diff --git a/src/image.js b/src/image.js
index 97bb237a16..5f92773fa8 100644
--- a/src/image.js
+++ b/src/image.js
@@ -1,6 +1,7 @@
+'use strict';
 
 var fs = require('fs'),
-	imagemagick = require('node-imagemagick'),
+	gm = require('gm').subClass({imageMagick: true}),
 	meta = require('./meta');
 
 var image = {};
@@ -11,35 +12,26 @@ image.resizeImage = function(path, extension, width, height, callback) {
 	}
 
 	if(extension === '.gif') {
-		imagemagick.convert([
-			path,
-			'-coalesce',
-			'-repage',
-			'0x0',
-			'-crop',
-			width+'x'+height+'+0+0',
-			'+repage',
-			path
-		], done);
+		gm().in(path)
+			.in('-coalesce')
+			.in('-resize')
+			.in(width+'x'+height)
+			.write(path, done);
 	} else {
-		imagemagick.crop({
-			srcPath: path,
-			dstPath: path,
-			width: width,
-			height: height
-		}, done);
+		gm(path)
+			.crop(width, height, 0, 0)
+			.write(path, done);
 	}
 };
 
 image.convertImageToPng = function(path, extension, callback) {
 	var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10);
 	if(convertToPNG && extension !== '.png') {
-		imagemagick.convert([path, 'png:-'], function(err, stdout) {
-			if(err) {
+		gm(path).toBuffer('png', function(err, buffer) {
+			if (err) {
 				return callback(err);
 			}
-
-			fs.writeFile(path, stdout, 'binary', callback);
+			fs.writeFile(path, buffer, 'binary', callback);
 		});
 	} else {
 		callback();
@@ -50,6 +42,6 @@ image.convertImageToBase64 = function(path, callback) {
 	fs.readFile(path, function(err, data) {
 		callback(err, data ? data.toString('base64') : null);
 	});
-}
+};
 
 module.exports = image;
\ No newline at end of file
diff --git a/src/routes/user.js b/src/routes/user.js
index 641404f8a6..db25182b2a 100644
--- a/src/routes/user.js
+++ b/src/routes/user.js
@@ -116,6 +116,7 @@ var fs = require('fs'),
 
 				var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256;
 				if (req.files.userPhoto.size > uploadSize * 1024) {
+					fs.unlink(req.files.userPhoto.path);
 					return res.send({
 						error: 'Images must be smaller than ' + uploadSize + ' kb!'
 					});
@@ -123,6 +124,7 @@ var fs = require('fs'),
 
 				var allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'];
 				if (allowedTypes.indexOf(req.files.userPhoto.type) === -1) {
+					fs.unlink(req.files.userPhoto.path);
 					return res.send({
 						error: 'Allowed image types are png, jpg and gif!'
 					});
@@ -130,6 +132,7 @@ var fs = require('fs'),
 
 				var extension = path.extname(req.files.userPhoto.name);
 				if (!extension) {
+					fs.unlink(req.files.userPhoto.path);
 					return res.send({
 						error: 'Error uploading file! Error : Invalid extension!'
 					});
@@ -184,6 +187,7 @@ var fs = require('fs'),
 					}
 
 					if(err) {
+						fs.unlink(req.files.userPhoto.path);
 						return res.send({error:err.message});
 					}