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 @@
[[category:sidebar.active_participants]]
-
+
- +
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); }); });