Merge remote-tracking branch 'origin/master' into email-revamp

v1.18.x
Julian Lam 11 years ago
commit 0ac9ec6001

@ -2,7 +2,7 @@
"name": "nodebb",
"license": "GPLv3 or later",
"description": "NodeBB Forum",
"version": "0.2.0",
"version": "0.2.1",
"homepage": "http://www.nodebb.org",
"repository": {
"type": "git",

@ -12,6 +12,7 @@
"reply": "Reply",
"edit": "Edit",
"delete": "Delete",
"fork": "Fork",
"banned": "banned",
"link": "Link",

@ -19,7 +19,7 @@ define(function() {
return parent.attr('data-uid');
}
function updateUserButtons() {
function updateUserBanButtons() {
jQuery('.ban-btn').each(function(index, element) {
var banBtn = $(element);
var uid = getUID(banBtn);
@ -27,15 +27,37 @@ define(function() {
banBtn.addClass('disabled');
else if (isUserBanned(banBtn))
banBtn.addClass('btn-warning');
else if (!isUserAdmin(banBtn))
banBtn.removeClass('disabled');
else
banBtn.removeClass('btn-warning');
updateUserAdminButtons();
});
}
function updateUserAdminButtons() {
jQuery('.admin-btn').each(function(index, element) {
var adminBtn = $(element);
var uid = getUID(adminBtn);
if (isUserAdmin(adminBtn)) {
adminBtn.attr('value', 'UnMake Admin').html('Remove Admin');
if (uid === yourid) {
adminBtn.addClass('disabled');
}
}
else if (isUserBanned(adminBtn))
adminBtn.addClass('disabled');
else if (!isUserBanned(adminBtn))
adminBtn.removeClass('disabled');
else
adminBtn.removeClass('btn-warning');
});
}
function initUsers() {
updateUserButtons();
updateUserBanButtons();
updateUserAdminButtons();
$('#users-container').on('click', '.ban-btn', function() {
var banBtn = $(this);
@ -49,17 +71,56 @@ define(function() {
socket.emit('api:admin.user.unbanUser', uid);
banBtn.removeClass('btn-warning');
parent.attr('data-banned', 0);
updateUserAdminButtons();
} else {
bootbox.confirm('Do you really want to ban "' + parent.attr('data-username') + '"?', function(confirm) {
if (confirm) {
socket.emit('api:admin.user.banUser', uid);
banBtn.addClass('btn-warning');
parent.attr('data-banned', 1);
updateUserAdminButtons();
}
});
}
}
return false;
});
$('#users-container').on('click', '.admin-btn', function() {
var adminBtn = $(this);
var isAdmin = isUserAdmin(adminBtn);
var parent = adminBtn.parents('.users-box');
var isBanned = isUserBanned(adminBtn);
var uid = getUID(adminBtn);
if(uid === yourid){
app.alert({
title: 'Error',
message: 'You can\'t remove yourself as Administrator!',
type: 'danger',
timeout: 5000
});
}
else if (!isAdmin) {
socket.emit('api:admin.user.makeAdmin', uid);
adminBtn.attr('value', 'UnMake Admin').html('Remove Admin');
parent.attr('data-admin', 1);
updateUserBanButtons();
} else if(uid !== yourid) {
bootbox.confirm('Do you really want to remove this user as admin "' + parent.attr('data-username') + '"?', function(confirm) {
if (confirm) {
socket.emit('api:admin.user.removeAdmin', uid);
adminBtn.attr('value', 'Make Admin').html('Make Admin');
parent.attr('data-admin', 0);
updateUserBanButtons();
}
});
}
return false;
});
}

@ -334,13 +334,13 @@ define(['composer'], function(composer) {
return false;
});
$('#post-container').delegate('.edit', 'click', function(e) {
$('#post-container').on('click', '.edit', function(e) {
var pid = $(this).parents('li').attr('data-pid');
composer.editPost(pid);
});
$('#post-container').delegate('.delete', 'click', function(e) {
$('#post-container').on('click', '.delete', function(e) {
var pid = $(this).parents('li').attr('data-pid'),
postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')),
deleteAction = !postEl.hasClass('deleted') ? true : false,
@ -369,6 +369,21 @@ define(['composer'], function(composer) {
}
});
$('#post-container').on('click', '.fork-post', function(e) {
var post = $(this).parents('li'),
pid = post.attr('data-pid');
socket.emit('api:topic.createTopicFromPost', {pid:pid}, function(err) {
if(err) {
return app.alertError(err.message);
}
post.fadeOut(500, function() {
post.remove();
});
});
});
$('#post-container').on('click', '.chat', function(e) {
var username = $(this).parents('li.row').attr('data-username');
var touid = $(this).parents('li.row').attr('data-uid');

@ -478,7 +478,6 @@ define(['taskbar'], function(taskbar) {
$(reader).on('loadend', function(e) {
var regex = /^data:.*;base64,(.*)$/;
console.log(file);
var matches = this.result.match(regex);
var fileData = {

@ -349,7 +349,9 @@
if (blockInfo) {
checkConditional('@first', blockInfo.iterator === 0);
checkConditional('!@first', blockInfo.iterator !== 0);
checkConditional('@last', blockInfo.iterator === blockInfo.total);
checkConditional('!@last', blockInfo.iterator !== blockInfo.total);
}
template = replace(namespace + d, data[d], template);

@ -76,6 +76,21 @@
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4 col-xs-12">
<div class="form-group">
<label for="cid-{categories.cid}-class">Custom Class</label>
<input id="cid-{categories.cid}-class" type="text" class="form-control" placeholder="col-md-6 col-xs-6" data-name="class" value="{categories.class}" />
</div>
</div>
<div class="col-sm-4 col-xs-12">
<div class="form-group">
<label for="cid-{categories.cid}-numRecentReplies"># of Recent Replies Displayed</label>
<input id="cid-{categories.cid}-numRecentReplies" type="text" class="form-control" placeholder="2" data-name="numRecentReplies" value="{categories.numRecentReplies}" />
</div>
</div>
</div>
<input type="hidden" data-name="order" data-value="{categories.order}"></input>
</form>

@ -33,6 +33,9 @@
<i class='fa fa-pencil'></i>
<span id='postcount'>{users.postcount}</span>
</div>
<div>
<a href="#" class="btn btn-default admin-btn">Make Admin</a>
</div>
<div>
<a href="#" class="btn btn-default ban-btn">Ban</a>
</div>

@ -4,7 +4,7 @@
<div class="row home" itemscope itemtype="http://www.schema.org/ItemList">
<!-- BEGIN categories -->
<div class="col-md-3 col-xs-6">
<div class="{categories.class}">
<a href="category/{categories.slug}" itemprop="url">
<meta itemprop="name" content="{categories.name}">
<h4><span class="badge {categories.badgeclass}">{categories.topic_count} </span> {categories.name}</h4>

@ -97,6 +97,9 @@
<div class="btn-group post-tools">
<button class="btn btn-sm btn-default edit" type="button" title="[[topic:edit]]"><i class="fa fa-pencil"></i></button>
<button class="btn btn-sm btn-default delete" type="button" title="[[topic:delete]]"><i class="fa fa-trash-o"></i></button>
<!-- IF !@first -->
<button class="btn btn-sm btn-default fork-post" type="button" title="[[topic:fork]]"><i class="fa fa-code-fork"></i></button>
<!-- ENDIF !@first -->
</div>
<!-- ENDIF posts.display_moderator_tools -->
</div>

@ -138,9 +138,7 @@ var db = require('./database.js'),
db.getSortedSetRevRange('categories:' + cid + ':tid', start, stop, callback);
};
Categories.getActiveUsers = function(cid, callback) {
db.getSetMembers('cid:' + cid + ':active_users', callback);
};
Categories.getAllCategories = function(current_user, callback) {
db.getListRange('categories:cid', 0, -1, function(err, cids) {
@ -217,6 +215,10 @@ var db = require('./database.js'),
};
Categories.getRecentReplies = function(cid, count, callback) {
if(count === 0) {
return callback(null, []);
}
db.getSortedSetRevRange('categories:recent_posts:cid:' + cid, 0, count - 1, function(err, pids) {
if (err) {
@ -264,25 +266,7 @@ var db = require('./database.js'),
});
};
Categories.moveActiveUsers = function(tid, oldCid, cid, callback) {
function updateUser(uid) {
Categories.addActiveUser(cid, uid);
Categories.isUserActiveIn(oldCid, uid, function(err, active) {
if (!err && !active) {
Categories.removeActiveUser(oldCid, uid);
}
});
}
topics.getUids(tid, function(err, uids) {
if (!err && uids) {
for (var i = 0; i < uids.length; ++i) {
updateUser(uids[i]);
}
}
});
};
Categories.getCategoryData = function(cid, callback) {
db.exists('category:' + cid, function(err, exists) {
@ -391,14 +375,38 @@ var db = require('./database.js'),
});
};
Categories.addActiveUser = function(cid, uid) {
Categories.addActiveUser = function(cid, uid, timestamp) {
if(parseInt(uid, 10)) {
db.setAdd('cid:' + cid + ':active_users', uid);
db.sortedSetAdd('cid:' + cid + ':active_users', timestamp, uid);
}
};
Categories.removeActiveUser = function(cid, uid) {
db.setRemove('cid:' + cid + ':active_users', uid);
db.sortedSetRemove('cid:' + cid + ':active_users', uid);
};
Categories.getActiveUsers = function(cid, callback) {
db.getSortedSetRevRange('cid:' + cid + ':active_users', 0, 15, callback);
};
Categories.moveActiveUsers = function(tid, oldCid, cid, callback) {
function updateUser(uid) {
Categories.addActiveUser(cid, uid);
Categories.isUserActiveIn(oldCid, uid, function(err, active) {
if (!err && !active) {
Categories.removeActiveUser(oldCid, uid);
}
});
}
topics.getUids(tid, function(err, uids) {
if (!err && uids) {
for (var i = 0; i < uids.length; ++i) {
updateUser(uids[i]);
}
}
});
};
Categories.onNewPostMade = function(uid, tid, pid, timestamp) {
@ -412,13 +420,7 @@ var db = require('./database.js'),
db.sortedSetAdd('categories:' + cid + ':tid', timestamp, tid);
}
db.setCount('cid:' + cid + ':active_users', function(err, amount) {
if (amount > 15) {
db.setRemoveRandom('cid:' + cid + ':active_users');
}
Categories.addActiveUser(cid, uid);
});
Categories.addActiveUser(cid, uid, timestamp);
});
}

@ -738,6 +738,21 @@
});
}
module.listRemoveAll = function(key, value, callback) {
db.collection('objects').update({_key: key }, { $pull: { array: value } }, function(err, result) {
if(err) {
if(callback) {
return callback(err);
}
return;
}
if(callback) {
callback(null, result);
}
});
}
module.getListRange = function(key, start, stop, callback) {
if(stop === -1) {

@ -402,6 +402,10 @@
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);
}

@ -134,6 +134,11 @@ var async = require('async'),
return next(new Error('unknown database : ' + config.database));
}
var allQuestions = install.redisQuestions.concat(install.mongoQuestions);
for(var x=0;x<allQuestions.length;x++) {
delete config[allQuestions[x].name];
}
config.bcrypt_rounds = 12;
config.upload_path = '/public/uploads';
config.use_port = config.use_port.slice(0, 1) === 'y';
@ -258,6 +263,21 @@ var async = require('async'),
}, function (err) {
meta.configs.init(next);
});
if (install.values) {
if (install.values['social:twitter:key'] && install.values['social:twitter:secret']) {
meta.configs.setOnEmpty('social:twitter:key', install.values['social:twitter:key']);
meta.configs.setOnEmpty('social:twitter:secret', install.values['social:twitter:secret']);
}
if (install.values['social:google:id'] && install.values['social:google:secret']) {
meta.configs.setOnEmpty('social:google:id', install.values['social:google:id']);
meta.configs.setOnEmpty('social:google:secret', install.values['social:google:secret']);
}
if (install.values['social:facebook:key'] && install.values['social:facebook:secret']) {
meta.configs.setOnEmpty('social:facebook:app_id', install.values['social:facebook:app_id']);
meta.configs.setOnEmpty('social:facebook:secret', install.values['social:facebook:secret']);
}
}
},
function (next) {
// Check if an administrator needs to be created

@ -137,7 +137,7 @@ var winston = require('winston'),
events.logPostDelete(uid, pid);
posts.getPostFields(pid, ['tid', 'uid'], function(err, postData) {
db.incrObjectFieldBy('topic:' + postData.tid, 'postcount', -1);
topics.decreasePostCount(postData.tid);
user.decrementUserFieldBy(postData.uid, 'postcount', 1, function(err, postcount) {
db.sortedSetAdd('users:postcount', postcount, postData.uid);
@ -193,7 +193,7 @@ var winston = require('winston'),
events.logPostRestore(uid, pid);
posts.getPostFields(pid, ['tid', 'uid', 'content'], function(err, postData) {
db.incrObjectFieldBy('topic:' + postData.tid, 'postcount', 1);
topics.increasePostCount(postData.tid);
user.incrementUserFieldBy(postData.uid, 'postcount', 1);

@ -28,63 +28,70 @@ var db = require('./database'),
return callback(new Error('invalid-user'), null);
}
topics.isLocked(tid, function(err, locked) {
if(err) {
return callback(err, null);
} else if(locked) {
return callback(new Error('topic-locked'), null);
}
db.incrObjectField('global', 'nextPid', function(err, pid) {
if(err) {
return callback(err, null);
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) {
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,
'editor': '',
'edited': 0,
'deleted': 0
};
db.setObject('post:' + pid, postData, function(err) {
if(err) {
return callback(err, null);
return next(err);
}
var timestamp = Date.now(),
postData = {
'pid': pid,
'uid': uid,
'tid': tid,
'content': newContent,
'timestamp': timestamp,
'reputation': 0,
'editor': '',
'edited': 0,
'deleted': 0
};
db.setObject('post:' + pid, postData);
db.incrObjectField('global', 'postCount');
topics.onNewPostMade(tid, pid, timestamp);
categories.onNewPostMade(uid, tid, pid, timestamp);
user.onNewPostMade(uid, tid, pid, timestamp);
plugins.fireHook('filter:post.get', postData, function(err, newPostData) {
if(err) {
return callback(err, null);
}
next(null, postData);
});
},
function(postData, next) {
plugins.fireHook('filter:post.get', postData, next);
},
function(postData, next) {
postTools.parse(postData.content, function(err, content) {
if(err) {
return next(err, null);
}
postTools.parse(newPostData.content, function(err, content) {
if(err) {
return callback(err, null);
}
newPostData.content = content;
postData.content = content;
plugins.fireHook('action:post.save', newPostData);
plugins.fireHook('action:post.save', postData);
db.searchIndex('post', content, pid);
db.searchIndex('post', content, postData.pid);
callback(null, newPostData);
});
});
next(null, postData);
});
});
}
], function(err, postData) {
callback(err, postData);
});
};

@ -55,9 +55,9 @@ var path = require('path'),
});
function iterator(category, callback) {
categories.getRecentReplies(category.cid, 2, function (err, posts) {
categories.getRecentReplies(category.cid, parseInt(category.numRecentReplies, 10), function (err, posts) {
category.posts = posts;
category.post_count = posts.length > 2 ? 2 : posts.length;
category.post_count = posts.length > 2 ? 2 : posts.length; // this was a hack to make metro work back in the day, post_count should just = length
callback(null);
});
}

@ -23,6 +23,50 @@ var async = require('async'),
(function(Topics) {
Topics.create = function(uid, title, cid, callback) {
db.incrObjectField('global', 'nextTid', function(err, tid) {
if(err) {
return callback(err);
}
db.setAdd('topics:tid', tid);
var slug = tid + '/' + utils.slugify(title),
timestamp = Date.now();
db.setObject('topic:' + tid, {
'tid': tid,
'uid': uid,
'cid': cid,
'title': title,
'slug': slug,
'timestamp': timestamp,
'lastposttime': 0,
'postcount': 0,
'viewcount': 0,
'locked': 0,
'deleted': 0,
'pinned': 0
}, function(err) {
if(err) {
return callback(err);
}
db.searchIndex('topic', title, tid);
user.addTopicIdToUser(uid, tid);
// in future it may be possible to add topics to several categories, so leaving the door open here.
db.sortedSetAdd('categories:' + cid + ':tid', timestamp, tid);
db.incrObjectField('category:' + cid, 'topic_count');
db.incrObjectField('global', 'topicCount');
feed.updateCategory(cid);
callback(null, tid);
});
});
};
Topics.post = function(uid, title, content, cid, callback) {
categoryTools.privileges(cid, uid, function(err, privileges) {
@ -61,41 +105,11 @@ var async = require('async'),
return callback(new Error('too-many-posts'), null);
}
db.incrObjectField('global', 'nextTid', function(err, tid) {
Topics.create(uid, title, cid, function(err, tid) {
if(err) {
return callback(err);
}
db.setAdd('topics:tid', tid);
var slug = tid + '/' + utils.slugify(title);
var timestamp = Date.now();
db.setObject('topic:' + tid, {
'tid': tid,
'uid': uid,
'cid': cid,
'title': title,
'slug': slug,
'timestamp': timestamp,
'lastposttime': 0,
'postcount': 0,
'viewcount': 0,
'locked': 0,
'deleted': 0,
'pinned': 0
});
db.searchIndex('topic', title, tid);
user.addTopicIdToUser(uid, tid);
// in future it may be possible to add topics to several categories, so leaving the door open here.
db.sortedSetAdd('categories:' + cid + ':tid', timestamp, tid);
db.incrObjectField('category:' + cid, 'topic_count');
db.incrObjectField('global', 'topicCount');
feed.updateCategory(cid);
Topics.reply(tid, uid, content, function(err, postData) {
if(err) {
return callback(err, null);
@ -184,6 +198,40 @@ var async = require('async'),
});
}
Topics.createTopicFromPost = function(pid, callback) {
posts.getPostData(pid, function(err, postData) {
if(err) {
return callback(err);
}
posts.getCidByPid(pid, function(err, cid) {
if(err) {
return callback(err);
}
// TODO : title should be given by client
var title = postData.content.substr(0, 20);
Topics.create(postData.uid, title, cid, function(err, tid) {
if(err) {
return callback(err);
}
Topics.removePostFromTopic(postData.tid, postData.pid);
Topics.decreasePostCount(postData.tid);
posts.setPostField(pid, 'tid', tid);
Topics.onNewPostMade(tid, postData.pid, postData.timestamp);
Topics.getTopicData(tid, function(err, topicData) {
callback(err, topicData);
});
});
});
});
}
Topics.getTopicData = function(tid, callback) {
db.getObject('topic:' + tid, function(err, data) {
if(err) {
@ -916,6 +964,10 @@ var async = require('async'),
db.incrObjectField('topic:' + tid, 'postcount', callback);
}
Topics.decreasePostCount = function(tid, callback) {
db.decrObjectField('topic:' + tid, 'postcount', callback);
}
Topics.increaseViewCount = function(tid, callback) {
db.incrObjectField('topic:' + tid, 'viewcount', callback);
}
@ -944,6 +996,10 @@ var async = require('async'),
db.listAppend('tid:' + tid + ':posts', pid);
}
Topics.removePostFromTopic = function(tid, pid) {
db.listRemoveAll('tid:' + tid + ':posts', pid);
}
Topics.getPids = function(tid, callback) {
db.getListRange('tid:' + tid + ':posts', 0, -1, callback);
}

@ -14,7 +14,7 @@ var db = require('./database'),
Upgrade.check = function(callback) {
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema
var latestSchema = new Date(2014, 0, 1).getTime();
var latestSchema = new Date(2014, 0, 5).getTime();
db.get('schemaDate', function(err, value) {
if (parseInt(value, 10) >= latestSchema) {
@ -65,8 +65,16 @@ Upgrade.upgrade = function(callback) {
async.each(uids, function(uid, next) {
User.getUserField(uid, 'username', function(err, username) {
newUserSlug = Utils.slugify(username);
User.setUserField(uid, 'userslug', newUserSlug, next);
if(err) {
return next(err);
}
if(username) {
newUserSlug = Utils.slugify(username);
User.setUserField(uid, 'userslug', newUserSlug, next);
} else {
winston.warn('uid '+ uid + ' doesn\'t have a valid username (' + username + '), skipping');
next(null);
}
});
}, function(err) {
next(err);
@ -108,6 +116,97 @@ Upgrade.upgrade = function(callback) {
winston.info('[2013/12/31] maximumTitleLength skipped');
next();
}
},
function(next) {
// Custom classes for each category, adding link field for each category
thisSchemaDate = new Date(2014, 0, 3).getTime();
if (schemaDate < thisSchemaDate) {
updatesMade = true;
db.getListRange('categories:cid', 0, -1, function(err, cids) {
if(err) {
return next(err);
}
for (var cid in cids) {
db.setObjectField('category:' + cids[cid], 'link', '');
db.setObjectField('category:' + cids[cid], 'class', 'col-md-3 col-xs-6');
}
winston.info('[2014/1/3] Added categories.class, categories.link fields');
next();
});
} else {
winston.info('[2014/1/3] categories.class, categories.link fields skipped');
next();
}
},
function(next) {
// Custom classes for each category, adding link field for each category
thisSchemaDate = new Date(2014, 0, 4).getTime();
if (schemaDate < thisSchemaDate) {
updatesMade = true;
db.getListRange('categories:cid', 0, -1, function(err, cids) {
if(err) {
return next(err);
}
for (var cid in cids) {
db.setObjectField('category:' + cids[cid], 'numRecentReplies', '2');
}
winston.info('[2014/1/4] Added categories.numRecentReplies fields');
next();
});
} else {
winston.info('[2014/1/4] categories.numRecentReplies fields skipped');
next();
}
},
function(next) {
thisSchemaDate = new Date(2014, 0, 5).getTime();
if (schemaDate < thisSchemaDate) {
updatesMade = true;
db.getListRange('categories:cid', 0, -1, function(err, cids) {
if(err) {
return next(err);
}
var timestamp = Date.now();
function upgradeCategory(cid, next) {
db.getSetMembers('cid:' + cid + ':active_users', function(err, uids) {
if(err) {
return next(err);
}
db.delete('cid:' + cid + ':active_users', function(err) {
if(err) {
return next(err);
}
for(var i=0; i<uids.length; ++i) {
db.sortedSetAdd('cid:' + cid + ':active_users', timestamp, uids[i]);
}
next();
});
});
}
async.each(cids, upgradeCategory, function(err) {
if(err) {
return next(err)
}
winston.info('[2014/1/5] Upgraded categories active users');
next();
});
});
} else {
winston.info('[2014/1/5] categories active users skipped');
next();
}
}
// Add new schema updates here
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 17!!!

@ -622,6 +622,12 @@ websockets.init = function(io) {
});
});
socket.on('api:topic.createTopicFromPost', function(data, callback) {
topics.createTopicFromPost(data.pid, function(err, data) {
callback(err?{message:err.message}:null, data);
});
});
socket.on('api:topic.move', function(data) {
threadTools.move(data.tid, data.cid, socket);
});

Loading…
Cancel
Save