diff --git a/.gitignore b/.gitignore
index 40ea2a6a86..478600f89f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,3 @@
-#################
-## npm
-#################
-
npm-debug.log
node_modules/
sftp-config.json
@@ -9,12 +5,10 @@ config.json
public/src/nodebb.min.js
public/config.json
public/css/*.css
-public/themes/*
-!/public/themes/vanilla
-!/public/themes/cerulean
-!/public/themes/modern
*.sublime-project
*.sublime-workspace
-plugins/*
.project
*.swp
+Vagrantfile
+.vagrant
+provision.sh
\ No newline at end of file
diff --git a/app.js b/app.js
index e8b662bbd4..2a4fb645ec 100644
--- a/app.js
+++ b/app.js
@@ -121,6 +121,10 @@
winston.warn('Configuration not found, starting NodeBB setup');
}
+ nconf.file({
+ file: __dirname + '/config.json'
+ });
+
var install = require('./src/install');
winston.info('Welcome to NodeBB!');
diff --git a/package.json b/package.json
index 168983c9cd..b59f9c96bd 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
"uglify-js": "~2.4.0",
"validator": "~1.5.1",
"nodebb-plugin-mentions": "~0.1.14",
- "nodebb-plugin-markdown": "~0.1.7",
+ "nodebb-plugin-markdown": "~0.1.8",
"nodebb-theme-vanilla": "designcreateplay/nodebb-theme-vanilla",
"nodebb-theme-cerulean": "0.0.5",
"cron": "~1.0.1"
diff --git a/public/src/app.js b/public/src/app.js
index eb4cf4a028..026c66496c 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -339,6 +339,7 @@ var socket,
chatModal = chat.getModal(touid);
}
chat.load(chatModal.attr('UUID'));
+ chat.center(chatModal);
});
}
diff --git a/public/src/forum/category.js b/public/src/forum/category.js
index 07d64471d3..c3d0799c26 100644
--- a/public/src/forum/category.js
+++ b/public/src/forum/category.js
@@ -27,12 +27,11 @@ define(function () {
return false;
});
- var new_post = document.getElementById('new_post');
- new_post.onclick = function () {
+ $('#new_post').on('click', function () {
require(['composer'], function (cmp) {
cmp.push(0, cid);
});
- }
+ });
ajaxify.register_events([
'event:new_topic'
@@ -59,11 +58,12 @@ define(function () {
li.innerHTML = '
' +
'' +
+ ''+ posts[i].username + '' +
'' +
posts[i].content +
'
' +
- '' + posts[i].username + ' -
' +
- '';
+ '' +
+ '';
frag.appendChild(li.cloneNode(true));
recent_replies.appendChild(frag);
@@ -106,9 +106,27 @@ define(function () {
}
socket.emit('api:categories.getRecentReplies', templates.get('category_id'));
+
+ addActiveUser(data);
+
$('#topics-container span.timeago').timeago();
}
+ function addActiveUser(data) {
+ var activeUser = $('.category-sidebar .active-users').find('a[data-uid="' + data.uid + '"]');
+ if(!activeUser.length) {
+ var newUser = templates.prepare(templates['category'].blocks['active_users']).parse({
+ active_users: [{
+ uid: data.uid,
+ username: data.username,
+ userslug: data.userslug,
+ picture: data.teaser_userpicture
+ }]
+ });
+ $(newUser).appendTo($('.category-sidebar .active-users'));
+ }
+ }
+
Category.onTopicsLoaded = function(topics) {
var html = templates.prepare(templates['category'].blocks['topics']).parse({
diff --git a/public/src/forum/footer.js b/public/src/forum/footer.js
index fa2240a93c..df9c52ac6f 100644
--- a/public/src/forum/footer.js
+++ b/public/src/forum/footer.js
@@ -1,28 +1,4 @@
(function() {
- var stats_users = document.getElementById('stats_users'),
- stats_topics = document.getElementById('stats_topics'),
- stats_posts = document.getElementById('stats_posts'),
- stats_online = document.getElementById('stats_online'),
- user_label = document.getElementById('user_label');
-
- socket.emit('user.count', {});
- socket.on('user.count', function(data) {
- stats_users.innerHTML = utils.makeNumberHumanReadable(data.count);
- stats_users.title = data.count;
- });
-
- socket.emit('post.stats');
- socket.on('post.stats', function(data) {
- stats_topics.innerHTML = utils.makeNumberHumanReadable(data.topics);
- stats_topics.title = data.topics;
- stats_posts.innerHTML = utils.makeNumberHumanReadable(data.posts);
- stats_posts.title = data.posts;
- });
-
- socket.emit('api:user.active.get');
- socket.on('api:user.active.get', function(data) {
- stats_online.innerHTML = data.users;
- });
socket.emit('api:updateHeader', {
fields: ['username', 'picture', 'userslug']
diff --git a/public/src/forum/home.js b/public/src/forum/home.js
new file mode 100644
index 0000000000..971be662a6
--- /dev/null
+++ b/public/src/forum/home.js
@@ -0,0 +1,30 @@
+define(function() {
+ var home = {};
+
+ home.init = function() {
+
+ ajaxify.register_events([
+ 'user.count',
+ 'post.stats',
+ 'api:user.active.get'
+ ]);
+
+ socket.emit('user.count', {});
+ socket.on('user.count', function(data) {
+ $('#stats_users').html(utils.makeNumberHumanReadable(data.count)).attr('title', data.count);
+ });
+
+ socket.emit('post.stats');
+ socket.on('post.stats', function(data) {
+ $('#stats_topics').html(utils.makeNumberHumanReadable(data.topics)).attr('title', data.topics);
+ $('#stats_posts').html(utils.makeNumberHumanReadable(data.posts)).attr('title', data.posts);
+ });
+
+ socket.emit('api:user.active.get');
+ socket.on('api:user.active.get', function(data) {
+ $('#stats_online').html(data.users);
+ });
+ }
+
+ return home;
+});
diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js
index aad7331faf..7239b20db7 100644
--- a/public/src/modules/chat.js
+++ b/public/src/modules/chat.js
@@ -85,7 +85,7 @@ define(['taskbar'], function(taskbar) {
return chatModal;
}
- function center(chatModal) {
+ module.center = function(chatModal) {
chatModal.css("position", "fixed");
chatModal.css("top", "100px");
chatModal.css("left", Math.max(0, (($(window).width() - $(chatModal).outerWidth()) / 2) + $(window).scrollLeft()) + "px");
@@ -95,7 +95,6 @@ define(['taskbar'], function(taskbar) {
module.load = function(uuid) {
var chatModal = $('div[UUID="'+uuid+'"]');
chatModal.show();
- center(chatModal);
module.bringModalToTop(chatModal);
checkOnlineStatus(chatModal);
}
diff --git a/public/src/translator.js b/public/src/translator.js
index 237969f609..43dc448b37 100644
--- a/public/src/translator.js
+++ b/public/src/translator.js
@@ -80,9 +80,10 @@
if (keys.hasOwnProperty(key)) {
var variables = keys[key].split(/[,][?\s+]/);
- var parsedKey = keys[key].replace('[[', '').replace(']]', '').split(':'),
- languageFile = parsedKey[0];
-
+ var parsedKey = keys[key].replace('[[', '').replace(']]', '').split(':');
+ if (!(parsedKey[0] && parsedKey[1])) continue;
+
+ var languageFile = parsedKey[0];
parsedKey = parsedKey[1].split(',')[0];
if (files.loaded[languageFile]) {
diff --git a/public/templates/category.tpl b/public/templates/category.tpl
index 5efceef0c8..6d23ffc495 100644
--- a/public/templates/category.tpl
+++ b/public/templates/category.tpl
@@ -76,9 +76,9 @@
-
+
-

+
diff --git a/public/templates/footer.tpl b/public/templates/footer.tpl
index 85d380fbc2..85343f1e32 100644
--- a/public/templates/footer.tpl
+++ b/public/templates/footer.tpl
@@ -60,29 +60,6 @@
diff --git a/public/templates/home.tpl b/public/templates/home.tpl
index da640ebdc7..48032a83b3 100644
--- a/public/templates/home.tpl
+++ b/public/templates/home.tpl
@@ -26,4 +26,27 @@
-
\ No newline at end of file
+
+
+
diff --git a/src/categories.js b/src/categories.js
index 95baee275e..d39dc1a729 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -96,7 +96,7 @@ var RDB = require('./redis.js'),
}
function getActiveUsers(next) {
- user.getMultipleUserFields(active_users, ['username', 'userslug', 'picture'], function(err, users) {
+ user.getMultipleUserFields(active_users, ['uid', 'username', 'userslug', 'picture'], function(err, users) {
next(err, users);
});
}
@@ -425,7 +425,8 @@ var RDB = require('./redis.js'),
};
Categories.addActiveUser = function(cid, uid) {
- RDB.sadd('cid:' + cid + ':active_users', uid);
+ if(parseInt(uid, 10))
+ RDB.sadd('cid:' + cid + ':active_users', uid);
};
Categories.removeActiveUser = function(cid, uid) {
diff --git a/src/install.js b/src/install.js
index 01a3806656..8541a0e15e 100644
--- a/src/install.js
+++ b/src/install.js
@@ -12,44 +12,44 @@ var async = require('async'),
questions: [{
name: 'base_url',
description: 'URL of this installation',
- 'default': 'http://localhost',
+ 'default': nconf.get('base_url') || 'http://localhost',
pattern: /^http(?:s)?:\/\//,
message: 'Base URL must begin with \'http://\' or \'https://\'',
}, {
name: 'port',
description: 'Port number of your NodeBB',
- 'default': 4567,
+ 'default': nconf.get('port') || 4567,
pattern: /[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]/,
message: 'Please enter a value betweeen 1 and 65535'
}, {
name: 'use_port',
description: 'Use a port number to access NodeBB?',
- 'default': 'y',
+ 'default': (nconf.get('use_port') ? 'y' : 'n') || 'y',
pattern: /y[es]*|n[o]?/,
message: 'Please enter \'yes\' or \'no\'',
}, {
name: 'secret',
description: 'Please enter a NodeBB secret',
- 'default': utils.generateUUID()
+ 'default': nconf.get('secret') || utils.generateUUID()
}, {
name: 'redis:host',
description: 'Host IP or address of your Redis instance',
- 'default': '127.0.0.1'
+ 'default': nconf.get('redis:host') || '127.0.0.1'
}, {
name: 'redis:port',
description: 'Host port of your Redis instance',
- 'default': 6379
+ 'default': nconf.get('redis:port') || 6379
}, {
name: 'redis:password',
description: 'Password of your Redis database'
}, {
name: "redis:database",
description: "Which database to use (0..n)",
- 'default': 0
+ 'default': nconf.get('redis:database') || 0
}, {
name: 'bind_address',
description: 'IP or Hostname to bind to',
- 'default': '0.0.0.0'
+ 'default': nconf.get('bind_address') || '0.0.0.0'
}],
setup: function (callback) {
async.series([
diff --git a/src/plugins.js b/src/plugins.js
index 51e45716b7..97394884db 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -17,8 +17,31 @@ var fs = require('fs'),
if (this.initialized) return;
if (global.env === 'development') winston.info('[plugins] Initializing plugins system');
+ this.reload(function(err) {
+ if (err) {
+ if (global.env === 'development') winston.info('[plugins] NodeBB encountered a problem while loading plugins', err.message);
+ return;
+ }
+
+ if (global.env === 'development') winston.info('[plugins] Plugins OK');
+
+ plugins.initialized = true;
+ plugins.readyEvent.emit('ready');
+ });
+ },
+ ready: function(callback) {
+ if (!this.initialized) this.readyEvent.once('ready', callback);
+ else callback();
+ },
+ initialized: false,
+ reload: function(callback) {
var _self = this;
+ // Resetting all local plugin data
+ this.loadedHooks = {};
+ this.staticDirs = {};
+ this.cssFiles.length = 0;
+
// Read the list of activated plugins and require their libraries
async.waterfall([
function(next) {
@@ -27,10 +50,8 @@ var fs = require('fs'),
function(plugins, next) {
if (plugins && Array.isArray(plugins) && plugins.length > 0) {
async.each(plugins, function(plugin, next) {
- var pluginPath = path.join(__dirname, '../plugins/', plugin),
- modulePath = path.join(__dirname, '../node_modules/', plugin);
- if (fs.existsSync(pluginPath)) _self.loadPlugin(pluginPath, next);
- else if (fs.existsSync(modulePath)) _self.loadPlugin(modulePath, next);
+ var modulePath = path.join(__dirname, '../node_modules/', plugin);
+ if (fs.existsSync(modulePath)) _self.loadPlugin(modulePath, next);
else {
if (global.env === 'development') winston.warn('[plugins] Plugin \'' + plugin + '\' not found');
next(); // Ignore this plugin silently
@@ -49,23 +70,8 @@ var fs = require('fs'),
next();
}
- ], function(err) {
- if (err) {
- if (global.env === 'development') winston.info('[plugins] NodeBB encountered a problem while loading plugins', err.message);
- return;
- }
-
- if (global.env === 'development') winston.info('[plugins] Plugins OK');
-
- _self.initialized = true;
- _self.readyEvent.emit('ready');
- });
+ ], callback);
},
- ready: function(callback) {
- if (!this.initialized) this.readyEvent.once('ready', callback);
- else callback();
- },
- initialized: false,
loadPlugin: function(pluginPath, callback) {
var _self = this;
@@ -82,16 +88,25 @@ var fs = require('fs'),
fs.exists(libraryPath, function(exists) {
if (exists) {
- _self.libraries[pluginData.id] = require(libraryPath);
+ if (!_self.libraries[pluginData.id]) {
+ _self.libraries[pluginData.id] = require(libraryPath);
+ }
+ // Register hooks for this plugin
if (pluginData.hooks && Array.isArray(pluginData.hooks) && pluginData.hooks.length > 0) {
async.each(pluginData.hooks, function(hook, next) {
_self.registerHook(pluginData.id, hook, next);
}, next);
}
+ } else {
+ winston.warn('[plugins.reload] Library not found for plugin: ' + pluginData.id);
+ next();
}
});
- } else next();
+ } else {
+ winston.warn('[plugins.reload] Library not found for plugin: ' + pluginData.id);
+ next();
+ }
},
function(next) {
// Static Directories for Plugins
@@ -219,15 +234,18 @@ var fs = require('fs'),
return;
}
- // (De)activation Hooks
- plugins.fireHook('action:plugin.' + (active ? 'de' : '') + 'activate', id);
+ // Reload meta data
+ plugins.reload(function() {
+ // (De)activation Hooks
+ plugins.fireHook('action:plugin.' + (active ? 'de' : '') + 'activate', id);
- if (callback) {
- callback({
- id: id,
- active: !active
- });
- }
+ if (callback) {
+ callback({
+ id: id,
+ active: !active
+ });
+ }
+ });
});
});
},
diff --git a/src/posts.js b/src/posts.js
index 06bac443b2..4c428e68fc 100644
--- a/src/posts.js
+++ b/src/posts.js
@@ -132,12 +132,6 @@ var RDB = require('./redis.js'),
});
};
- Posts.filterBannedPosts = function(posts) {
- return posts.filter(function(post) {
- return post.user_banned === '0';
- });
- }
-
// TODO: this function is never called except from some debug route. clean up?
Posts.getPostData = function(pid, callback) {
RDB.hgetall('post:' + pid, function(err, data) {
@@ -146,8 +140,9 @@ var RDB = require('./redis.js'),
if (!err) callback(newData);
else callback(data);
});
- } else
- console.log(err);
+ } else {
+ winston.error(err);
+ }
});
}
diff --git a/src/routes/plugins.js b/src/routes/plugins.js
new file mode 100644
index 0000000000..33e384e8d2
--- /dev/null
+++ b/src/routes/plugins.js
@@ -0,0 +1,25 @@
+var nconf = require('nconf'),
+ path = require('path'),
+ fs = require('fs'),
+ Plugins = require('../plugins'),
+
+ PluginRoutes = function(app) {
+ // Static Assets
+ app.get('/plugins/:id/*', function(req, res) {
+ var relPath = req.url.replace('/plugins/' + req.params.id, '');
+ if (Plugins.staticDirs[req.params.id]) {
+ var fullPath = path.join(Plugins.staticDirs[req.params.id], relPath);
+ fs.exists(fullPath, function(exists) {
+ if (exists) {
+ res.sendfile(fullPath);
+ } else {
+ res.redirect('/404');
+ }
+ })
+ } else {
+ res.redirect('/404');
+ }
+ });
+ };
+
+module.exports = PluginRoutes;
\ No newline at end of file
diff --git a/src/topics.js b/src/topics.js
index c95b3be200..57c500b4c9 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -336,7 +336,7 @@ var RDB = require('./redis.js'),
topicData.badgeclass = (topicInfo.hasread && current_user != 0) ? '' : 'badge-important';
topicData.teaser_text = topicInfo.teaserInfo.text || '',
topicData.teaser_username = topicInfo.teaserInfo.username || '';
- topicData.teaser_userpicture = topicInfo.teaserInfo.picture || '';
+ topicData.teaser_userpicture = topicInfo.teaserInfo.picture || require('gravatar').url('', {}, https = nconf.get('https'));
topicData.teaser_pid = topicInfo.teaserInfo.pid;
topicData.teaser_timestamp = topicInfo.teaserInfo.timestamp ? (new Date(parseInt(topicInfo.teaserInfo.timestamp, 10)).toISOString()) : '';
@@ -461,6 +461,7 @@ var RDB = require('./redis.js'),
topicData.badgeclass = hasRead ? '' : 'badge-important';
topicData.teaser_text = teaser.text || '';
topicData.teaser_username = teaser.username || '';
+ topicData.userslug = teaser.userslug || '';
topicData.teaser_timestamp = teaser.timestamp ? (new Date(parseInt(teaser.timestamp,10)).toISOString()) : '';
topicData.teaser_userpicture = teaser.picture;
@@ -606,7 +607,7 @@ var RDB = require('./redis.js'),
if (!err) {
posts.getPostFields(pid, ['pid', 'content', 'uid', 'timestamp'], function(postData) {
- user.getUserFields(postData.uid, ['username', 'picture'], function(err, userData) {
+ user.getUserFields(postData.uid, ['username', 'userslug', 'picture'], function(err, userData) {
if (err)
return callback(err, null);
@@ -615,6 +616,7 @@ var RDB = require('./redis.js'),
returnObj = {
"pid": postData.pid,
"username": userData.username,
+ "userslug": userData.userslug,
"picture": userData.picture,
"timestamp": timestamp
};
diff --git a/src/webserver.js b/src/webserver.js
index f82c01fa29..bf847d960f 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -151,19 +151,6 @@ var express = require('express'),
},
function(next) {
async.parallel([
- function(next) {
- // Static Directories for NodeBB Plugins
- plugins.ready(function () {
- for (d in plugins.staticDirs) {
- app.use(nconf.get('relative_path') + '/plugins/' + d, express.static(plugins.staticDirs[d]));
- if (process.env.NODE_ENV === 'development') {
- winston.info('Static directory routed for plugin: ' + d);
- }
- }
-
- next();
- });
- },
function(next) {
// Theme configuration
RDB.hmget('config', 'theme:type', 'theme:id', 'theme:staticDir', 'theme:templates', function(err, themeData) {
@@ -701,6 +688,9 @@ var express = require('express'),
});
});
+ // Other routes
+ require('./routes/plugins')(app);
+
// Debug routes
if (process.env.NODE_ENV === 'development') {
require('./routes/debug')(app);
diff --git a/src/websockets.js b/src/websockets.js
index ea3bd0dca0..aa650d4173 100644
--- a/src/websockets.js
+++ b/src/websockets.js
@@ -666,10 +666,17 @@ module.exports.init = function(io) {
socket.on('api:config.set', function(data) {
meta.configs.set(data.key, data.value, function(err) {
- if (!err) socket.emit('api:config.set', {
- status: 'ok'
- });
- /* Another hook, for my (adarqui's) logger module */
+ if (!err) {
+ socket.emit('api:config.set', {
+ status: 'ok'
+ });
+
+ plugins.fireHook('action:config.set', {
+ key: data.key,
+ value: data.value
+ });
+ }
+
logger.monitorConfig(this, data);
});
});