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?
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