diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json index 2562dfe575..eaf15bff36 100644 --- a/public/language/en_GB/user.json +++ b/public/language/en_GB/user.json @@ -79,6 +79,9 @@ "browsing": "Browsing Settings", "open_links_in_new_tab": "Open outgoing links in new tab?", + "enable_topic_searching": "Enable In-Topic Searching", + "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen.", + "follow_topics_you_reply_to": "Follow topics that you reply to.", "follow_topics_you_create": "Follow topics you create." } diff --git a/public/src/admin/advanced/logs.js b/public/src/admin/advanced/logs.js new file mode 100644 index 0000000000..c44f6bf93f --- /dev/null +++ b/public/src/admin/advanced/logs.js @@ -0,0 +1,41 @@ +"use strict"; +/* global define, socket */ + +define('admin/advanced/logs', function() { + var Logs = {}; + + Logs.init = function() { + var logsEl = $('.logs pre'); + + // Affix menu + $('.affix').affix(); + + $('.logs').find('button[data-action]').on('click', function(e) { + var btnEl = $(this), + action = btnEl.attr('data-action'); + + switch(action) { + case 'reload': + socket.emit('admin.logs.get', function(err, logs) { + if (!err) { + logsEl.text(logs); + } else { + app.alertError(err.message); + } + }); + break; + + case 'clear': + socket.emit('admin.logs.clear', function(err) { + if (!err) { + app.alertSuccess('Logs Cleared!') + btnEl.prev().click(); + } + }); + break; + } + }); + }; + + return Logs; +}); diff --git a/public/src/admin/extend/plugins.js b/public/src/admin/extend/plugins.js index 5c1a1229ca..52c9144360 100644 --- a/public/src/admin/extend/plugins.js +++ b/public/src/admin/extend/plugins.js @@ -35,7 +35,22 @@ define('admin/extend/plugins', function() { Plugins.suggest(pluginID, function(err, payload) { if (!err) { - Plugins.toggleInstall(pluginID, payload.version); + require(['semver'], function(semver) { + if (payload.version !== 'latest') { + Plugins.toggleInstall(pluginID, payload.version); + } else if (payload.version === 'latest') { + bootbox.confirm( + '

No Compatibility Infomation Found

This plugin did not specify a specific version for installation given your NodeBB version. Full compatibility cannot be guaranteed, and may cause your NodeBB to no longer start properly.

' + + '

In the event that NodeBB cannot boot properly:

' + + '
$ ./nodebb reset plugin="' + pluginID + '"
' + + '

Continue installation of latest version of this plugin?

' + , function(confirm) { + if (confirm) { + Plugins.toggleInstall(pluginID, 'latest'); + } + }); + } + }); } else { bootbox.confirm('

NodeBB could not reach the package manager, proceed with installation of latest version?

Server returned (' + err.status + '): ' + err.responseText + '
', function(confirm) { if (confirm) { @@ -55,7 +70,7 @@ define('admin/extend/plugins', function() { Plugins.suggest(pluginID, function(err, payload) { if (!err) { require(['semver'], function(semver) { - if (payload.version === 'latest' || semver.gt(payload.version, parent.find('.currentVersion').text())) { + if (payload.version !== 'latest' && semver.gt(payload.version, parent.find('.currentVersion').text())) { btn.attr('disabled', true).find('i').attr('class', 'fa fa-refresh fa-spin'); socket.emit('admin.plugins.upgrade', { id: pluginID, @@ -68,6 +83,27 @@ define('admin/extend/plugins', function() { parent.find('.currentVersion').text(payload.version); btn.remove(); }); + } else if (payload.version === 'latest') { + bootbox.confirm( + '

No Compatibility Infomation Found

This plugin did not specify a specific version for installation given your NodeBB version. Full compatibility cannot be guaranteed, and may cause your NodeBB to no longer start properly.

' + + '

In the event that NodeBB cannot boot properly:

' + + '
$ ./nodebb reset plugin="' + pluginID + '"
' + + '

Continue installation of latest version of this plugin?

' + , function(confirm) { + if (confirm) { + socket.emit('admin.plugins.upgrade', { + id: pluginID, + version: payload.version + }, function(err) { + if (err) { + return app.alertError(err.message); + } + parent.find('.fa-exclamation-triangle').remove(); + parent.find('.currentVersion').text(payload.version); + btn.remove(); + }); + } + }); } else { bootbox.alert('

Your version of NodeBB (v' + app.config.version + ') is only cleared to upgrade to v' + payload.version + ' of this plugin. Please update your NodeBB if you wish to install a newer version of this plugin.'); } diff --git a/public/src/app.js b/public/src/app.js index 09828f14c1..1bd80d9fac 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -488,14 +488,16 @@ var socket, }); Mousetrap.bind('ctrl+f', function(e) { - // If in topic, open search window and populate, otherwise regular behaviour - var match = ajaxify.currentPage.match(/^topic\/([\d]+)/), - tid; - if (match) { - e.preventDefault(); - tid = match[1]; - searchInput.val('in:topic-' + tid + ' '); - prepareSearch(); + if (config.topicSearchEnabled) { + // If in topic, open search window and populate, otherwise regular behaviour + var match = ajaxify.currentPage.match(/^topic\/([\d]+)/), + tid; + if (match) { + e.preventDefault(); + tid = match[1]; + searchInput.val('in:topic-' + tid + ' '); + prepareSearch(); + } } }); }); @@ -539,7 +541,9 @@ var socket, handleStatusChange(); - handleSearch(); + if (config.searchEnabled) { + handleSearch(); + } $('#logout-link').on('click', app.logout); diff --git a/src/controllers/admin.js b/src/controllers/admin.js index 52bb3b7f18..5a43e1ba6c 100644 --- a/src/controllers/admin.js +++ b/src/controllers/admin.js @@ -187,14 +187,9 @@ adminController.events.get = function(req, res, next) { }; adminController.logs.get = function(req, res, next) { - var logPath = path.join('logs', path.sep, 'output.log'); - fs.readFile(logPath, function(err, data) { - if (err || !data) { - data = ''; - } - + meta.logs.get(function(err, logs) { res.render('admin/advanced/logs', { - data: validator.escape(data.toString()) + data: validator.escape(logs) }); }); }; diff --git a/src/controllers/api.js b/src/controllers/api.js index e6c5d478c1..5f96861299 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -53,6 +53,7 @@ apiController.getConfig = function(req, res, next) { config.requireEmailConfirmation = parseInt(meta.config.requireEmailConfirmation, 10) === 1; config.topicPostSort = meta.config.topicPostSort || 'oldest_to_newest'; config.csrf_token = req.csrfToken(); + config.searchEnabled = plugins.hasListeners('filter:search.query'); if (!req.user) { if (res.locals.isAPI) { @@ -75,6 +76,7 @@ apiController.getConfig = function(req, res, next) { config.userLang = settings.language || config.defaultLang; config.openOutgoingLinksInNewTab = settings.openOutgoingLinksInNewTab; config.topicPostSort = settings.topicPostSort || config.topicPostSort; + config.topicSearchEnabled = settings.topicSearchEnabled || false; if (res.locals.isAPI) { res.status(200).json(config); diff --git a/src/meta.js b/src/meta.js index 86f3b2ac9b..fdaa2d8107 100644 --- a/src/meta.js +++ b/src/meta.js @@ -20,6 +20,7 @@ var async = require('async'), require('./meta/css')(Meta); require('./meta/sounds')(Meta); require('./meta/settings')(Meta); + require('./meta/logs')(Meta); Meta.templates = require('./meta/templates'); /* Assorted */ diff --git a/src/meta/configs.js b/src/meta/configs.js index 4b314c758f..ae32d45599 100644 --- a/src/meta/configs.js +++ b/src/meta/configs.js @@ -59,16 +59,43 @@ module.exports = function(Meta) { }; Meta.configs.setMultiple = function(data, callback) { - db.setObject('config', data, function(err) { + processConfig(data, function(err) { if (err) { return callback(err); } + db.setObject('config', data, function(err) { + if (err) { + return callback(err); + } - updateConfig(data); - callback(); + updateConfig(data); + callback(); + }); }); }; + function processConfig(data, callback) { + if (data.customCSS) { + saveRenderedCss(data, callback); + return; + } + callback(); + } + + function saveRenderedCss(data, callback) { + var less = require('less'); + less.render(data.customCSS, { + compress: true + }, function(err, lessObject) { + if (err) { + winston.error('[less] Could not convert custom LESS to CSS! Please check your syntax.'); + return callback(null, ''); + } + data.renderedCustomCSS = lessObject.css; + callback(null, lessObject.css); + }); + } + function updateConfig(data) { var msg = {action: 'config:update', data: data}; if (process.send) { diff --git a/src/meta/css.js b/src/meta/css.js index 4006b524ae..fb1478dc1f 100644 --- a/src/meta/css.js +++ b/src/meta/css.js @@ -126,7 +126,8 @@ module.exports = function(Meta) { function minify(source, paths, destination, callback) { less.render(source, { - paths: paths + paths: paths, + compress: true }, function(err, lessOutput) { if (err) { winston.error('[meta/css] Could not minify LESS/CSS: ' + err.message); diff --git a/src/meta/logs.js b/src/meta/logs.js new file mode 100644 index 0000000000..01ee677887 --- /dev/null +++ b/src/meta/logs.js @@ -0,0 +1,28 @@ +'use strict'; + +var path = require('path'), + fs = require('fs'), + winston = require('winston'); + +module.exports = function(Meta) { + + Meta.logs = { + path: path.join('logs', path.sep, 'output.log') + }; + + Meta.logs.get = function(callback) { + fs.readFile(this.path, { + encoding: 'utf-8' + }, function(err, logs) { + if (err) { + winston.error('[meta/logs] Could not retrieve logs: ' + err.message); + } + + callback(undefined, logs || ''); + }); + } + + Meta.logs.clear = function(callback) { + fs.truncate(this.path, 0, callback); + } +}; \ No newline at end of file diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index a9b55c31e6..ad2b6eb408 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -198,7 +198,7 @@ middleware.checkAccountPermissions = function(req, res, next) { middleware.buildHeader = function(req, res, next) { res.locals.renderHeader = true; - + middleware.applyCSRF(req, res, function() { async.parallel({ config: function(next) { @@ -305,24 +305,10 @@ middleware.renderHeader = function(req, res, callback) { async.parallel({ customCSS: function(next) { templateValues.useCustomCSS = parseInt(meta.config.useCustomCSS, 10) === 1; - if (!templateValues.useCustomCSS) { + if (!templateValues.useCustomCSS || !meta.config.customCSS || !meta.config.renderedCustomCSS) { return next(null, ''); } - - if (!meta.config.customCSS) { - return next(null, ''); - } - - var less = require('less'); - - less.render(meta.config.customCSS, function(err, lessObject) { - if (err) { - winston.error('[less] Could not convert custom LESS to CSS! Please check your syntax.'); - return next(null, ''); - } - - next(null, lessObject.css); - }); + next(null, meta.config.renderedCustomCSS); }, customJS: function(next) { templateValues.useCustomJS = parseInt(meta.config.useCustomJS, 10) === 1; diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index db7d62d8e3..a2f09aa8b1 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -32,7 +32,8 @@ var async = require('async'), config: {}, settings: {}, email: {}, - analytics: {} + analytics: {}, + logs: {} }; SocketAdmin.before = function(socket, method, next) { @@ -207,6 +208,14 @@ SocketAdmin.analytics.get = function(socket, data, callback) { } }; +SocketAdmin.logs.get = function(socket, data, callback) { + meta.logs.get(callback); +}; + +SocketAdmin.logs.clear = function(socket, data, callback) { + meta.logs.clear(callback); +}; + function getHourlyStatsForSet(set, hours, callback) { var hour = new Date(), terms = {}, diff --git a/src/user/digest.js b/src/user/digest.js index 6c3adba7f7..a253ae62af 100644 --- a/src/user/digest.js +++ b/src/user/digest.js @@ -84,6 +84,11 @@ module.exports = (function(Digest) { notifications = notifications.filter(Boolean); + // If there are no notifications and no new topics, don't bother sending a digest + if (!notifications.length && !data.topics.topics.length) { + return next(); + } + for(var i=0; i +

+
+
Logs Control Panel
+
+ + +
+
+