diff --git a/.gitignore b/.gitignore
index 82ed213c66..783b71a23c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,4 +40,8 @@ pidfile
## File-based project format:
*.ipr
-*.iws
\ No newline at end of file
+*.iws
+
+## Transifex
+tx.exe
+.transifexrc
\ No newline at end of file
diff --git a/.jshintrc b/.jshintrc
index f5b67ad0bc..1981c254c5 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -24,7 +24,7 @@
// "single" : require single quotes
// "double" : require double quotes
"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
"trailing" : false, // true: Prohibit trailing whitespaces
"maxparams" : false, // {int} Max number of formal params allowed per function
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d6d64a04a9..5098d7d39a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,7 +4,7 @@ First of all, thank you! Please consider this [style guide](https://docs.nodebb.
## 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.
diff --git a/app.js b/app.js
index c6714a6425..4aa9371e18 100644
--- a/app.js
+++ b/app.js
@@ -23,14 +23,14 @@
var nconf = require('nconf');
nconf.argv().env('__');
-var fs = require('fs'),
- url = require('url'),
+var url = require('url'),
async = require('async'),
semver = require('semver'),
winston = require('winston'),
colors = require('colors'),
path = require('path'),
pkg = require('./package.json'),
+ file = require('./src/file'),
utils = require('./public/src/utils.js');
global.env = process.env.NODE_ENV || 'production';
@@ -53,7 +53,7 @@ if (nconf.get('config')) {
configFile = path.resolve(__dirname, nconf.get('config'));
}
-var configExists = fs.existsSync(configFile);
+var configExists = file.existsSync(configFile);
loadConfig();
diff --git a/loader.js b/loader.js
index 96c78da4ee..20e8335a9a 100644
--- a/loader.js
+++ b/loader.js
@@ -8,7 +8,7 @@ var nconf = require('nconf'),
async = require('async'),
logrotate = require('logrotate-stream'),
-
+ file = require('./src/file'),
pkg = require('./package.json');
nconf.argv().env().file({
@@ -243,7 +243,7 @@ Loader.notifyWorkers = function(msg, worker_pid) {
fs.open(path.join(__dirname, 'config.json'), 'r', function(err) {
if (!err) {
if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) {
- if (fs.existsSync(pidFilePath)) {
+ if (file.existsSync(pidFilePath)) {
try {
var pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' });
process.kill(pid, 0);
diff --git a/minifier.js b/minifier.js
index 621d61fa08..c1df3930c1 100644
--- a/minifier.js
+++ b/minifier.js
@@ -4,6 +4,7 @@ var uglifyjs = require('uglify-js'),
less = require('less'),
async = require('async'),
fs = require('fs'),
+ file = require('./src/file'),
crypto = require('crypto'),
utils = require('./public/src/utils'),
@@ -14,16 +15,16 @@ var uglifyjs = require('uglify-js'),
/* Javascript */
Minifier.js.minify = function (scripts, minify, callback) {
scripts = scripts.filter(function(file) {
- return fs.existsSync(file) && file.endsWith('.js');
+ return file && file.endsWith('.js');
});
- if (minify) {
- minifyScripts(scripts, function() {
- callback.apply(this, arguments);
- });
- } else {
- concatenateScripts(scripts, callback);
- }
+ async.filter(scripts, file.exists, function(scripts) {
+ if (minify) {
+ minifyScripts(scripts, callback);
+ } else {
+ concatenateScripts(scripts, callback);
+ }
+ });
};
process.on('message', function(payload) {
diff --git a/package.json b/package.json
index 0c18d6468e..a52e884f6f 100644
--- a/package.json
+++ b/package.json
@@ -48,10 +48,10 @@
"nodebb-plugin-soundpack-default": "0.1.4",
"nodebb-plugin-spam-be-gone": "0.4.2",
"nodebb-rewards-essentials": "0.0.5",
- "nodebb-theme-lavender": "2.0.4",
- "nodebb-theme-persona": "3.0.23",
- "nodebb-theme-vanilla": "4.0.15",
- "nodebb-widget-essentials": "2.0.1",
+ "nodebb-theme-lavender": "2.0.5",
+ "nodebb-theme-persona": "3.0.29",
+ "nodebb-theme-vanilla": "4.0.17",
+ "nodebb-widget-essentials": "2.0.2",
"npm": "^2.1.4",
"passport": "^0.3.0",
"passport-local": "1.0.0",
diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json
index eab097418b..a03ec807de 100644
--- a/public/language/en_GB/topic.json
+++ b/public/language/en_GB/topic.json
@@ -29,7 +29,7 @@
"flag": "Flag",
"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_confirm": "Are you sure you want to flag this post?",
diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json
index f3f651984d..3505d1814c 100644
--- a/public/language/en_GB/user.json
+++ b/public/language/en_GB/user.json
@@ -54,6 +54,7 @@
"confirm_password": "Confirm Password",
"password": "Password",
"username_taken_workaround": "The username you requested was already taken, so we have altered it slightly. You are now known as %1",
+ "password_same_as_username": "Your password is the same as your username, please select another password.",
"upload_picture": "Upload picture",
"upload_a_picture": "Upload a picture",
diff --git a/public/language/en_US/topic.json b/public/language/en_US/topic.json
index d9742f2eda..447376322b 100644
--- a/public/language/en_US/topic.json
+++ b/public/language/en_US/topic.json
@@ -25,7 +25,7 @@
"tools": "Tools",
"flag": "Flag",
"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_confirm": "Are you sure you want to flag this post?",
"flag_success": "This post has been flagged for moderation.",
diff --git a/public/less/admin/manage/categories.less b/public/less/admin/manage/categories.less
index 9b1e1fec3f..b9be869550 100644
--- a/public/less/admin/manage/categories.less
+++ b/public/less/admin/manage/categories.less
@@ -82,4 +82,11 @@ div.categories {
.description {
margin: 0;
}
+
+ .children-placeholder{
+ border: 1px dashed #ddd;
+ min-height: 20px;
+ height: 20px;
+ }
+
}
\ No newline at end of file
diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js
index c3e1dacab0..1d346ccd8a 100644
--- a/public/src/admin/manage/categories.js
+++ b/public/src/admin/manage/categories.js
@@ -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 = {};
payload[cid] = {
- disabled: !state | 0
+ disabled: disabled ? 1 : 0
};
socket.emit('admin.categories.update', payload, function(err, result) {
if (err) {
return app.alertError(err.message);
- } else {
- ajaxify.refresh();
}
+ ajaxify.refresh();
});
};
@@ -114,8 +113,9 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri
newCategoryId = e.to.dataset.cid;
}
- function itemDragDidEnd(e){
+ function itemDragDidEnd(e) {
var isCategoryUpdate = (newCategoryId != -1);
+
//Update needed?
if((e.newIndex != undefined && e.oldIndex != e.newIndex) || isCategoryUpdate){
var parentCategory = isCategoryUpdate ? sortables[newCategoryId] : sortables[e.from.dataset.cid],
@@ -153,9 +153,7 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri
// Handle and children categories in this level have
for(var x=0,numCategories=categories.length;x 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
diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
index 90532490d1..2f11307471 100644
--- a/public/src/ajaxify.js
+++ b/public/src/ajaxify.js
@@ -307,6 +307,9 @@ $(document).ready(function() {
}
app.load();
- templates.cache['500'] = $('.tpl-500').html();
+
+ $('[data-template]').each(function() {
+ templates.cache[$(this).attr('data-template')] = $(this).html();
+ });
});
\ No newline at end of file
diff --git a/public/src/client/register.js b/public/src/client/register.js
index cca8617972..25d76a73fe 100644
--- a/public/src/client/register.js
+++ b/public/src/client/register.js
@@ -176,6 +176,8 @@ define('forum/register', ['csrf', 'translator'], function(csrf, translator) {
showError(password_notify, '[[user:change_password_error_length]]');
} else if (!utils.isPasswordValid(password)) {
showError(password_notify, '[[user:change_password_error]]');
+ } else if (password === $('#username').val()) {
+ showError(password_notify, '[[user:password_same_as_username]]');
} else {
showSuccess(password_notify, successIcon);
}
diff --git a/public/src/client/topic.js b/public/src/client/topic.js
index d85b079632..b8a60ceef3 100644
--- a/public/src/client/topic.js
+++ b/public/src/client/topic.js
@@ -57,11 +57,13 @@ define('forum/topic', [
addBlockQuoteHandler();
+ addParentHandler();
+
handleBookmark(tid);
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);
@@ -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() {
if (!config.usePagination) {
@@ -219,43 +241,30 @@ define('forum/topic', [
return index;
};
- Topic.navigatorCallback = function(topPostIndex, bottomPostIndex, elementCount) {
+ Topic.navigatorCallback = function(index, elementCount) {
var path = ajaxify.removeRelativePath(window.location.pathname.slice(1));
if (!path.startsWith('topic')) {
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 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) {
- var payload = {
+ socket.emit('topics.bookmark', {
'tid': ajaxify.data.tid,
- 'index': postIndex
- };
- socket.emit('topics.bookmark', payload, function(err) {
- if (err) {
- console.warn('Error saving bookmark:', err);
- }
- ajaxify.data.bookmark = postIndex;
+ 'index': index
+ }, function(err) {
+ ajaxify.data.bookmark = index;
});
} else {
- localStorage.setItem(bookmarkKey, postIndex);
+ localStorage.setItem(bookmarkKey, index);
}
}
// 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');
}
@@ -264,14 +273,15 @@ define('forum/topic', [
var topicId = parts[1],
slug = parts[2];
var newUrl = 'topic/' + topicId + '/' + (slug ? slug : '');
- if (postIndex > 1) {
- newUrl += '/' + postIndex;
+ if (index > 1) {
+ newUrl += '/' + index;
}
if (newUrl !== currentUrl) {
if (Topic.replaceURLTimeout) {
clearTimeout(Topic.replaceURLTimeout);
}
+
Topic.replaceURLTimeout = setTimeout(function() {
Topic.replaceURLTimeout = 0;
if (history.replaceState) {
@@ -284,7 +294,6 @@ define('forum/topic', [
}, 500);
}
}
- return index;
};
diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js
index e13fc1a18c..f17e8cbab5 100644
--- a/public/src/client/topic/posts.js
+++ b/public/src/client/topic/posts.js
@@ -13,15 +13,20 @@ define('forum/topic/posts', [
var Posts = {};
Posts.onNewPost = function(data) {
- var tid = ajaxify.data.tid;
- if (data && data.posts && data.posts.length && parseInt(data.posts[0].tid, 10) !== parseInt(tid, 10)) {
+ if (!data || !data.posts || !data.posts.length) {
return;
}
- if (!data || !data.posts || !data.posts.length) {
+ if (parseInt(data.posts[0].tid, 10) !== parseInt(ajaxify.data.tid, 10)) {
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);
if (config.usePagination) {
@@ -152,56 +157,16 @@ define('forum/topic/posts', [
components.get('topic').append(html);
}
- infinitescroll.removeExtra(components.get('posts'), direction, 40);
+ infinitescroll.removeExtra(components.get('post'), direction, 40);
- html.hide().fadeIn('slow');
+ $(window).trigger('action:posts.loaded', {posts: data.posts});
- var pids = [];
- for(var i=0; i middleOfViewport) {
return false;
}
});
- if (topIndex && !bottomIndex) {
- bottomIndex = topIndex;
+ if (typeof navigator.callback === 'function') {
+ navigator.callback(index, count);
}
- if (typeof navigator.callback === 'function' && topIndex && bottomIndex) {
- index = navigator.callback(topIndex, bottomIndex, count);
- navigator.updateTextAndProgressBar();
- }
+ navigator.updateTextAndProgressBar();
};
navigator.updateTextAndProgressBar = function() {
@@ -155,27 +147,16 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
}
};
- function elementInView(el) {
- 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) {
+ navigator.scrollToPost = function(postIndex, highlight, duration) {
if (!utils.isNumber(postIndex) || !components.get('topic').length) {
return;
}
- offset = offset || 0;
duration = duration !== undefined ? duration : 400;
navigator.scrollActive = true;
if (components.get('post/anchor', postIndex).length) {
- return navigator.scrollToPostIndex(postIndex, highlight, duration, offset);
+ return navigator.scrollToPostIndex(postIndex, highlight, duration);
}
if (config.usePagination) {
@@ -183,10 +164,10 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
if (parseInt(page, 10) !== pagination.currentPage) {
pagination.loadPage(page, function() {
- navigator.scrollToPostIndex(postIndex, highlight, duration, offset);
+ navigator.scrollToPostIndex(postIndex, highlight, duration);
});
} else {
- navigator.scrollToPostIndex(postIndex, highlight, duration, offset);
+ navigator.scrollToPostIndex(postIndex, highlight, duration);
}
} else {
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);
if (!scrollTo.length) {
navigator.scrollActive = false;
return;
}
- offset = offset || 0;
+
duration = duration !== undefined ? duration : 400;
navigator.scrollActive = true;
var done = false;
+
function animateScroll() {
- var scrollTop = (scrollTo.offset().top - ($(window).height() / 2) - offset) + 'px';
+ var scrollTop = (scrollTo.offset().top - ($(window).height() / 2)) + 'px';
$('html, body').animate({
scrollTop: scrollTop
diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js
index e2b70220f3..35429af587 100644
--- a/public/src/modules/translator.js
+++ b/public/src/modules/translator.js
@@ -264,11 +264,12 @@
var fs = require('fs'),
path = require('path'),
winston = require('winston'),
+ file = require('../../../src/file'),
meta = require('../../../src/meta');
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\'');
language = 'en_GB';
}
diff --git a/src/categories/update.js b/src/categories/update.js
index 9ad8acf61e..4ebfda2f11 100644
--- a/src/categories/update.js
+++ b/src/categories/update.js
@@ -10,44 +10,51 @@ module.exports = function(Categories) {
Categories.update = function(modified, callback) {
- function updateCategory(cid, next) {
- Categories.exists(cid, function(err, exists) {
- if (err || !exists) {
- return next(err);
- }
+ var cids = Object.keys(modified);
+
+ async.each(cids, function(cid, next) {
+ updateCategory(cid, modified[cid], next);
+ }, function(err) {
+ callback(err, cids);
+ });
+ };
- var modifiedFields = modified[cid];
+ function updateCategory(cid, modifiedFields, callback) {
+ Categories.exists(cid, function(err, exists) {
+ if (err || !exists) {
+ return callback(err);
+ }
+
+
+ if (modifiedFields.hasOwnProperty('name')) {
+ modifiedFields.slug = cid + '/' + utils.slugify(modifiedFields.name);
+ }
- if (modifiedFields.hasOwnProperty('name')) {
- modifiedFields.slug = cid + '/' + utils.slugify(modifiedFields.name);
+ plugins.fireHook('filter:category.update', {category: modifiedFields}, function(err, categoryData) {
+ if (err) {
+ return callback(err);
}
- plugins.fireHook('filter:category.update', {category: modifiedFields}, function(err, categoryData) {
+ var category = categoryData.category;
+ var fields = Object.keys(category);
+ // 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);
+ }, function(err) {
if (err) {
- return next(err);
+ return callback(err);
}
-
- var category = categoryData.category;
- var fields = Object.keys(category);
- async.each(fields, function(key, next) {
- updateCategoryField(cid, key, category[key], next);
- }, function(err) {
- if (err) {
- return next(err);
- }
- plugins.fireHook('action:category.update', {cid: cid, modified: category});
- next();
- });
+ plugins.fireHook('action:category.update', {cid: cid, modified: category});
+ callback();
});
});
- }
-
- var cids = Object.keys(modified);
-
- async.each(cids, updateCategory, function(err) {
- callback(err, cids);
});
- };
+ }
function updateCategoryField(cid, key, value, callback) {
if (key === 'parentCid') {
diff --git a/src/controllers/admin/themes.js b/src/controllers/admin/themes.js
index 0f87ecc83c..e5ef8a9343 100644
--- a/src/controllers/admin/themes.js
+++ b/src/controllers/admin/themes.js
@@ -1,24 +1,24 @@
'use strict';
var path = require('path');
-var fs = require('fs');
+var file = require('../../file');
var themesController = {};
themesController.get = function(req, res, next) {
var themeDir = path.join(__dirname, '../../../node_modules/' + req.params.theme);
- fs.exists(themeDir, function(exists) {
- if (exists) {
- var themeConfig = require(path.join(themeDir, 'theme.json')),
- screenshotPath = path.join(themeDir, themeConfig.screenshot);
- if (themeConfig.screenshot && fs.existsSync(screenshotPath)) {
- res.sendFile(screenshotPath);
- } else {
- res.sendFile(path.join(__dirname, '../../../public/images/themes/default.png'));
- }
- } else {
+ file.exists(themeDir, function(exists) {
+ if (!exists) {
return next();
}
+
+ var themeConfig = require(path.join(themeDir, 'theme.json')),
+ screenshotPath = path.join(themeDir, themeConfig.screenshot);
+ if (themeConfig.screenshot && file.existsSync(screenshotPath)) {
+ res.sendFile(screenshotPath);
+ } else {
+ res.sendFile(path.join(__dirname, '../../../public/images/themes/default.png'));
+ }
});
};
diff --git a/src/controllers/categories.js b/src/controllers/categories.js
index ce1edc0532..220779c524 100644
--- a/src/controllers/categories.js
+++ b/src/controllers/categories.js
@@ -253,9 +253,10 @@ categoriesController.get = function(req, res, callback) {
data.pageCount = pageCount;
data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
data.rssFeedUrl = nconf.get('relative_path') + '/category/' + data.cid + '.rss';
- data.pagination = pagination.create(data.currentPage, data.pageCount);
data.title = data.name;
+ data.pagination = pagination.create(data.currentPage, data.pageCount);
data.pagination.rel.forEach(function(rel) {
+ rel.href = nconf.get('url') + '/category/' + data.slug + rel.href;
res.locals.linkTags.push(rel);
});
diff --git a/src/controllers/topics.js b/src/controllers/topics.js
index c4abfcbfbf..7632d26c5d 100644
--- a/src/controllers/topics.js
+++ b/src/controllers/topics.js
@@ -263,6 +263,7 @@ topicsController.get = function(req, res, callback) {
data.rssFeedUrl = nconf.get('relative_path') + '/topic/' + data.tid + '.rss';
data.pagination = pagination.create(data.currentPage, data.pageCount);
data.pagination.rel.forEach(function(rel) {
+ rel.href = nconf.get('url') + '/topic/' + data.slug + rel.href;
res.locals.linkTags.push(rel);
});
diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js
index 8a833699f9..fe80afaaf4 100644
--- a/src/database/mongo/hash.js
+++ b/src/database/mongo/hash.js
@@ -68,7 +68,7 @@ module.exports = function(db, module) {
return callback(err, null);
}
- callback(null, item[field] || null);
+ callback(null, item.hasOwnProperty(field) ? item[field] : null);
});
};
diff --git a/src/file.js b/src/file.js
index 94465b4ad7..2f503f0309 100644
--- a/src/file.js
+++ b/src/file.js
@@ -8,7 +8,6 @@ var fs = require('fs'),
Magic = mmmagic.Magic,
mime = require('mime'),
- meta = require('./meta'),
utils = require('../public/src/utils');
var file = {};
@@ -63,6 +62,7 @@ file.isFileTypeAllowed = function(path, allowedExtensions, callback) {
};
file.allowedExtensions = function() {
+ var meta = require('./meta');
var allowedExtensions = (meta.config.allowedFileExtensions || '').trim();
if (!allowedExtensions) {
return [];
@@ -80,4 +80,21 @@ file.allowedExtensions = function() {
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;
\ No newline at end of file
diff --git a/src/logger.js b/src/logger.js
index cd437924be..5b15e90ad6 100644
--- a/src/logger.js
+++ b/src/logger.js
@@ -10,6 +10,7 @@ var fs = require('fs'),
winston = require('winston'),
util = require('util'),
socketio = require('socket.io'),
+ file = require('./file'),
meta = require('./meta'),
morgan = require('morgan');
@@ -76,7 +77,7 @@ var opts = {
/* Open the streams to log to: either a path or stdout */
var stream;
if(value) {
- if(fs.existsSync(value)) {
+ if(file.existsSync(value)) {
var stats = fs.statSync(value);
if(stats) {
if(stats.isDirectory()) {
diff --git a/src/meta/css.js b/src/meta/css.js
index f3257e624f..46b54ccb18 100644
--- a/src/meta/css.js
+++ b/src/meta/css.js
@@ -11,6 +11,7 @@ var winston = require('winston'),
plugins = require('../plugins'),
emitter = require('../emitter'),
db = require('../database'),
+ file = require('../file'),
utils = require('../../public/src/utils');
module.exports = function(Meta) {
@@ -149,24 +150,25 @@ module.exports = function(Meta) {
Meta.css.getFromFile = function(callback) {
var cachePath = path.join(__dirname, '../../public/stylesheet.css'),
acpCachePath = path.join(__dirname, '../../public/admin.css');
- fs.exists(cachePath, function(exists) {
- if (exists) {
- if (nconf.get('isPrimary') === 'true') {
- winston.verbose('[meta/css] Reading stylesheets from file');
- async.map([cachePath, acpCachePath], fs.readFile, function(err, files) {
- Meta.css.cache = files[0];
- Meta.css.acpCache = files[1];
-
- emitter.emit('meta:css.compiled');
- callback();
- });
- } else {
- callback();
- }
- } else {
+ file.exists(cachePath, function(exists) {
+ if (!exists) {
winston.warn('[meta/css] No stylesheets found on disk, re-minifying');
- Meta.css.minify.apply(Meta.css, arguments);
+ Meta.css.minify(callback);
+ return;
}
+
+ if (nconf.get('isPrimary') !== 'true') {
+ return callback();
+ }
+
+ winston.verbose('[meta/css] Reading stylesheets from file');
+ async.map([cachePath, acpCachePath], fs.readFile, function(err, files) {
+ Meta.css.cache = files[0];
+ Meta.css.acpCache = files[1];
+
+ emitter.emit('meta:css.compiled');
+ callback();
+ });
});
};
@@ -197,10 +199,10 @@ module.exports = function(Meta) {
}
function filterMissingFiles(files) {
- return files.filter(function(file) {
- var exists = fs.existsSync(path.join(__dirname, '../../node_modules', file));
+ return files.filter(function(filePath) {
+ var exists = file.existsSync(path.join(__dirname, '../../node_modules', filePath));
if (!exists) {
- winston.warn('[meta/css] File not found! ' + file);
+ winston.warn('[meta/css] File not found! ' + filePath);
}
return exists;
});
diff --git a/src/meta/js.js b/src/meta/js.js
index 9bddbd58e9..dd8c364924 100644
--- a/src/meta/js.js
+++ b/src/meta/js.js
@@ -8,7 +8,7 @@ var winston = require('winston'),
os = require('os'),
nconf = require('nconf'),
fs = require('fs'),
-
+ file = require('../file'),
plugins = require('../plugins'),
emitter = require('../emitter'),
utils = require('../../public/src/utils');
@@ -208,30 +208,31 @@ module.exports = function(Meta) {
var scriptPath = path.join(__dirname, '../../public/nodebb.min.js'),
mapPath = path.join(__dirname, '../../public/nodebb.min.js.map'),
paths = [scriptPath];
- fs.exists(scriptPath, function(exists) {
- if (exists) {
- if (nconf.get('isPrimary') === 'true') {
- fs.exists(mapPath, function(exists) {
- if (exists) {
- paths.push(mapPath);
- }
+ file.exists(scriptPath, function(exists) {
+ if (!exists) {
+ winston.warn('[meta/js] No script file found on disk, re-minifying');
+ Meta.js.minify(minify, callback);
+ return;
+ }
- winston.verbose('[meta/js] Reading client-side scripts from file');
- async.map(paths, fs.readFile, function(err, files) {
- Meta.js.cache = files[0];
- Meta.js.map = files[1] || '';
+ if (nconf.get('isPrimary') !== 'true') {
+ return callback();
+ }
- emitter.emit('meta:js.compiled');
- callback();
- });
- });
- } else {
- callback();
+ file.exists(mapPath, function(exists) {
+ if (exists) {
+ paths.push(mapPath);
}
- } else {
- winston.warn('[meta/js] No script file found on disk, re-minifying');
- Meta.js.minify.apply(Meta.js, arguments);
- }
+
+ winston.verbose('[meta/js] Reading client-side scripts from file');
+ async.map(paths, fs.readFile, function(err, files) {
+ Meta.js.cache = files[0];
+ Meta.js.map = files[1] || '';
+
+ emitter.emit('meta:js.compiled');
+ callback();
+ });
+ });
});
};
diff --git a/src/meta/themes.js b/src/meta/themes.js
index e10194c9af..fedb1ff0d9 100644
--- a/src/meta/themes.js
+++ b/src/meta/themes.js
@@ -6,6 +6,8 @@ var nconf = require('nconf'),
fs = require('fs'),
path = require('path'),
async = require('async'),
+
+ file = require('../file'),
db = require('../database');
module.exports = function(Meta) {
@@ -34,27 +36,24 @@ module.exports = function(Meta) {
async.map(themes, function (theme, next) {
var config = path.join(themePath, theme, 'theme.json');
- if (fs.existsSync(config)) {
- fs.readFile(config, function (err, file) {
- if (err) {
- return next();
- } else {
- var configObj = JSON.parse(file.toString());
-
- // Minor adjustments for API output
- configObj.type = 'local';
- if (configObj.screenshot) {
- configObj.screenshot_url = nconf.get('relative_path') + '/css/previews/' + configObj.id;
- } else {
- configObj.screenshot_url = nconf.get('relative_path') + '/images/themes/default.png';
- }
-
- next(err, configObj);
- }
- });
- } else {
- next();
- }
+ fs.readFile(config, function (err, file) {
+ if (err) {
+ return next();
+ }
+
+ var configObj = JSON.parse(file.toString());
+
+ // Minor adjustments for API output
+ configObj.type = 'local';
+ if (configObj.screenshot) {
+ configObj.screenshot_url = nconf.get('relative_path') + '/css/previews/' + configObj.id;
+ } else {
+ configObj.screenshot_url = nconf.get('relative_path') + '/images/themes/default.png';
+ }
+
+ next(null, configObj);
+ });
+
}, function (err, themes) {
themes = themes.filter(function (theme) {
return (theme !== undefined);
@@ -145,7 +144,7 @@ module.exports = function(Meta) {
if (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;
}
diff --git a/src/middleware/index.js b/src/middleware/index.js
index fedb8b55cb..80b6677970 100644
--- a/src/middleware/index.js
+++ b/src/middleware/index.js
@@ -2,6 +2,7 @@
var meta = require('../meta'),
db = require('../database'),
+ file = require('../file'),
auth = require('../routes/authentication'),
path = require('path'),
@@ -21,7 +22,7 @@ var middleware = {};
function setupFavicon(app) {
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));
}
}
diff --git a/src/plugins.js b/src/plugins.js
index 7abf65dafa..f86251293a 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -14,6 +14,7 @@ var fs = require('fs'),
translator = require('../public/src/modules/translator'),
utils = require('../public/src/utils'),
hotswap = require('./hotswap'),
+ file = require('./file'),
controllers = require('./controllers'),
app, middleware;
@@ -103,7 +104,7 @@ var fs = require('fs'),
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);
});
},
diff --git a/src/plugins/load.js b/src/plugins/load.js
index ad441a2b97..f7d54cc6a8 100644
--- a/src/plugins/load.js
+++ b/src/plugins/load.js
@@ -6,6 +6,7 @@ var fs = require('fs'),
async = require('async'),
winston = require('winston'),
nconf = require('nconf'),
+ file = require('../file'),
utils = require('../../public/src/utils');
@@ -107,7 +108,7 @@ module.exports = function(Plugins) {
var realPath = pluginData.staticDirs[mappedPath];
var staticDir = path.join(pluginPath, realPath);
- fs.exists(staticDir, function(exists) {
+ file.exists(staticDir, function(exists) {
if (exists) {
Plugins.staticDirs[pluginData.id + '/' + mappedPath] = staticDir;
} else {
diff --git a/src/privileges/topics.js b/src/privileges/topics.js
index 53a69c4552..ab0ee8fe74 100644
--- a/src/privileges/topics.js
+++ b/src/privileges/topics.js
@@ -51,6 +51,7 @@ module.exports = function(privileges) {
editable: editable,
deletable: deletable,
view_deleted: isAdminOrMod || results.isOwner,
+ isAdminOrMod: isAdminOrMod,
disabled: disabled,
tid: tid,
uid: uid
diff --git a/src/routes/plugins.js b/src/routes/plugins.js
index ad1c0e84fa..72af49fbed 100644
--- a/src/routes/plugins.js
+++ b/src/routes/plugins.js
@@ -22,31 +22,18 @@ module.exports = function(app, middleware, controllers) {
} else {
return null;
}
- }).filter(function(a) { return a; });
+ }).filter(Boolean);
- if (matches) {
- async.map(matches, function(mappedPath, next) {
- var filePath = path.join(plugins.staticDirs[mappedPath], decodeURIComponent(relPath.slice(mappedPath.length)));
+ if (!matches) {
+ return next();
+ }
- fs.exists(filePath, function(exists) {
- if (exists) {
- next(null, filePath);
- } else {
- next();
- }
- });
- }, function(err, matches) {
- if (err) {
- return next(err);
- }
- matches = matches.filter(Boolean);
+ matches = matches.map(function(mappedPath) {
+ return path.join(plugins.staticDirs[mappedPath], decodeURIComponent(relPath.slice(mappedPath.length)));
+ });
- if (matches.length) {
- res.sendFile(matches[0]);
- } else {
- next();
- }
- });
+ if (matches.length) {
+ res.sendFile(matches[0]);
} else {
next();
}
diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js
index 9e81c09993..605fafd475 100644
--- a/src/socket.io/posts.js
+++ b/src/socket.io/posts.js
@@ -145,21 +145,6 @@ SocketPosts.getRawPost = function(socket, pid, 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) {
loadMorePosts('uid:' + data.uid + ':favourites', socket.uid, data, callback);
};
diff --git a/src/topics/create.js b/src/topics/create.js
index dd6e993c27..ae60a8c191 100644
--- a/src/topics/create.js
+++ b/src/topics/create.js
@@ -283,6 +283,9 @@ module.exports = function(Topics) {
topicInfo: function(next) {
Topics.getTopicFields(tid, ['tid', 'title', 'slug', 'cid', 'postcount'], next);
},
+ parents: function(next) {
+ Topics.addParentPosts([postData], next);
+ },
content: function(next) {
posts.parsePost(postData, next);
}
diff --git a/src/topics/posts.js b/src/topics/posts.js
index bf1e51ac4d..521a101097 100644
--- a/src/topics/posts.js
+++ b/src/topics/posts.js
@@ -112,6 +112,9 @@ module.exports = function(Topics) {
},
privileges: function(next) {
privileges.posts.get(pids, uid, next);
+ },
+ parents: function(next) {
+ Topics.addParentPosts(postData, next);
}
}, function(err, results) {
if (err) {
@@ -129,9 +132,9 @@ module.exports = function(Topics) {
postObj.votes = postObj.votes || 0;
postObj.display_moderator_tools = results.privileges[i].editable;
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]]';
}
@@ -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) {
posts.forEach(function(post, index) {
if (reverse) {
diff --git a/src/views/500.tpl b/src/views/500.tpl
index 6408fbb79d..537cbac136 100644
--- a/src/views/500.tpl
+++ b/src/views/500.tpl
@@ -1,3 +1,4 @@
+
\ No newline at end of file
diff --git a/src/views/admin/partials/categories/category-rows.tpl b/src/views/admin/partials/categories/category-rows.tpl
index 273d7e31b5..715427c34e 100644
--- a/src/views/admin/partials/categories/category-rows.tpl
+++ b/src/views/admin/partials/categories/category-rows.tpl
@@ -26,4 +26,7 @@
+
+
+
\ No newline at end of file
diff --git a/src/webserver.js b/src/webserver.js
index 9b6fb24e8c..87b9f1a8cc 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -186,9 +186,10 @@ module.exports.testSocket = function(socketPath, callback) {
return callback(new Error('invalid socket path : ' + socketPath));
}
var net = require('net');
+ var file = require('./file');
async.series([
function(next) {
- fs.exists(socketPath, function(exists) {
+ file.exists(socketPath, function(exists) {
if (exists) {
next();
} else {