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