Merge remote-tracking branch 'origin/master' into webserver.js-refactor

v1.18.x
psychobunny 11 years ago
commit 607bc8545b

@ -14,10 +14,7 @@ var nconf = require('nconf'),
nbb.on('message', function(cmd) { nbb.on('message', function(cmd) {
if (cmd === 'nodebb:restart') { if (cmd === 'nodebb:restart') {
nbb.on('exit', function() { nbb_restart();
nbb_start();
});
nbb.kill();
} }
}); });
}, },
@ -29,10 +26,17 @@ var nconf = require('nconf'),
fs.unlinkSync(pidFilePath); fs.unlinkSync(pidFilePath);
} }
} }
},
nbb_restart = function() {
nbb.on('exit', function() {
nbb_start();
});
nbb.kill();
}; };
process.on('SIGINT', nbb_stop); process.on('SIGINT', nbb_stop);
process.on('SIGTERM', nbb_stop); process.on('SIGTERM', nbb_stop);
process.on('SIGHUP', nbb_restart);
nbb_start(); nbb_start();
}, },

@ -9,6 +9,7 @@ case "$1" in
echo "Starting NodeBB"; echo "Starting NodeBB";
echo " \"./nodebb stop\" to stop the NodeBB server"; echo " \"./nodebb stop\" to stop the NodeBB server";
echo " \"./nodebb log\" to view server output"; echo " \"./nodebb log\" to view server output";
echo "" > ./logs/output.log;
node loader -d "$@" node loader -d "$@"
;; ;;
@ -17,7 +18,13 @@ case "$1" in
kill `cat pidfile`; kill `cat pidfile`;
;; ;;
reload|restart)
echo "Restarting NodeBB.";
kill -1 `cat pidfile`;
;;
log) log)
clear;
tail -F ./logs/output.log; tail -F ./logs/output.log;
;; ;;
@ -54,11 +61,13 @@ case "$1" in
*) *)
echo "Welcome to NodeBB" echo "Welcome to NodeBB"
echo $"Usage: $0 {start|stop|log|setup|reset|upgrade|dev|watch}" echo $"Usage: $0 {start|stop|reload|restart|log|setup|reset|upgrade|dev|watch}"
echo '' echo ''
column -s ' ' -t <<< ' column -s ' ' -t <<< '
start Start the NodeBB server start Start the NodeBB server
stop Stops the NodeBB server stop Stops the NodeBB server
reload Restarts NodeBB
restart Restarts NodeBB
log Opens the logging interface (useful for debugging) log Opens the logging interface (useful for debugging)
setup Runs the NodeBB setup script setup Runs the NodeBB setup script
reset Disables all plugins, restores the default theme. reset Disables all plugins, restores the default theme.

@ -21,7 +21,9 @@ var ajaxify = {};
window.onpopstate = function (event) { window.onpopstate = function (event) {
if (event !== null && event.state && event.state.url !== undefined && !ajaxify.initialLoad) { if (event !== null && event.state && event.state.url !== undefined && !ajaxify.initialLoad) {
ajaxify.go(event.state.url, null, true); ajaxify.go(event.state.url, function() {
$(window).trigger('action:popstate', {url: event.state.url});
}, true);
} }
}; };
@ -29,6 +31,7 @@ var ajaxify = {};
ajaxify.initialLoad = false; ajaxify.initialLoad = false;
ajaxify.go = function (url, callback, quiet) { ajaxify.go = function (url, callback, quiet) {
// "quiet": If set to true, will not call pushState // "quiet": If set to true, will not call pushState
app.enterRoom('global'); app.enterRoom('global');
@ -101,7 +104,7 @@ var ajaxify = {};
} }
}); });
if (callback) { if (typeof callback === 'function') {
callback(); callback();
} }
@ -129,7 +132,7 @@ var ajaxify = {};
$this.addClass($this.attr('no-widget-class')); $this.addClass($this.attr('no-widget-class'));
}); });
} }
next(err); next(err);
}); });
}, function(err) { }, function(err) {

@ -448,13 +448,14 @@ var socket,
app.enableInfiniteLoading = function(callback) { app.enableInfiniteLoading = function(callback) {
$(window).off('scroll').on('scroll', function() { $(window).off('scroll').on('scroll', function() {
var top = $(window).height() * 0.1; var top = $(window).height() * 0.1;
var bottom = ($(document).height() - $(window).height()) * 0.9; var bottom = ($(document).height() - $(window).height()) * 0.9;
var currentScrollTop = $(window).scrollTop(); var currentScrollTop = $(window).scrollTop();
if($(window).scrollTop() < top && previousScrollTop > currentScrollTop) { if(currentScrollTop < top && currentScrollTop < previousScrollTop) {
callback(-1); callback(-1);
} else if ($(window).scrollTop() > bottom && previousScrollTop < currentScrollTop) { } else if (currentScrollTop > bottom && currentScrollTop > previousScrollTop) {
callback(1); callback(1);
} }
previousScrollTop = currentScrollTop; previousScrollTop = currentScrollTop;

@ -38,13 +38,128 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
socket.on('event:new_topic', Category.onNewTopic); socket.on('event:new_topic', Category.onNewTopic);
enableInfiniteLoading(); enableInfiniteLoading();
$('#topics-container').on('click', '.topic-title', function() {
var clickedTid = $(this).parents('li.category-item[data-tid]').attr('data-tid');
$('#topics-container li.category-item').each(function(index, el) {
if($(el).offset().top - $(window).scrollTop() > 0) {
tid = $(el).attr('data-tid');
localStorage.setItem('category:bookmark', tid);
localStorage.setItem('category:bookmark:clicked', clickedTid);
return false;
}
});
});
};
$(window).on('action:popstate', function(ev, data) {
if(data.url.indexOf('category/') === 0) {
var bookmark = localStorage.getItem('category:bookmark');
var clicked = localStorage.getItem('category:bookmark:clicked');
if (bookmark) {
if(config.usePagination) {
socket.emit('topics.getTidPage', bookmark, function(err, page) {
if(err) {
return;
}
if(parseInt(page, 10) !== pagination.currentPage) {
pagination.loadPage(page);
} else {
Category.scrollToTopic(bookmark, clicked, 400);
}
});
} else {
socket.emit('topics.getTidIndex', bookmark, function(err, index) {
if(err) {
return;
}
if(index === 0) {
Category.highlightTopic(clicked);
return;
}
if (index < 0) {
index = 0;
}
$('#topics-container').empty();
loadingMoreTopics = false;
Category.loadMoreTopics(templates.get('category_id'), index, function() {
Category.scrollToTopic(bookmark, clicked, 0);
});
});
}
}
}
});
Category.highlightTopic = function(tid) {
var highlight = $('#topics-container li.category-item[data-tid="' + tid + '"]');
if(highlight.length && !highlight.hasClass('highlight')) {
highlight.addClass('highlight');
setTimeout(function() {
highlight.removeClass('highlight');
}, 5000);
}
};
Category.scrollToTopic = function(tid, clickedTid, duration, offset) {
if(!tid) {
return;
}
if(!offset) {
offset = 0;
}
if($('#topics-container li.category-item[data-tid="' + tid + '"]').length) {
var cid = templates.get('category_id');
var scrollTo = $('#topics-container li.category-item[data-tid="' + tid + '"]');
if (cid && scrollTo.length) {
$('html, body').animate({
scrollTop: (scrollTo.offset().top - $('#header-menu').height() - offset) + 'px'
}, duration !== undefined ? duration : 400, function() {
Category.highlightTopic(clickedTid);
});
}
}
}; };
function enableInfiniteLoading() { function enableInfiniteLoading() {
if(!config.usePagination) { if(!config.usePagination) {
app.enableInfiniteLoading(function() { app.enableInfiniteLoading(function(direction) {
if(!loadingMoreTopics) {
Category.loadMoreTopics(templates.get('category_id')); if(!loadingMoreTopics && $('#topics-container').children().length) {
var after = 0;
var el = null;
if(direction > 0) {
el = $('#topics-container .category-item[data-tid]').last();
after = parseInt(el.attr('data-index'), 10) + 1;
} else {
el = $('#topics-container .category-item[data-tid]').first();
after = parseInt(el.attr('data-index'), 10);
after -= config.topicsPerPage;
if(after < 0) {
after = 0;
}
}
var offset = el.offset().top - $('#header-menu').offset().top + $('#header-menu').height();
Category.loadMoreTopics(templates.get('category_id'), after, function() {
if(direction < 0 && el) {
Category.scrollToTopic(el.attr('data-tid'), null, 0, offset);
}
});
} }
}); });
} else { } else {
@ -94,56 +209,112 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
$(window).trigger('action:categories.new_topic.loaded'); $(window).trigger('action:categories.new_topic.loaded');
}); });
} };
Category.onTopicsLoaded = function(topics, callback) {
if(!topics || !topics.length) {
return;
}
function removeAlreadyAddedTopics() {
topics = topics.filter(function(topic) {
return $('#topics-container li[data-tid="' + topic.tid +'"]').length === 0;
});
}
var after = null,
before = null;
function findInsertionPoint() {
if (!$('#topics-container .category-item[data-tid]').length) {
return;
}
var last = $('#topics-container .category-item[data-tid]').last();
var lastIndex = last.attr('data-index');
var firstIndex = topics[topics.length - 1].index;
if (firstIndex > lastIndex) {
after = last;
} else {
before = $('#topics-container .category-item[data-tid]').first();
}
}
removeAlreadyAddedTopics();
if(!topics.length) {
return;
}
findInsertionPoint();
Category.onTopicsLoaded = function(topics) {
var html = templates.prepare(templates['category'].blocks['topics']).parse({ var html = templates.prepare(templates['category'].blocks['topics']).parse({
topics: topics topics: topics
}); });
translator.translate(html, function(translatedHTML) { translator.translate(html, function(translatedHTML) {
var container = $('#topics-container'); var container = $('#topics-container'),
html = $(translatedHTML);
$('#topics-container, .category-sidebar').removeClass('hidden'); $('#topics-container, .category-sidebar').removeClass('hidden');
$('#category-no-topics').remove(); $('#category-no-topics').remove();
html = $(translatedHTML);
if(config.usePagination) { if(config.usePagination) {
container.empty().append(html); container.empty().append(html);
} else { } else {
container.append(html); if(after) {
html.insertAfter(after);
} else if(before) {
html.insertBefore(before);
} else {
container.append(html);
}
} }
$('#topics-container span.timeago').timeago(); $('#topics-container span.timeago').timeago();
app.createUserTooltips(); app.createUserTooltips();
app.makeNumbersHumanReadable(html.find('.human-readable-number')); app.makeNumbersHumanReadable(html.find('.human-readable-number'));
if (typeof callback === 'function') {
callback(topics);
}
}); });
} };
Category.loadMoreTopics = function(cid) { Category.loadMoreTopics = function(cid, after, callback) {
if (loadingMoreTopics || !$('#topics-container').length) { if (loadingMoreTopics || !$('#topics-container').length) {
return; return;
} }
if(after === 0 && $('#topics-container li.category-item[data-index="0"]').length) {
return;
}
$(window).trigger('action:categories.loading'); $(window).trigger('action:categories.loading');
loadingMoreTopics = true; loadingMoreTopics = true;
socket.emit('categories.loadMore', { socket.emit('categories.loadMore', {
cid: cid, cid: cid,
after: $('#topics-container').attr('data-nextstart') after: after
}, function (err, data) { }, function (err, data) {
loadingMoreTopics = false;
if(err) { if(err) {
return app.alertError(err.message); return app.alertError(err.message);
} }
if (data && data.topics.length) { if (data && data.topics.length) {
Category.onTopicsLoaded(data.topics); Category.onTopicsLoaded(data.topics, callback);
$('#topics-container').attr('data-nextstart', data.nextStart); $('#topics-container').attr('data-nextstart', data.nextStart);
} else {
if (typeof callback === 'function') {
callback(data.topics);
}
} }
loadingMoreTopics = false;
$(window).trigger('action:categories.loaded'); $(window).trigger('action:categories.loaded');
}); });
} };
return Category; return Category;
}); });

@ -1027,14 +1027,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
} }
$('#pagination').html(index + ' out of ' + Topic.postCount); $('#pagination').html(index + ' out of ' + Topic.postCount);
$('.progress-bar').width((index / Topic.postCount * 100) + '%'); $('.progress-bar').width((index / Topic.postCount * 100) + '%');
return false;
}
});
$('.posts > .post-row').each(function() {
var el = $(this);
if (elementInView(el)) {
if(!parseInt(el.attr('data-index'), 10)) { if(!parseInt(el.attr('data-index'), 10)) {
localStorage.removeItem('topic:' + templates.get('topic_id') + ':bookmark'); localStorage.removeItem('topic:' + templates.get('topic_id') + ':bookmark');
} else { } else {
@ -1063,7 +1056,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
var elTop = el.offset().top; var elTop = el.offset().top;
var elBottom = elTop + Math.floor(el.height()); var elBottom = elTop + Math.floor(el.height());
return !(elTop > scrollBottom || elBottom < scrollTop); return (elTop >= scrollTop && elBottom <= scrollBottom) || (elTop <= scrollTop && elBottom >= scrollTop);
} }
Topic.scrollToPost = function(pid, highlight, duration, offset) { Topic.scrollToPost = function(pid, highlight, duration, offset) {

@ -20,9 +20,7 @@
<ul id="users-container" class="users admin"> <ul id="users-container" class="users admin">
<!-- BEGIN users --> <!-- BEGIN users -->
<div class="users-box" data-uid="{users.uid}" data-admin="{users.administrator}" data-username="{users.username}" data-banned="{users.banned}"> <div class="users-box" data-uid="{users.uid}" data-admin="{users.administrator}" data-username="{users.username}" data-banned="{users.banned}">
<a href="{relative_path}/user/{users.userslug}"> <a href="{relative_path}/user/{users.userslug}"><img src="{users.picture}" class="img-thumbnail"/></a>
<img src="{users.picture}" class="img-thumbnail"/>
</a>
<br/> <br/>
<a href="{relative_path}/user/{users.userslug}">{users.username}</a> <a href="{relative_path}/user/{users.userslug}">{users.username}</a>
<br/> <br/>

@ -39,7 +39,7 @@
<ul id="topics-container" itemscope itemtype="http://www.schema.org/ItemList" data-nextstart="{nextStart}"> <ul id="topics-container" itemscope itemtype="http://www.schema.org/ItemList" data-nextstart="{nextStart}">
<meta itemprop="itemListOrder" content="descending"> <meta itemprop="itemListOrder" content="descending">
<!-- BEGIN topics --> <!-- BEGIN topics -->
<li class="category-item <!-- IF topics.deleted -->deleted<!-- ENDIF topics.deleted --><!-- IF topics.unread -->unread<!-- ENDIF topics.unread -->" itemprop="itemListElement"> <li class="category-item <!-- IF topics.deleted -->deleted<!-- ENDIF topics.deleted --><!-- IF topics.unread -->unread<!-- ENDIF topics.unread -->" itemprop="itemListElement" data-tid="{topics.tid}" data-index="{topics.index}">
<div class="col-md-12 col-xs-12 panel panel-default topic-row"> <div class="col-md-12 col-xs-12 panel panel-default topic-row">

File diff suppressed because one or more lines are too long

@ -85,11 +85,13 @@ var db = require('./database'),
}; };
Categories.getCategoryTopics = function(cid, start, stop, uid, callback) { Categories.getCategoryTopics = function(cid, start, stop, uid, callback) {
var tids;
async.waterfall([ async.waterfall([
function(next) { function(next) {
Categories.getTopicIds(cid, start, stop, next); Categories.getTopicIds(cid, start, stop, next);
}, },
function(tids, next) { function(topicIds, next) {
tids = topicIds;
topics.getTopicsByTids(tids, uid, next); topics.getTopicsByTids(tids, uid, next);
}, },
function(topics, next) { function(topics, next) {
@ -100,6 +102,15 @@ var db = require('./database'),
}); });
} }
var indices = {};
for(var i=0; i<tids.length; ++i) {
indices[tids[i]] = start + i;
}
for(var i=0; i<topics.length; ++i) {
topics[i].index = indices[topics[i].tid];
}
db.sortedSetRevRank('categories:' + cid + ':tid', topics[topics.length - 1].tid, function(err, rank) { db.sortedSetRevRank('categories:' + cid + ':tid', topics[topics.length - 1].tid, function(err, rank) {
if(err) { if(err) {
return next(err); return next(err);
@ -118,6 +129,16 @@ var db = require('./database'),
db.getSortedSetRevRange('categories:' + cid + ':tid', start, stop, callback); db.getSortedSetRevRange('categories:' + cid + ':tid', start, stop, callback);
}; };
Categories.getTopicIndex = function(tid, callback) {
topics.getTopicField(tid, 'cid', function(err, cid) {
if(err) {
return callback(err);
}
db.sortedSetRevRank('categories:' + cid + ':tid', tid, callback);
});
};
Categories.getPageCount = function(cid, uid, callback) { Categories.getPageCount = function(cid, uid, callback) {
db.sortedSetCard('categories:' + cid + ':tid', function(err, topicCount) { db.sortedSetCard('categories:' + cid + ':tid', function(err, topicCount) {
if(err) { if(err) {

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

@ -120,7 +120,7 @@ SocketAdmin.categories.create = function(socket, data, callback) {
SocketAdmin.categories.update = function(socket, data) { SocketAdmin.categories.update = function(socket, data) {
if(!data) { if(!data) {
return callback(new Error('invalid data')); throw new Error('invalid data');
} }
admin.categories.update(data, socket); admin.categories.update(data, socket);
@ -380,4 +380,4 @@ SocketAdmin.groups.update = function(socket, data, callback) {
}); });
}; };
module.exports = SocketAdmin; module.exports = SocketAdmin;

@ -243,11 +243,11 @@ SocketPosts.getFavouritedUsers = function(socket, pid, callback) {
SocketPosts.getPidPage = function(socket, pid, callback) { SocketPosts.getPidPage = function(socket, pid, callback) {
posts.getPidPage(pid, socket.uid, callback); posts.getPidPage(pid, socket.uid, callback);
} };
SocketPosts.getPidIndex = function(socket, pid, callback) { SocketPosts.getPidIndex = function(socket, pid, callback) {
posts.getPidIndex(pid, callback); posts.getPidIndex(pid, callback);
} };
SocketPosts.flag = function(socket, pid, callback) { SocketPosts.flag = function(socket, pid, callback) {
if (!socket.uid) { if (!socket.uid) {

@ -1,4 +1,5 @@
var topics = require('../topics'), var topics = require('../topics'),
categories = require('../categories'),
threadTools = require('../threadTools'), threadTools = require('../threadTools'),
index = require('./index'), index = require('./index'),
user = require('../user'), user = require('../user'),
@ -291,9 +292,16 @@ SocketTopics.loadMoreFromSet = function(socket, data, callback) {
topics.getTopicsFromSet(socket.uid, data.set, start, end, callback); topics.getTopicsFromSet(socket.uid, data.set, start, end, callback);
}; };
SocketTopics.getPageCount = function(socket, tid, callback) { SocketTopics.getPageCount = function(socket, tid, callback) {
topics.getPageCount(tid, socket.uid, callback); topics.getPageCount(tid, socket.uid, callback);
}; };
SocketTopics.getTidPage = function(socket, tid, callback) {
topics.getTidPage(tid, socket.uid, callback);
};
SocketTopics.getTidIndex = function(socket, tid, callback) {
categories.getTopicIndex(tid, callback);
};
module.exports = SocketTopics; module.exports = SocketTopics;

@ -426,6 +426,26 @@ var async = require('async'),
}); });
}; };
Topics.getTidPage = function(tid, uid, callback) {
if(!tid) {
return callback(new Error('invalid-tid'));
}
async.parallel({
index: function(next) {
categories.getTopicIndex(tid, next);
},
settings: function(next) {
user.getSettings(uid, next);
}
}, function(err, results) {
if(err) {
return callback(err);
}
callback(null, Math.ceil((results.index + 1) / results.settings.topicsPerPage));
});
};
Topics.getCategoryData = function(tid, callback) { Topics.getCategoryData = function(tid, callback) {
Topics.getTopicField(tid, 'cid', function(err, cid) { Topics.getTopicField(tid, 'cid', function(err, cid) {
if(err) { if(err) {

Loading…
Cancel
Save