Merge remote-tracking branch 'origin/master' into user-icons

v1.18.x
Julian Lam 10 years ago
commit 97dd5500a1

4
.gitignore vendored

@ -41,3 +41,7 @@ pidfile
## File-based project format: ## File-based project format:
*.ipr *.ipr
*.iws *.iws
## Transifex
tx.exe
.transifexrc

@ -24,7 +24,7 @@
// "single" : require single quotes // "single" : require single quotes
// "double" : require double quotes // "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : false, // true: Require all defined variables be used TODO: Set this to true, update codebase. "unused" : true, // true: Require all defined variables be used
"strict" : true, // true: Requires all functions run in ES5 Strict Mode "strict" : true, // true: Requires all functions run in ES5 Strict Mode
"trailing" : false, // true: Prohibit trailing whitespaces "trailing" : false, // true: Prohibit trailing whitespaces
"maxparams" : false, // {int} Max number of formal params allowed per function "maxparams" : false, // {int} Max number of formal params allowed per function

@ -4,7 +4,7 @@ First of all, thank you! Please consider this [style guide](https://docs.nodebb.
## Contributor License Agreement ## Contributor License Agreement
Thank you for considering contributing to NodeBB. **Before we can accept any pull requests, please take a moment to read and sign our [license agreement](https://www.clahub.com/agreements/NodeBB/NodeBB)**. In summary, signing this document means that 1) you own the code that you are contributing and 2) you give permission to NodeBB Inc. to license the code to others. This agreement applies to any repository under the NodeBB organization. Thank you for considering contributing to NodeBB. **Before you are able to submit a pull request, please take a moment to read our [contributor license agreement](https://gist.github.com/psychobunny/65946d7aa8854b12fab9)** and agree to it on the pull request page on GitHub. In summary, signing this document means that 1) you own the code that you are contributing and 2) you give permission to NodeBB Inc. to license the code to others. This agreement applies to any repository under the NodeBB organization.
If you are writing contributions as part of employment from another company / individual, then your employer will need to sign a separate agreement. Please [contact us](mailto:accounts@nodebb.org) so that we can send this additional agreement to your employer. If you are writing contributions as part of employment from another company / individual, then your employer will need to sign a separate agreement. Please [contact us](mailto:accounts@nodebb.org) so that we can send this additional agreement to your employer.

@ -23,14 +23,14 @@
var nconf = require('nconf'); var nconf = require('nconf');
nconf.argv().env('__'); nconf.argv().env('__');
var fs = require('fs'), var url = require('url'),
url = require('url'),
async = require('async'), async = require('async'),
semver = require('semver'), semver = require('semver'),
winston = require('winston'), winston = require('winston'),
colors = require('colors'), colors = require('colors'),
path = require('path'), path = require('path'),
pkg = require('./package.json'), pkg = require('./package.json'),
file = require('./src/file'),
utils = require('./public/src/utils.js'); utils = require('./public/src/utils.js');
global.env = process.env.NODE_ENV || 'production'; global.env = process.env.NODE_ENV || 'production';
@ -53,7 +53,7 @@ if (nconf.get('config')) {
configFile = path.resolve(__dirname, nconf.get('config')); configFile = path.resolve(__dirname, nconf.get('config'));
} }
var configExists = fs.existsSync(configFile); var configExists = file.existsSync(configFile);
loadConfig(); loadConfig();

@ -8,7 +8,7 @@ var nconf = require('nconf'),
async = require('async'), async = require('async'),
logrotate = require('logrotate-stream'), logrotate = require('logrotate-stream'),
file = require('./src/file'),
pkg = require('./package.json'); pkg = require('./package.json');
nconf.argv().env().file({ nconf.argv().env().file({
@ -243,7 +243,7 @@ Loader.notifyWorkers = function(msg, worker_pid) {
fs.open(path.join(__dirname, 'config.json'), 'r', function(err) { fs.open(path.join(__dirname, 'config.json'), 'r', function(err) {
if (!err) { if (!err) {
if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) { if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) {
if (fs.existsSync(pidFilePath)) { if (file.existsSync(pidFilePath)) {
try { try {
var pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' }); var pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' });
process.kill(pid, 0); process.kill(pid, 0);

@ -4,6 +4,7 @@ var uglifyjs = require('uglify-js'),
less = require('less'), less = require('less'),
async = require('async'), async = require('async'),
fs = require('fs'), fs = require('fs'),
file = require('./src/file'),
crypto = require('crypto'), crypto = require('crypto'),
utils = require('./public/src/utils'), utils = require('./public/src/utils'),
@ -14,16 +15,16 @@ var uglifyjs = require('uglify-js'),
/* Javascript */ /* Javascript */
Minifier.js.minify = function (scripts, minify, callback) { Minifier.js.minify = function (scripts, minify, callback) {
scripts = scripts.filter(function(file) { scripts = scripts.filter(function(file) {
return fs.existsSync(file) && file.endsWith('.js'); return file && file.endsWith('.js');
}); });
async.filter(scripts, file.exists, function(scripts) {
if (minify) { if (minify) {
minifyScripts(scripts, function() { minifyScripts(scripts, callback);
callback.apply(this, arguments);
});
} else { } else {
concatenateScripts(scripts, callback); concatenateScripts(scripts, callback);
} }
});
}; };
process.on('message', function(payload) { process.on('message', function(payload) {

@ -48,10 +48,10 @@
"nodebb-plugin-soundpack-default": "0.1.4", "nodebb-plugin-soundpack-default": "0.1.4",
"nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-plugin-spam-be-gone": "0.4.2",
"nodebb-rewards-essentials": "0.0.5", "nodebb-rewards-essentials": "0.0.5",
"nodebb-theme-lavender": "2.0.4", "nodebb-theme-lavender": "2.0.5",
"nodebb-theme-persona": "3.0.23", "nodebb-theme-persona": "3.0.29",
"nodebb-theme-vanilla": "4.0.15", "nodebb-theme-vanilla": "4.0.17",
"nodebb-widget-essentials": "2.0.1", "nodebb-widget-essentials": "2.0.2",
"npm": "^2.1.4", "npm": "^2.1.4",
"passport": "^0.3.0", "passport": "^0.3.0",
"passport-local": "1.0.0", "passport-local": "1.0.0",

@ -29,7 +29,7 @@
"flag": "Flag", "flag": "Flag",
"locked": "Locked", "locked": "Locked",
"bookmark_instructions" : "Click here to return to your last position or close to discard.", "bookmark_instructions" : "Click here to return to the last unread post in this thread.",
"flag_title": "Flag this post for moderation", "flag_title": "Flag this post for moderation",
"flag_confirm": "Are you sure you want to flag this post?", "flag_confirm": "Are you sure you want to flag this post?",

@ -54,6 +54,7 @@
"confirm_password": "Confirm Password", "confirm_password": "Confirm Password",
"password": "Password", "password": "Password",
"username_taken_workaround": "The username you requested was already taken, so we have altered it slightly. You are now known as <strong>%1</strong>", "username_taken_workaround": "The username you requested was already taken, so we have altered it slightly. You are now known as <strong>%1</strong>",
"password_same_as_username": "Your password is the same as your username, please select another password.",
"upload_picture": "Upload picture", "upload_picture": "Upload picture",
"upload_a_picture": "Upload a picture", "upload_a_picture": "Upload a picture",

@ -25,7 +25,7 @@
"tools": "Tools", "tools": "Tools",
"flag": "Flag", "flag": "Flag",
"locked": "Locked", "locked": "Locked",
"bookmark_instructions": "Click here to return to your last position or close to discard.", "bookmark_instructions" : "Click here to return to the last unread post in this thread.",
"flag_title": "Flag this post for moderation", "flag_title": "Flag this post for moderation",
"flag_confirm": "Are you sure you want to flag this post?", "flag_confirm": "Are you sure you want to flag this post?",
"flag_success": "This post has been flagged for moderation.", "flag_success": "This post has been flagged for moderation.",

@ -82,4 +82,11 @@ div.categories {
.description { .description {
margin: 0; margin: 0;
} }
.children-placeholder{
border: 1px dashed #ddd;
min-height: 20px;
height: 20px;
}
} }

@ -94,19 +94,18 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri
} }
}; };
Categories.toggle = function(cid, state) { Categories.toggle = function(cid, disabled) {
var payload = {}; var payload = {};
payload[cid] = { payload[cid] = {
disabled: !state | 0 disabled: disabled ? 1 : 0
}; };
socket.emit('admin.categories.update', payload, function(err, result) { socket.emit('admin.categories.update', payload, function(err, result) {
if (err) { if (err) {
return app.alertError(err.message); return app.alertError(err.message);
} else {
ajaxify.refresh();
} }
ajaxify.refresh();
}); });
}; };
@ -116,6 +115,7 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri
function itemDragDidEnd(e) { function itemDragDidEnd(e) {
var isCategoryUpdate = (newCategoryId != -1); var isCategoryUpdate = (newCategoryId != -1);
//Update needed? //Update needed?
if((e.newIndex != undefined && e.oldIndex != e.newIndex) || isCategoryUpdate){ if((e.newIndex != undefined && e.oldIndex != e.newIndex) || isCategoryUpdate){
var parentCategory = isCategoryUpdate ? sortables[newCategoryId] : sortables[e.from.dataset.cid], var parentCategory = isCategoryUpdate ? sortables[newCategoryId] : sortables[e.from.dataset.cid],
@ -153,10 +153,8 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri
// Handle and children categories in this level have // Handle and children categories in this level have
for(var x=0,numCategories=categories.length;x<numCategories;x++) { for(var x=0,numCategories=categories.length;x<numCategories;x++) {
if (categories[x].hasOwnProperty('children') && categories[x].children.length > 0) {
renderList(categories[x].children, $('li[data-cid="' + categories[x].cid + '"]'), categories[x].cid); renderList(categories[x].children, $('li[data-cid="' + categories[x].cid + '"]'), categories[x].cid);
} }
}
// Make list sortable // Make list sortable
sortables[parentId] = Sortable.create($('ul[data-cid="' + parentId + '"]')[0], { sortables[parentId] = Sortable.create($('ul[data-cid="' + parentId + '"]')[0], {

@ -307,6 +307,9 @@ $(document).ready(function() {
} }
app.load(); app.load();
templates.cache['500'] = $('.tpl-500').html();
$('[data-template]').each(function() {
templates.cache[$(this).attr('data-template')] = $(this).html();
});
}); });

@ -176,6 +176,8 @@ define('forum/register', ['csrf', 'translator'], function(csrf, translator) {
showError(password_notify, '[[user:change_password_error_length]]'); showError(password_notify, '[[user:change_password_error_length]]');
} else if (!utils.isPasswordValid(password)) { } else if (!utils.isPasswordValid(password)) {
showError(password_notify, '[[user:change_password_error]]'); showError(password_notify, '[[user:change_password_error]]');
} else if (password === $('#username').val()) {
showError(password_notify, '[[user:password_same_as_username]]');
} else { } else {
showSuccess(password_notify, successIcon); showSuccess(password_notify, successIcon);
} }

@ -57,11 +57,13 @@ define('forum/topic', [
addBlockQuoteHandler(); addBlockQuoteHandler();
addParentHandler();
handleBookmark(tid); handleBookmark(tid);
handleKeys(); handleKeys();
navigator.init('[component="post"]', ajaxify.data.postcount, Topic.toTop, Topic.toBottom, Topic.navigatorCallback, Topic.calculateIndex); navigator.init('[component="post/anchor"]', ajaxify.data.postcount, Topic.toTop, Topic.toBottom, Topic.navigatorCallback, Topic.calculateIndex);
$(window).on('scroll', updateTopicTitle); $(window).on('scroll', updateTopicTitle);
@ -191,6 +193,26 @@ define('forum/topic', [
}); });
} }
function addParentHandler() {
components.get('topic').on('click', '[component="post/parent"]', function() {
var toPid = $(this).attr('data-topid');
var toPost = $('[component="post"][data-pid="' + toPid + '"]');
if (toPost.length) {
return navigator.scrollToPost(toPost.attr('data-index'), true);
}
socket.emit('posts.getPidIndex', {pid: toPid, tid: ajaxify.data.tid, topicPostSort: config.topicPostSort}, function(err, index) {
if (err) {
return app.alertError(err.message);
}
if (utils.isNumber(index)) {
navigator.scrollToPost(index, true);
}
});
});
}
function enableInfiniteLoadingOrPagination() { function enableInfiniteLoadingOrPagination() {
if (!config.usePagination) { if (!config.usePagination) {
@ -219,43 +241,30 @@ define('forum/topic', [
return index; return index;
}; };
Topic.navigatorCallback = function(topPostIndex, bottomPostIndex, elementCount) { Topic.navigatorCallback = function(index, elementCount) {
var path = ajaxify.removeRelativePath(window.location.pathname.slice(1)); var path = ajaxify.removeRelativePath(window.location.pathname.slice(1));
if (!path.startsWith('topic')) { if (!path.startsWith('topic')) {
return 1; return 1;
} }
var postIndex = topPostIndex;
var index = bottomPostIndex;
if (config.topicPostSort !== 'oldest_to_newest') {
if (bottomPostIndex === 0) {
index = 1;
} else {
index = Math.max(elementCount - bottomPostIndex + 2, 1);
}
}
var bookmarkKey = 'topic:' + ajaxify.data.tid + ':bookmark'; var bookmarkKey = 'topic:' + ajaxify.data.tid + ':bookmark';
var currentBookmark = ajaxify.data.bookmark || localStorage.getItem(bookmarkKey); var currentBookmark = ajaxify.data.bookmark || localStorage.getItem(bookmarkKey);
if (!currentBookmark || parseInt(postIndex, 10) > parseInt(currentBookmark, 10)) { if (!currentBookmark || parseInt(index, 10) > parseInt(currentBookmark, 10)) {
if (app.user.uid) { if (app.user.uid) {
var payload = { socket.emit('topics.bookmark', {
'tid': ajaxify.data.tid, 'tid': ajaxify.data.tid,
'index': postIndex 'index': index
}; }, function(err) {
socket.emit('topics.bookmark', payload, function(err) { ajaxify.data.bookmark = index;
if (err) {
console.warn('Error saving bookmark:', err);
}
ajaxify.data.bookmark = postIndex;
}); });
} else { } else {
localStorage.setItem(bookmarkKey, postIndex); localStorage.setItem(bookmarkKey, index);
} }
} }
// removes the bookmark alert when we get to / past the bookmark // removes the bookmark alert when we get to / past the bookmark
if (!currentBookmark || parseInt(postIndex, 10) >= parseInt(currentBookmark, 10)) { if (!currentBookmark || parseInt(index, 10) >= parseInt(currentBookmark, 10)) {
app.removeAlert('bookmark'); app.removeAlert('bookmark');
} }
@ -264,14 +273,15 @@ define('forum/topic', [
var topicId = parts[1], var topicId = parts[1],
slug = parts[2]; slug = parts[2];
var newUrl = 'topic/' + topicId + '/' + (slug ? slug : ''); var newUrl = 'topic/' + topicId + '/' + (slug ? slug : '');
if (postIndex > 1) { if (index > 1) {
newUrl += '/' + postIndex; newUrl += '/' + index;
} }
if (newUrl !== currentUrl) { if (newUrl !== currentUrl) {
if (Topic.replaceURLTimeout) { if (Topic.replaceURLTimeout) {
clearTimeout(Topic.replaceURLTimeout); clearTimeout(Topic.replaceURLTimeout);
} }
Topic.replaceURLTimeout = setTimeout(function() { Topic.replaceURLTimeout = setTimeout(function() {
Topic.replaceURLTimeout = 0; Topic.replaceURLTimeout = 0;
if (history.replaceState) { if (history.replaceState) {
@ -284,7 +294,6 @@ define('forum/topic', [
}, 500); }, 500);
} }
} }
return index;
}; };

@ -13,15 +13,20 @@ define('forum/topic/posts', [
var Posts = {}; var Posts = {};
Posts.onNewPost = function(data) { Posts.onNewPost = function(data) {
var tid = ajaxify.data.tid; if (!data || !data.posts || !data.posts.length) {
if (data && data.posts && data.posts.length && parseInt(data.posts[0].tid, 10) !== parseInt(tid, 10)) {
return; return;
} }
if (!data || !data.posts || !data.posts.length) { if (parseInt(data.posts[0].tid, 10) !== parseInt(ajaxify.data.tid, 10)) {
return; return;
} }
data.posts.forEach(function(post) {
post.selfPost = !!app.user.uid && parseInt(post.uid, 10) === parseInt(app.user.uid, 10);
post.display_moderator_tools = post.selfPost || ajaxify.data.isAdminOrMod;
post.display_move_tools = ajaxify.data.isAdminOrMod;
});
updatePostCounts(data.posts); updatePostCounts(data.posts);
if (config.usePagination) { if (config.usePagination) {
@ -152,54 +157,14 @@ define('forum/topic/posts', [
components.get('topic').append(html); components.get('topic').append(html);
} }
infinitescroll.removeExtra(components.get('posts'), direction, 40); infinitescroll.removeExtra(components.get('post'), direction, 40);
html.hide().fadeIn('slow');
var pids = [];
for(var i=0; i<data.posts.length; ++i) {
pids.push(data.posts[i].pid);
}
$(window).trigger('action:posts.loaded', {posts: data.posts}); $(window).trigger('action:posts.loaded', {posts: data.posts});
onNewPostsLoaded(html, pids);
callback(html);
});
}
function onNewPostsLoaded(html, pids) {
if (app.user.uid) {
socket.emit('posts.getPrivileges', pids, function(err, privileges) {
if(err) {
return app.alertError(err.message);
}
for(var i=0; i<pids.length; ++i) {
toggleModTools(pids[i], privileges[i]);
}
});
} else {
for(var i=0; i<pids.length; ++i) {
toggleModTools(pids[i], {editable: false, move: false});
}
}
Posts.processPage(html); Posts.processPage(html);
}
function toggleModTools(pid, privileges) { callback(html);
var postEl = components.get('post', 'pid', pid), });
isSelfPost = parseInt(postEl.attr('data-uid'), 10) === parseInt(app.user.uid, 10);
if (!privileges.editable) {
postEl.find('[component="post/edit"], [component="post/delete"], [component="post/purge"]').remove();
}
if (!privileges.move) {
postEl.find('[component="post/move"]').remove();
}
postEl.find('[component="user/chat"], [component="post/flag"]').toggleClass('hidden', isSelfPost || !app.user.uid);
} }
Posts.loadMorePosts = function(direction) { Posts.loadMorePosts = function(direction) {

@ -90,30 +90,22 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
navigator.update = function() { navigator.update = function() {
toggle(!!count); toggle(!!count);
var topIndex = 0; var middleOfViewport = $(window).scrollTop() + $(window).height() / 2;
var bottomIndex = 0;
$(navigator.selector).each(function() {
var el = $(this);
if (elementInView(el)) { index = parseInt($(navigator.selector).first().attr('data-index'), 10);
if (!topIndex) {
topIndex = parseInt(el.attr('data-index'), 10) + 1; $(navigator.selector).each(function() {
} else { index++;
bottomIndex = parseInt(el.attr('data-index'), 10) + 1; if ($(this).offset().top > middleOfViewport) {
}
} else if (topIndex && bottomIndex) {
return false; return false;
} }
}); });
if (topIndex && !bottomIndex) { if (typeof navigator.callback === 'function') {
bottomIndex = topIndex; navigator.callback(index, count);
} }
if (typeof navigator.callback === 'function' && topIndex && bottomIndex) {
index = navigator.callback(topIndex, bottomIndex, count);
navigator.updateTextAndProgressBar(); navigator.updateTextAndProgressBar();
}
}; };
navigator.updateTextAndProgressBar = function() { navigator.updateTextAndProgressBar = function() {
@ -155,27 +147,16 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
} }
}; };
function elementInView(el) { navigator.scrollToPost = function(postIndex, highlight, duration) {
var scrollTop = $(window).scrollTop() + $('#header-menu').height();
var scrollBottom = scrollTop + $(window).height();
var elTop = el.offset().top;
var elBottom = elTop + Math.floor(el.height());
return (elTop >= scrollTop && elBottom < scrollBottom) || (elTop < scrollTop && elBottom > scrollTop);
}
navigator.scrollToPost = function(postIndex, highlight, duration, offset) {
if (!utils.isNumber(postIndex) || !components.get('topic').length) { if (!utils.isNumber(postIndex) || !components.get('topic').length) {
return; return;
} }
offset = offset || 0;
duration = duration !== undefined ? duration : 400; duration = duration !== undefined ? duration : 400;
navigator.scrollActive = true; navigator.scrollActive = true;
if (components.get('post/anchor', postIndex).length) { if (components.get('post/anchor', postIndex).length) {
return navigator.scrollToPostIndex(postIndex, highlight, duration, offset); return navigator.scrollToPostIndex(postIndex, highlight, duration);
} }
if (config.usePagination) { if (config.usePagination) {
@ -183,10 +164,10 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
if (parseInt(page, 10) !== pagination.currentPage) { if (parseInt(page, 10) !== pagination.currentPage) {
pagination.loadPage(page, function() { pagination.loadPage(page, function() {
navigator.scrollToPostIndex(postIndex, highlight, duration, offset); navigator.scrollToPostIndex(postIndex, highlight, duration);
}); });
} else { } else {
navigator.scrollToPostIndex(postIndex, highlight, duration, offset); navigator.scrollToPostIndex(postIndex, highlight, duration);
} }
} else { } else {
navigator.scrollActive = false; navigator.scrollActive = false;
@ -195,19 +176,20 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
} }
}; };
navigator.scrollToPostIndex = function(postIndex, highlight, duration, offset) { navigator.scrollToPostIndex = function(postIndex, highlight, duration) {
var scrollTo = components.get('post/anchor', postIndex); var scrollTo = components.get('post/anchor', postIndex);
if (!scrollTo.length) { if (!scrollTo.length) {
navigator.scrollActive = false; navigator.scrollActive = false;
return; return;
} }
offset = offset || 0;
duration = duration !== undefined ? duration : 400; duration = duration !== undefined ? duration : 400;
navigator.scrollActive = true; navigator.scrollActive = true;
var done = false; var done = false;
function animateScroll() { function animateScroll() {
var scrollTop = (scrollTo.offset().top - ($(window).height() / 2) - offset) + 'px'; var scrollTop = (scrollTo.offset().top - ($(window).height() / 2)) + 'px';
$('html, body').animate({ $('html, body').animate({
scrollTop: scrollTop scrollTop: scrollTop

@ -264,11 +264,12 @@
var fs = require('fs'), var fs = require('fs'),
path = require('path'), path = require('path'),
winston = require('winston'), winston = require('winston'),
file = require('../../../src/file'),
meta = require('../../../src/meta'); meta = require('../../../src/meta');
language = language || meta.config.defaultLang || 'en_GB'; language = language || meta.config.defaultLang || 'en_GB';
if (!fs.existsSync(path.join(__dirname, '../../language', language))) { if (!file.existsSync(path.join(__dirname, '../../language', language))) {
winston.warn('[translator] Language \'' + meta.config.defaultLang + '\' not found. Defaulting to \'en_GB\''); winston.warn('[translator] Language \'' + meta.config.defaultLang + '\' not found. Defaulting to \'en_GB\'');
language = 'en_GB'; language = 'en_GB';
} }

@ -10,13 +10,21 @@ module.exports = function(Categories) {
Categories.update = function(modified, callback) { Categories.update = function(modified, callback) {
function updateCategory(cid, next) { var cids = Object.keys(modified);
async.each(cids, function(cid, next) {
updateCategory(cid, modified[cid], next);
}, function(err) {
callback(err, cids);
});
};
function updateCategory(cid, modifiedFields, callback) {
Categories.exists(cid, function(err, exists) { Categories.exists(cid, function(err, exists) {
if (err || !exists) { if (err || !exists) {
return next(err); return callback(err);
} }
var modifiedFields = modified[cid];
if (modifiedFields.hasOwnProperty('name')) { if (modifiedFields.hasOwnProperty('name')) {
modifiedFields.slug = cid + '/' + utils.slugify(modifiedFields.name); modifiedFields.slug = cid + '/' + utils.slugify(modifiedFields.name);
@ -24,31 +32,30 @@ module.exports = function(Categories) {
plugins.fireHook('filter:category.update', {category: modifiedFields}, function(err, categoryData) { plugins.fireHook('filter:category.update', {category: modifiedFields}, function(err, categoryData) {
if (err) { if (err) {
return next(err); return callback(err);
} }
var category = categoryData.category; var category = categoryData.category;
var fields = Object.keys(category); var fields = Object.keys(category);
async.each(fields, function(key, next) { // move parent to front, so its updated first
var parentCidIndex = fields.indexOf('parentCid');
if (parentCidIndex !== -1 && fields.length > 1) {
fields.splice(0, 0, fields.splice(parentCidIndex, 1)[0]);
}
async.eachSeries(fields, function(key, next) {
updateCategoryField(cid, key, category[key], next); updateCategoryField(cid, key, category[key], next);
}, function(err) { }, function(err) {
if (err) { if (err) {
return next(err); return callback(err);
} }
plugins.fireHook('action:category.update', {cid: cid, modified: category}); plugins.fireHook('action:category.update', {cid: cid, modified: category});
next(); callback();
}); });
}); });
}); });
} }
var cids = Object.keys(modified);
async.each(cids, updateCategory, function(err) {
callback(err, cids);
});
};
function updateCategoryField(cid, key, value, callback) { function updateCategoryField(cid, key, value, callback) {
if (key === 'parentCid') { if (key === 'parentCid') {
return updateParent(cid, value, callback); return updateParent(cid, value, callback);

@ -1,24 +1,24 @@
'use strict'; 'use strict';
var path = require('path'); var path = require('path');
var fs = require('fs'); var file = require('../../file');
var themesController = {}; var themesController = {};
themesController.get = function(req, res, next) { themesController.get = function(req, res, next) {
var themeDir = path.join(__dirname, '../../../node_modules/' + req.params.theme); var themeDir = path.join(__dirname, '../../../node_modules/' + req.params.theme);
fs.exists(themeDir, function(exists) { file.exists(themeDir, function(exists) {
if (exists) { if (!exists) {
return next();
}
var themeConfig = require(path.join(themeDir, 'theme.json')), var themeConfig = require(path.join(themeDir, 'theme.json')),
screenshotPath = path.join(themeDir, themeConfig.screenshot); screenshotPath = path.join(themeDir, themeConfig.screenshot);
if (themeConfig.screenshot && fs.existsSync(screenshotPath)) { if (themeConfig.screenshot && file.existsSync(screenshotPath)) {
res.sendFile(screenshotPath); res.sendFile(screenshotPath);
} else { } else {
res.sendFile(path.join(__dirname, '../../../public/images/themes/default.png')); res.sendFile(path.join(__dirname, '../../../public/images/themes/default.png'));
} }
} else {
return next();
}
}); });
}; };

@ -253,9 +253,10 @@ categoriesController.get = function(req, res, callback) {
data.pageCount = pageCount; data.pageCount = pageCount;
data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
data.rssFeedUrl = nconf.get('relative_path') + '/category/' + data.cid + '.rss'; data.rssFeedUrl = nconf.get('relative_path') + '/category/' + data.cid + '.rss';
data.pagination = pagination.create(data.currentPage, data.pageCount);
data.title = data.name; data.title = data.name;
data.pagination = pagination.create(data.currentPage, data.pageCount);
data.pagination.rel.forEach(function(rel) { data.pagination.rel.forEach(function(rel) {
rel.href = nconf.get('url') + '/category/' + data.slug + rel.href;
res.locals.linkTags.push(rel); res.locals.linkTags.push(rel);
}); });

@ -263,6 +263,7 @@ topicsController.get = function(req, res, callback) {
data.rssFeedUrl = nconf.get('relative_path') + '/topic/' + data.tid + '.rss'; data.rssFeedUrl = nconf.get('relative_path') + '/topic/' + data.tid + '.rss';
data.pagination = pagination.create(data.currentPage, data.pageCount); data.pagination = pagination.create(data.currentPage, data.pageCount);
data.pagination.rel.forEach(function(rel) { data.pagination.rel.forEach(function(rel) {
rel.href = nconf.get('url') + '/topic/' + data.slug + rel.href;
res.locals.linkTags.push(rel); res.locals.linkTags.push(rel);
}); });

@ -68,7 +68,7 @@ module.exports = function(db, module) {
return callback(err, null); return callback(err, null);
} }
callback(null, item[field] || null); callback(null, item.hasOwnProperty(field) ? item[field] : null);
}); });
}; };

@ -8,7 +8,6 @@ var fs = require('fs'),
Magic = mmmagic.Magic, Magic = mmmagic.Magic,
mime = require('mime'), mime = require('mime'),
meta = require('./meta'),
utils = require('../public/src/utils'); utils = require('../public/src/utils');
var file = {}; var file = {};
@ -63,6 +62,7 @@ file.isFileTypeAllowed = function(path, allowedExtensions, callback) {
}; };
file.allowedExtensions = function() { file.allowedExtensions = function() {
var meta = require('./meta');
var allowedExtensions = (meta.config.allowedFileExtensions || '').trim(); var allowedExtensions = (meta.config.allowedFileExtensions || '').trim();
if (!allowedExtensions) { if (!allowedExtensions) {
return []; return [];
@ -80,4 +80,21 @@ file.allowedExtensions = function() {
return allowedExtensions; return allowedExtensions;
}; };
file.exists = function(path, callback) {
fs.stat(path, function(err, stat) {
callback(!err && stat);
});
};
file.existsSync = function(path) {
var exists = false;
try {
exists = fs.statSync(path);
} catch(err) {
exists = false;
}
return !!exists;
};
module.exports = file; module.exports = file;

@ -10,6 +10,7 @@ var fs = require('fs'),
winston = require('winston'), winston = require('winston'),
util = require('util'), util = require('util'),
socketio = require('socket.io'), socketio = require('socket.io'),
file = require('./file'),
meta = require('./meta'), meta = require('./meta'),
morgan = require('morgan'); morgan = require('morgan');
@ -76,7 +77,7 @@ var opts = {
/* Open the streams to log to: either a path or stdout */ /* Open the streams to log to: either a path or stdout */
var stream; var stream;
if(value) { if(value) {
if(fs.existsSync(value)) { if(file.existsSync(value)) {
var stats = fs.statSync(value); var stats = fs.statSync(value);
if(stats) { if(stats) {
if(stats.isDirectory()) { if(stats.isDirectory()) {

@ -11,6 +11,7 @@ var winston = require('winston'),
plugins = require('../plugins'), plugins = require('../plugins'),
emitter = require('../emitter'), emitter = require('../emitter'),
db = require('../database'), db = require('../database'),
file = require('../file'),
utils = require('../../public/src/utils'); utils = require('../../public/src/utils');
module.exports = function(Meta) { module.exports = function(Meta) {
@ -149,9 +150,17 @@ module.exports = function(Meta) {
Meta.css.getFromFile = function(callback) { Meta.css.getFromFile = function(callback) {
var cachePath = path.join(__dirname, '../../public/stylesheet.css'), var cachePath = path.join(__dirname, '../../public/stylesheet.css'),
acpCachePath = path.join(__dirname, '../../public/admin.css'); acpCachePath = path.join(__dirname, '../../public/admin.css');
fs.exists(cachePath, function(exists) { file.exists(cachePath, function(exists) {
if (exists) { if (!exists) {
if (nconf.get('isPrimary') === 'true') { winston.warn('[meta/css] No stylesheets found on disk, re-minifying');
Meta.css.minify(callback);
return;
}
if (nconf.get('isPrimary') !== 'true') {
return callback();
}
winston.verbose('[meta/css] Reading stylesheets from file'); winston.verbose('[meta/css] Reading stylesheets from file');
async.map([cachePath, acpCachePath], fs.readFile, function(err, files) { async.map([cachePath, acpCachePath], fs.readFile, function(err, files) {
Meta.css.cache = files[0]; Meta.css.cache = files[0];
@ -160,13 +169,6 @@ module.exports = function(Meta) {
emitter.emit('meta:css.compiled'); emitter.emit('meta:css.compiled');
callback(); callback();
}); });
} else {
callback();
}
} else {
winston.warn('[meta/css] No stylesheets found on disk, re-minifying');
Meta.css.minify.apply(Meta.css, arguments);
}
}); });
}; };
@ -197,10 +199,10 @@ module.exports = function(Meta) {
} }
function filterMissingFiles(files) { function filterMissingFiles(files) {
return files.filter(function(file) { return files.filter(function(filePath) {
var exists = fs.existsSync(path.join(__dirname, '../../node_modules', file)); var exists = file.existsSync(path.join(__dirname, '../../node_modules', filePath));
if (!exists) { if (!exists) {
winston.warn('[meta/css] File not found! ' + file); winston.warn('[meta/css] File not found! ' + filePath);
} }
return exists; return exists;
}); });

@ -8,7 +8,7 @@ var winston = require('winston'),
os = require('os'), os = require('os'),
nconf = require('nconf'), nconf = require('nconf'),
fs = require('fs'), fs = require('fs'),
file = require('../file'),
plugins = require('../plugins'), plugins = require('../plugins'),
emitter = require('../emitter'), emitter = require('../emitter'),
utils = require('../../public/src/utils'); utils = require('../../public/src/utils');
@ -208,10 +208,18 @@ module.exports = function(Meta) {
var scriptPath = path.join(__dirname, '../../public/nodebb.min.js'), var scriptPath = path.join(__dirname, '../../public/nodebb.min.js'),
mapPath = path.join(__dirname, '../../public/nodebb.min.js.map'), mapPath = path.join(__dirname, '../../public/nodebb.min.js.map'),
paths = [scriptPath]; paths = [scriptPath];
fs.exists(scriptPath, function(exists) { file.exists(scriptPath, function(exists) {
if (exists) { if (!exists) {
if (nconf.get('isPrimary') === 'true') { winston.warn('[meta/js] No script file found on disk, re-minifying');
fs.exists(mapPath, function(exists) { Meta.js.minify(minify, callback);
return;
}
if (nconf.get('isPrimary') !== 'true') {
return callback();
}
file.exists(mapPath, function(exists) {
if (exists) { if (exists) {
paths.push(mapPath); paths.push(mapPath);
} }
@ -225,13 +233,6 @@ module.exports = function(Meta) {
callback(); callback();
}); });
}); });
} else {
callback();
}
} else {
winston.warn('[meta/js] No script file found on disk, re-minifying');
Meta.js.minify.apply(Meta.js, arguments);
}
}); });
}; };

@ -6,6 +6,8 @@ var nconf = require('nconf'),
fs = require('fs'), fs = require('fs'),
path = require('path'), path = require('path'),
async = require('async'), async = require('async'),
file = require('../file'),
db = require('../database'); db = require('../database');
module.exports = function(Meta) { module.exports = function(Meta) {
@ -34,11 +36,11 @@ module.exports = function(Meta) {
async.map(themes, function (theme, next) { async.map(themes, function (theme, next) {
var config = path.join(themePath, theme, 'theme.json'); var config = path.join(themePath, theme, 'theme.json');
if (fs.existsSync(config)) {
fs.readFile(config, function (err, file) { fs.readFile(config, function (err, file) {
if (err) { if (err) {
return next(); return next();
} else { }
var configObj = JSON.parse(file.toString()); var configObj = JSON.parse(file.toString());
// Minor adjustments for API output // Minor adjustments for API output
@ -49,12 +51,9 @@ module.exports = function(Meta) {
configObj.screenshot_url = nconf.get('relative_path') + '/images/themes/default.png'; configObj.screenshot_url = nconf.get('relative_path') + '/images/themes/default.png';
} }
next(err, configObj); next(null, configObj);
}
}); });
} else {
next();
}
}, function (err, themes) { }, function (err, themes) {
themes = themes.filter(function (theme) { themes = themes.filter(function (theme) {
return (theme !== undefined); return (theme !== undefined);
@ -145,7 +144,7 @@ module.exports = function(Meta) {
if (themeObj.templates) { if (themeObj.templates) {
themePath = path.join(nconf.get('themes_path'), themeObj.id, themeObj.templates); themePath = path.join(nconf.get('themes_path'), themeObj.id, themeObj.templates);
} else if (fs.existsSync(fallback)) { } else if (file.existsSync(fallback)) {
themePath = fallback; themePath = fallback;
} }

@ -2,6 +2,7 @@
var meta = require('../meta'), var meta = require('../meta'),
db = require('../database'), db = require('../database'),
file = require('../file'),
auth = require('../routes/authentication'), auth = require('../routes/authentication'),
path = require('path'), path = require('path'),
@ -21,7 +22,7 @@ var middleware = {};
function setupFavicon(app) { function setupFavicon(app) {
var faviconPath = path.join(__dirname, '../../', 'public', meta.config['brand:favicon'] ? meta.config['brand:favicon'] : 'favicon.ico'); var faviconPath = path.join(__dirname, '../../', 'public', meta.config['brand:favicon'] ? meta.config['brand:favicon'] : 'favicon.ico');
if (fs.existsSync(faviconPath)) { if (file.existsSync(faviconPath)) {
app.use(nconf.get('relative_path'), favicon(faviconPath)); app.use(nconf.get('relative_path'), favicon(faviconPath));
} }
} }

@ -14,6 +14,7 @@ var fs = require('fs'),
translator = require('../public/src/modules/translator'), translator = require('../public/src/modules/translator'),
utils = require('../public/src/utils'), utils = require('../public/src/utils'),
hotswap = require('./hotswap'), hotswap = require('./hotswap'),
file = require('./file'),
controllers = require('./controllers'), controllers = require('./controllers'),
app, middleware; app, middleware;
@ -103,7 +104,7 @@ var fs = require('fs'),
return path.join(__dirname, '../node_modules/', plugin); return path.join(__dirname, '../node_modules/', plugin);
}); });
async.filter(plugins, fs.exists, function(plugins){ async.filter(plugins, file.exists, function(plugins) {
async.eachSeries(plugins, Plugins.loadPlugin, next); async.eachSeries(plugins, Plugins.loadPlugin, next);
}); });
}, },

@ -6,6 +6,7 @@ var fs = require('fs'),
async = require('async'), async = require('async'),
winston = require('winston'), winston = require('winston'),
nconf = require('nconf'), nconf = require('nconf'),
file = require('../file'),
utils = require('../../public/src/utils'); utils = require('../../public/src/utils');
@ -107,7 +108,7 @@ module.exports = function(Plugins) {
var realPath = pluginData.staticDirs[mappedPath]; var realPath = pluginData.staticDirs[mappedPath];
var staticDir = path.join(pluginPath, realPath); var staticDir = path.join(pluginPath, realPath);
fs.exists(staticDir, function(exists) { file.exists(staticDir, function(exists) {
if (exists) { if (exists) {
Plugins.staticDirs[pluginData.id + '/' + mappedPath] = staticDir; Plugins.staticDirs[pluginData.id + '/' + mappedPath] = staticDir;
} else { } else {

@ -51,6 +51,7 @@ module.exports = function(privileges) {
editable: editable, editable: editable,
deletable: deletable, deletable: deletable,
view_deleted: isAdminOrMod || results.isOwner, view_deleted: isAdminOrMod || results.isOwner,
isAdminOrMod: isAdminOrMod,
disabled: disabled, disabled: disabled,
tid: tid, tid: tid,
uid: uid uid: uid

@ -22,24 +22,15 @@ module.exports = function(app, middleware, controllers) {
} else { } else {
return null; return null;
} }
}).filter(function(a) { return a; }); }).filter(Boolean);
if (matches) { if (!matches) {
async.map(matches, function(mappedPath, next) { return next();
var filePath = path.join(plugins.staticDirs[mappedPath], decodeURIComponent(relPath.slice(mappedPath.length)));
fs.exists(filePath, function(exists) {
if (exists) {
next(null, filePath);
} else {
next();
} }
matches = matches.map(function(mappedPath) {
return path.join(plugins.staticDirs[mappedPath], decodeURIComponent(relPath.slice(mappedPath.length)));
}); });
}, function(err, matches) {
if (err) {
return next(err);
}
matches = matches.filter(Boolean);
if (matches.length) { if (matches.length) {
res.sendFile(matches[0]); res.sendFile(matches[0]);
@ -47,8 +38,4 @@ module.exports = function(app, middleware, controllers) {
next(); next();
} }
}); });
} else {
next();
}
});
}; };

@ -145,21 +145,6 @@ SocketPosts.getRawPost = function(socket, pid, callback) {
], callback); ], callback);
}; };
SocketPosts.getPrivileges = function(socket, pids, callback) {
privileges.posts.get(pids, socket.uid, function(err, privileges) {
if (err) {
return callback(err);
}
if (!Array.isArray(privileges) || !privileges.length) {
return callback(new Error('[[error:invalid-data]]'));
}
callback(null, privileges);
});
};
SocketPosts.loadMoreFavourites = function(socket, data, callback) { SocketPosts.loadMoreFavourites = function(socket, data, callback) {
loadMorePosts('uid:' + data.uid + ':favourites', socket.uid, data, callback); loadMorePosts('uid:' + data.uid + ':favourites', socket.uid, data, callback);
}; };

@ -283,6 +283,9 @@ module.exports = function(Topics) {
topicInfo: function(next) { topicInfo: function(next) {
Topics.getTopicFields(tid, ['tid', 'title', 'slug', 'cid', 'postcount'], next); Topics.getTopicFields(tid, ['tid', 'title', 'slug', 'cid', 'postcount'], next);
}, },
parents: function(next) {
Topics.addParentPosts([postData], next);
},
content: function(next) { content: function(next) {
posts.parsePost(postData, next); posts.parsePost(postData, next);
} }

@ -112,6 +112,9 @@ module.exports = function(Topics) {
}, },
privileges: function(next) { privileges: function(next) {
privileges.posts.get(pids, uid, next); privileges.posts.get(pids, uid, next);
},
parents: function(next) {
Topics.addParentPosts(postData, next);
} }
}, function(err, results) { }, function(err, results) {
if (err) { if (err) {
@ -129,7 +132,7 @@ module.exports = function(Topics) {
postObj.votes = postObj.votes || 0; postObj.votes = postObj.votes || 0;
postObj.display_moderator_tools = results.privileges[i].editable; postObj.display_moderator_tools = results.privileges[i].editable;
postObj.display_move_tools = results.privileges[i].move && postObj.index !== 0; postObj.display_move_tools = results.privileges[i].move && postObj.index !== 0;
postObj.selfPost = parseInt(uid, 10) === parseInt(postObj.uid, 10); postObj.selfPost = !!parseInt(uid, 10) && parseInt(uid, 10) === parseInt(postObj.uid, 10);
if (postObj.deleted && !results.privileges[i].view_deleted) { if (postObj.deleted && !results.privileges[i].view_deleted) {
postObj.content = '[[topic:post_is_deleted]]'; postObj.content = '[[topic:post_is_deleted]]';
@ -146,6 +149,44 @@ module.exports = function(Topics) {
}); });
}; };
Topics.addParentPosts = function(postData, callback) {
var parentPids = postData.map(function(postObj) {
return postObj && postObj.hasOwnProperty('toPid') ? parseInt(postObj.toPid, 10) : null;
}).filter(Boolean);
if (!parentPids.length) {
return callback();
}
var parentPosts;
async.waterfall([
async.apply(posts.getPostsFields, parentPids, ['uid']),
function(_parentPosts, next) {
parentPosts = _parentPosts;
var parentUids = parentPosts.map(function(postObj) { return parseInt(postObj.uid, 10); }).filter(function(uid, idx, users) {
return users.indexOf(uid) === idx;
});
user.getUsersFields(parentUids, ['username'], next);
},
function (userData, next) {
var usersMap = {};
userData.forEach(function(user) {
usersMap[user.uid] = user.username;
});
var parents = {};
parentPosts.forEach(function(post, i) {
parents[parentPids[i]] = {username: usersMap[post.uid]};
});
postData.forEach(function(post) {
post.parent = parents[post.toPid];
});
next();
}
], callback);
};
Topics.calculatePostIndices = function(posts, start, stop, postCount, reverse) { Topics.calculatePostIndices = function(posts, start, stop, postCount, reverse) {
posts.forEach(function(post, index) { posts.forEach(function(post, index) {
if (reverse) { if (reverse) {

@ -1,3 +1,4 @@
<script type="text/tpl" data-template="500">
<div class="alert alert-danger"> <div class="alert alert-danger">
<strong>[[global:500.title]]</strong> <strong>[[global:500.title]]</strong>
<p>[[global:500.message]]</p> <p>[[global:500.message]]</p>
@ -5,3 +6,4 @@
<!-- IF error --><p>{error}</p><!-- ENDIF error --> <!-- IF error --><p>{error}</p><!-- ENDIF error -->
</div> </div>
</script>

@ -26,4 +26,7 @@
</div> </div>
</li> </li>
<!-- END categories --> <!-- END categories -->
<li class="children-placeholder"></li>
</ul> </ul>

@ -186,9 +186,10 @@ module.exports.testSocket = function(socketPath, callback) {
return callback(new Error('invalid socket path : ' + socketPath)); return callback(new Error('invalid socket path : ' + socketPath));
} }
var net = require('net'); var net = require('net');
var file = require('./file');
async.series([ async.series([
function(next) { function(next) {
fs.exists(socketPath, function(exists) { file.exists(socketPath, function(exists) {
if (exists) { if (exists) {
next(); next();
} else { } else {

Loading…
Cancel
Save