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 01/19] 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 02/19] 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 03/19] 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 04/19] 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 05/19] 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 06/19] 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 07/19] 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 08/19] 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 09/19] 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 10/19] 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 11/19] 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 12/19] 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 13/19] #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 14/19] 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 15/19] 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 16/19] 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 17/19] 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 18/19] 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 19/19] 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