diff --git a/install/data/footer.json b/install/data/footer.json
index 12528110c6..53b2176ade 100644
--- a/install/data/footer.json
+++ b/install/data/footer.json
@@ -2,7 +2,7 @@
{
"widget": "html",
"data" : {
- "html": "",
+ "html": "",
"title":"",
"container":""
}
diff --git a/package.json b/package.json
index 8a7041d5a5..c007894688 100644
--- a/package.json
+++ b/package.json
@@ -55,19 +55,19 @@
"morgan": "^1.3.2",
"mousetrap": "^1.5.3",
"nconf": "~0.8.2",
- "nodebb-plugin-composer-default": "4.4.18",
+ "nodebb-plugin-composer-default": "4.4.19",
"nodebb-plugin-dbsearch": "2.0.4",
"nodebb-plugin-emoji-extended": "1.1.1",
"nodebb-plugin-emoji-one": "1.2.1",
- "nodebb-plugin-markdown": "7.1.1",
- "nodebb-plugin-mentions": "2.1.2",
+ "nodebb-plugin-markdown": "7.1.2",
+ "nodebb-plugin-mentions": "2.1.5",
"nodebb-plugin-soundpack-default": "1.0.0",
"nodebb-plugin-spam-be-gone": "0.5.0",
"nodebb-rewards-essentials": "0.0.9",
- "nodebb-theme-lavender": "4.0.2",
- "nodebb-theme-persona": "5.0.11",
+ "nodebb-theme-lavender": "4.0.5",
+ "nodebb-theme-persona": "5.0.12",
"nodebb-theme-slick": "1.1.0",
- "nodebb-theme-vanilla": "6.0.7",
+ "nodebb-theme-vanilla": "6.0.9",
"nodebb-widget-essentials": "3.0.0",
"nodemailer": "2.6.4",
"nodemailer-sendmail-transport": "1.0.0",
diff --git a/public/language/de/global.json b/public/language/de/global.json
index d97a50031f..d9d0a7eb3e 100644
--- a/public/language/de/global.json
+++ b/public/language/de/global.json
@@ -104,6 +104,6 @@
"cookies.accept": "Verstanden!",
"cookies.learn_more": "Erfahre mehr",
"edited": "Bearbeitet",
- "disabled": "Disabled",
- "select": "Select"
+ "disabled": "Deaktiviert",
+ "select": "Auswählen"
}
\ No newline at end of file
diff --git a/public/language/de/topic.json b/public/language/de/topic.json
index 3f948c38aa..f39f464fa1 100644
--- a/public/language/de/topic.json
+++ b/public/language/de/topic.json
@@ -59,7 +59,7 @@
"thread_tools.unlock": "Thema öffnen",
"thread_tools.move": "Thema verschieben",
"thread_tools.move_all": "Alle verschieben",
- "thread_tools.select_category": "Select Category",
+ "thread_tools.select_category": "Kategorie auswählen",
"thread_tools.fork": "Thema aufspalten",
"thread_tools.delete": "Thema löschen",
"thread_tools.delete-posts": "Beiträge entfernen",
diff --git a/public/language/vi/admin/advanced/logs.json b/public/language/vi/admin/advanced/logs.json
index b9de400e1c..945f128f34 100644
--- a/public/language/vi/admin/advanced/logs.json
+++ b/public/language/vi/admin/advanced/logs.json
@@ -1,7 +1,7 @@
{
- "logs": "Logs",
- "control-panel": "Logs Control Panel",
- "reload": "Reload Logs",
- "clear": "Clear Logs",
- "clear-success": "Logs Cleared!"
+ "logs": "Nhật ký",
+ "control-panel": "Bảng điều khiển log",
+ "reload": "Tải lại log",
+ "clear": "Xóa các log",
+ "clear-success": "Các log đã được xóa!"
}
\ No newline at end of file
diff --git a/public/language/vi/admin/appearance/skins.json b/public/language/vi/admin/appearance/skins.json
index 4db6fbdd8a..678229cd1c 100644
--- a/public/language/vi/admin/appearance/skins.json
+++ b/public/language/vi/admin/appearance/skins.json
@@ -1,9 +1,9 @@
{
- "loading": "Loading Skins...",
- "homepage": "Homepage",
- "select-skin": "Select Skin",
- "current-skin": "Current Skin",
- "skin-updated": "Skin Updated",
- "applied-success": "%1 skin was succesfully applied",
- "revert-success": "Skin reverted to base colours"
+ "loading": "Đang tải giao diện ...",
+ "homepage": "Trang chủ",
+ "select-skin": "Chọn giao diện",
+ "current-skin": "Giao diện hiện tại",
+ "skin-updated": "Đã cập nhật giao diện",
+ "applied-success": "1% giao diện đã được sử dụng thành công",
+ "revert-success": "Đã trả giao diện về màu cơ bản"
}
\ No newline at end of file
diff --git a/public/language/zh-CN/admin/manage/categories.json b/public/language/zh-CN/admin/manage/categories.json
index 77f2249fc8..babc97bb9f 100644
--- a/public/language/zh-CN/admin/manage/categories.json
+++ b/public/language/zh-CN/admin/manage/categories.json
@@ -10,7 +10,7 @@
"custom-class": "自定义 Class",
"num-recent-replies": "最近回复数",
"ext-link": "外部链接",
- "is-section": "Treat this category as a section",
+ "is-section": "将该板块作为段落",
"upload-image": "上传图片",
"delete-image": "移除",
"category-image": "版块图片",
diff --git a/public/less/admin/manage/categories.less b/public/less/admin/manage/categories.less
index c0c0a544dc..28eb9c7ff9 100644
--- a/public/less/admin/manage/categories.less
+++ b/public/less/admin/manage/categories.less
@@ -12,8 +12,6 @@ div.categories {
.row {
margin-left: -15px;
margin-right: -15px;
- padding-bottom: 12px;
- margin-bottom: 12px;
}
> li li:last-child {
@@ -37,7 +35,6 @@ div.categories {
li {
min-height: @acp-line-height;
margin: @acp-base-line 0;
- margin-top: 28px;
&.placeholder {
border: 1px dashed #2196F3;
@@ -56,21 +53,40 @@ div.categories {
}
}
+ .toggle {
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ line-height: 24px;
+ text-align: center;
+ vertical-align: bottom;
+ background-size: cover;
+ float: left;
+ margin-right: 0px;
+ cursor: pointer;
+ .fa {
+ font-size: 85%;
+ }
+ }
+
.icon {
- width: @acp-line-height;
- height: @acp-line-height;
+ width: 24px;
+ height: 24px;
border-radius: 50%;
- line-height: @acp-line-height;
+ line-height: 24px;
text-align: center;
vertical-align: bottom;
background-size: cover;
float: left;
- margin-right: @acp-margin;
+ margin-right: 0px;
cursor: move;
+ .fa {
+ font-size: 85%;
+ }
}
.information {
- padding-left: 70px;
+ padding-left: 60px;
}
.category-header {
diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js
index cb5d7e9975..2951481411 100644
--- a/public/src/admin/manage/categories.js
+++ b/public/src/admin/manage/categories.js
@@ -31,6 +31,12 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri
Categories.toggle([cid].concat(children), !disabled);
return false;
});
+
+ $('.categories').on('click', '.toggle', function () {
+ var el = $(this);
+ el.find('i').toggleClass('fa-minus').toggleClass('fa-plus');
+ el.closest('[data-cid]').find('> ul[data-cid]').toggleClass('hidden');
+ });
};
Categories.throwCreateModal = function () {
diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
index 9ca9052dc9..e2303c501c 100644
--- a/public/src/ajaxify.js
+++ b/public/src/ajaxify.js
@@ -204,7 +204,7 @@ $(document).ready(function () {
}
ajaxify.loadScript(tpl_url, done);
- ajaxify.widgets.render(tpl_url, url, done);
+ ajaxify.widgets.render(tpl_url, done);
$(window).trigger('action:ajaxify.contentLoaded', { url: url, tpl: tpl_url });
diff --git a/public/src/client/search.js b/public/src/client/search.js
index 19f66daebe..38a34fc818 100644
--- a/public/src/client/search.js
+++ b/public/src/client/search.js
@@ -119,6 +119,10 @@ define('forum/search', ['search', 'autocomplete', 'storage'], function (searchMo
$('#show-as-topics').prop('checked', isTopic).parent().toggleClass('active', isTopic);
$('#show-as-posts').prop('checked', isPost).parent().toggleClass('active', isPost);
}
+
+ $(window).trigger('action:search.fillOutForm', {
+ form: formData,
+ });
}
}
@@ -151,7 +155,7 @@ define('forum/search', ['search', 'autocomplete', 'storage'], function (searchMo
function handleSavePreferences() {
$('#save-preferences').on('click', function () {
- storage.setItem('search-preferences', JSON.stringify(getSearchDataFromDOMFromDOM()));
+ storage.setItem('search-preferences', JSON.stringify(getSearchDataFromDOM()));
app.alertSuccess('[[search:search-preferences-saved]]');
return false;
});
diff --git a/public/src/modules/search.js b/public/src/modules/search.js
index d9188e10c8..ce24bd4c40 100644
--- a/public/src/modules/search.js
+++ b/public/src/modules/search.js
@@ -77,7 +77,7 @@ define('search', ['navigator', 'translator', 'storage'], function (nav, translat
$(window).trigger('action:search.createQueryString', {
query: query,
- dom: data,
+ data: data,
});
return decodeURIComponent($.param(query));
diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js
index 8c68b0c06a..465a240e4e 100644
--- a/public/src/modules/translator.js
+++ b/public/src/modules/translator.js
@@ -580,39 +580,13 @@
prepareDOM: function prepareDOM() {
// Load the appropriate timeago locale file,
// and correct NodeBB language codes to timeago codes, if necessary
- var languageCode;
- switch (config.userLang) {
- case 'en-GB':
- case 'en-US':
- languageCode = 'en';
- break;
-
- case 'fa-IR':
- languageCode = 'fa';
- break;
-
- case 'pt-BR':
- languageCode = 'pt-br';
- break;
-
- case 'nb':
- languageCode = 'no';
- break;
-
- default:
- languageCode = config.userLang;
- break;
- }
+ var languageCode = utils.userLangToTimeagoCode(config.userLang);
- jQuery.getScript(config.relative_path + '/assets/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '.js').done(function () {
- jQuery('.timeago').timeago();
- adaptor.timeagoShort = assign({}, jQuery.timeago.settings.strings);
+ adaptor.timeagoShort = assign({}, jQuery.timeago.settings.strings);
- // Retrieve the shorthand timeago values as well
- jQuery.getScript(config.relative_path + '/assets/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '-short.js').done(function () {
- // Switch back to long-form
- adaptor.toggleTimeagoShorthand();
- });
+ jQuery.getScript(config.relative_path + '/assets/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '-short.js').done(function () {
+ // Switch back to long-form
+ adaptor.toggleTimeagoShorthand();
});
// Add directional code if necessary
diff --git a/public/src/utils.js b/public/src/utils.js
index fe655c2e3b..6e7f0400d1 100644
--- a/public/src/utils.js
+++ b/public/src/utils.js
@@ -103,7 +103,16 @@
hasLanguageKey: function (input) {
return utils.languageKeyRegex.test(input);
},
-
+ userLangToTimeagoCode: function (userLang) {
+ var mapping = {
+ 'en-GB': 'en',
+ 'en-US': 'en',
+ 'fa-IR': 'fa',
+ 'pt-BR': 'pt-br',
+ nb: 'no',
+ };
+ return mapping[userLang] || userLang;
+ },
// shallow objects merge
merge: function () {
var result = {};
diff --git a/public/src/widgets.js b/public/src/widgets.js
index f5f667d2a7..082b291624 100644
--- a/public/src/widgets.js
+++ b/public/src/widgets.js
@@ -1,90 +1,64 @@
'use strict';
-
(function (ajaxify) {
ajaxify.widgets = {};
- ajaxify.widgets.reposition = function (location) {
- $('body [has-widget-class]').each(function () {
- var $this = $(this);
- if ($this.attr('has-widget-target') === location) {
- $this.removeClass();
- $this.addClass($this.attr('has-widget-class'));
- }
- });
- };
-
- ajaxify.widgets.render = function (template, url, callback) {
+ ajaxify.widgets.render = function (template, callback) {
callback = callback || function () {};
+
if (template.match(/^admin/)) {
return callback();
}
- var widgetLocations = ['sidebar', 'footer', 'header'];
+ var locations = Object.keys(ajaxify.data.widgets);
- $('#content [widget-area]').each(function () {
- var location = $(this).attr('widget-area');
- if ($.inArray(location, widgetLocations) === -1) {
- widgetLocations.push(location);
+ locations.forEach(function (location) {
+ var area = $('#content [widget-area="' + location + '"]');
+ if (area.length) {
+ return;
}
- });
- $.get(config.relative_path + '/api/widgets/render?' + config['cache-buster'], {
- locations: widgetLocations,
- template: template + '.tpl',
- url: url,
- cid: ajaxify.data.cid,
- isMobile: utils.isMobile(),
- }, function (renderedAreas) {
- for (var x = 0; x < renderedAreas.length; x += 1) {
- var renderedWidgets = renderedAreas[x].widgets;
- var location = renderedAreas[x].location;
- var html = '';
+ var widgetsAtLocation = ajaxify.data.widgets[location] || [];
+ var html = '';
- for (var i = 0; i < renderedWidgets.length; i += 1) {
- html += templates.parse(renderedWidgets[i].html, {});
- }
+ widgetsAtLocation.forEach(function (widget) {
+ html += widget.html;
- var area = $('#content [widget-area="' + location + '"]');
-
- if (!area.length && window.location.pathname.indexOf('/admin') === -1 && renderedWidgets.length) {
- if (location === 'footer' && !$('#content [widget-area="footer"]').length) {
- $('#content').append($('
'));
- } else if (location === 'sidebar' && !$('#content [widget-area="sidebar"]').length) {
- if ($('[component="account/cover"]').length) {
- $('[component="account/cover"]').nextAll().wrapAll($(''));
- } else if ($('[component="groups/cover"]').length) {
- $('[component="groups/cover"]').nextAll().wrapAll($(''));
- } else {
- $('#content > *').wrapAll($(''));
- }
- } else if (location === 'header' && !$('#content [widget-area="header"]').length) {
- $('#content').prepend($(''));
+ if (location === 'footer' && !$('#content [widget-area="footer"]').length) {
+ $('#content').append($(''));
+ } else if (location === 'sidebar' && !$('#content [widget-area="sidebar"]').length) {
+ if ($('[component="account/cover"]').length) {
+ $('[component="account/cover"]').nextAll().wrapAll($(''));
+ } else if ($('[component="groups/cover"]').length) {
+ $('[component="groups/cover"]').nextAll().wrapAll($(''));
+ } else {
+ $('#content > *').wrapAll($(''));
}
-
- area = $('#content [widget-area="' + location + '"]');
+ } else if (location === 'header' && !$('#content [widget-area="header"]').length) {
+ $('#content').prepend($(''));
}
+ });
+ area = $('#content [widget-area="' + location + '"]');
+ if (html && area.length) {
area.html(html);
+ }
- if (renderedWidgets.length) {
- area.removeClass('hidden');
- ajaxify.widgets.reposition(location);
- }
+ if (widgetsAtLocation.length) {
+ area.removeClass('hidden');
}
+ });
- var widgetAreas = $('#content [widget-area]');
- widgetAreas.find('img:not(.not-responsive)').addClass('img-responsive');
- widgetAreas.find('.timeago').timeago();
- widgetAreas.find('img[title].teaser-pic,img[title].user-img').each(function () {
- $(this).tooltip({
- placement: 'top',
- title: $(this).attr('title'),
- });
+ var widgetAreas = $('#content [widget-area]');
+ widgetAreas.find('img:not(.not-responsive)').addClass('img-responsive');
+ widgetAreas.find('.timeago').timeago();
+ widgetAreas.find('img[title].teaser-pic,img[title].user-img').each(function () {
+ $(this).tooltip({
+ placement: 'top',
+ title: $(this).attr('title'),
});
- $(window).trigger('action:widgets.loaded', {});
-
- callback(renderedAreas);
});
+ $(window).trigger('action:widgets.loaded', {});
+ callback();
};
}(ajaxify || {}));
diff --git a/src/batch.js b/src/batch.js
index fac78cd021..3a91775025 100644
--- a/src/batch.js
+++ b/src/batch.js
@@ -49,18 +49,20 @@ exports.processSortedSet = function (setKey, process, options, callback) {
return !done;
},
function (next) {
- db.getSortedSetRange(setKey, start, stop, function (err, ids) {
- if (err) {
- return next(err);
- }
- if (!ids.length || options.doneIf(start, stop, ids)) {
- done = true;
- return next();
- }
- process(ids, function (err) {
- if (err) {
- return next(err);
+ async.waterfall([
+ function (next) {
+ db.getSortedSetRange(setKey, start, stop, next);
+ },
+ function (ids, _next) {
+ if (!ids.length || options.doneIf(start, stop, ids)) {
+ done = true;
+ return next();
}
+ process(ids, function (err) {
+ _next(err);
+ });
+ },
+ function (next) {
start += utils.isNumber(options.alwaysStartAt) ? options.alwaysStartAt : options.batch + 1;
stop = start + options.batch;
@@ -69,8 +71,8 @@ exports.processSortedSet = function (setKey, process, options, callback) {
} else {
next();
}
- });
- });
+ },
+ ], next);
},
callback
);
@@ -106,17 +108,21 @@ exports.processArray = function (array, process, options, callback) {
done = true;
return next();
}
- process(currentBatch, function (err) {
- if (err) {
- return next(err);
- }
- start += batch;
- if (options.interval) {
- setTimeout(next, options.interval);
- } else {
- next();
- }
- });
+ async.waterfall([
+ function (next) {
+ process(currentBatch, function (err) {
+ next(err);
+ });
+ },
+ function (next) {
+ start += batch;
+ if (options.interval) {
+ setTimeout(next, options.interval);
+ } else {
+ next();
+ }
+ },
+ ], next);
},
function (err) {
callback(err);
diff --git a/src/controllers/accounts/notifications.js b/src/controllers/accounts/notifications.js
index c6715ba144..d398003782 100644
--- a/src/controllers/accounts/notifications.js
+++ b/src/controllers/accounts/notifications.js
@@ -12,7 +12,7 @@ var notificationsController = module.exports;
notificationsController.get = function (req, res, next) {
var regularFilters = [
{ name: '[[notifications:all]]', filter: '' },
- { name: '[[notifications:topics]]', filter: 'new-topic' },
+ { name: '[[global:topics]]', filter: 'new-topic' },
{ name: '[[notifications:replies]]', filter: 'new-reply' },
{ name: '[[notifications:chat]]', filter: 'new-chat' },
{ name: '[[notifications:follows]]', filter: 'follow' },
diff --git a/src/controllers/api.js b/src/controllers/api.js
index 5ec2f55697..b464748f49 100644
--- a/src/controllers/api.js
+++ b/src/controllers/api.js
@@ -11,12 +11,11 @@ var topics = require('../topics');
var categories = require('../categories');
var privileges = require('../privileges');
var plugins = require('../plugins');
-var widgets = require('../widgets');
var translator = require('../translator');
var apiController = module.exports;
-apiController.getConfig = function (req, res, next) {
+apiController.loadConfig = function (req, callback) {
var config = {};
config.environment = process.env.NODE_ENV;
config.relative_path = nconf.get('relative_path');
@@ -59,7 +58,7 @@ apiController.getConfig = function (req, res, next) {
config.requireEmailConfirmation = parseInt(meta.config.requireEmailConfirmation, 10) === 1;
config.topicPostSort = meta.config.topicPostSort || 'oldest_to_newest';
config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest';
- config.csrf_token = req.csrfToken();
+ config.csrf_token = req.csrfToken && req.csrfToken();
config.searchEnabled = plugins.hasListeners('filter:search.query');
config.bootswatchSkin = meta.config.bootswatchSkin || 'noskin';
config.defaultBootswatchSkin = meta.config.bootswatchSkin || 'noskin';
@@ -80,7 +79,7 @@ apiController.getConfig = function (req, res, next) {
async.waterfall([
function (next) {
- if (!req.user) {
+ if (!req.uid) {
return next(null, config);
}
user.getSettings(req.uid, next);
@@ -98,41 +97,22 @@ apiController.getConfig = function (req, res, next) {
config.bootswatchSkin = (settings.bootswatchSkin && settings.bootswatchSkin !== 'default') ? settings.bootswatchSkin : config.bootswatchSkin;
plugins.fireHook('filter:config.get', config, next);
},
- ], function (err, config) {
- if (err) {
- return next(err);
- }
-
- if (res.locals.isAPI) {
- res.json(config);
- } else {
- next(null, config);
- }
- });
+ ], callback);
};
-
-apiController.renderWidgets = function (req, res, next) {
- if (!req.query.template || !req.query.locations) {
- return res.status(200).json({});
- }
-
- widgets.render(req.uid,
- {
- template: req.query.template,
- url: req.query.url,
- locations: req.query.locations,
- isMobile: req.query.isMobile === 'true',
- cid: req.query.cid,
+apiController.getConfig = function (req, res, next) {
+ async.waterfall([
+ function (next) {
+ apiController.loadConfig(req, next);
},
- req,
- res,
- function (err, widgets) {
- if (err) {
- return next(err);
+ function (config, next) {
+ if (res.locals.isAPI) {
+ res.json(config);
+ } else {
+ next(null, config);
}
- res.status(200).json(widgets);
- });
+ },
+ ], next);
};
apiController.getPostData = function (pid, uid, callback) {
diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js
index 8db1d6266f..732652aa38 100644
--- a/src/controllers/helpers.js
+++ b/src/controllers/helpers.js
@@ -53,7 +53,7 @@ helpers.notAllowed = function (req, res, error) {
helpers.redirect = function (res, url) {
if (res.locals.isAPI) {
- res.set('X-Redirect', url).status(200).json(url);
+ res.set('X-Redirect', encodeURI(url)).status(200).json(url);
} else {
res.redirect(nconf.get('relative_path') + encodeURI(url));
}
diff --git a/src/controllers/index.js b/src/controllers/index.js
index 0f15d2f4ab..84c20ade81 100644
--- a/src/controllers/index.js
+++ b/src/controllers/index.js
@@ -291,8 +291,8 @@ Controllers.confirmEmail = function (req, res) {
Controllers.robots = function (req, res) {
res.set('Content-Type', 'text/plain');
- if (meta.config['robots.txt']) {
- res.send(meta.config['robots.txt']);
+ if (meta.config['robots:txt']) {
+ res.send(meta.config['robots:txt']);
} else {
res.send('User-agent: *\n' +
'Disallow: ' + nconf.get('relative_path') + '/admin/\n' +
diff --git a/src/database/mongo/helpers.js b/src/database/mongo/helpers.js
index 47f8434c77..7a791c9fca 100644
--- a/src/database/mongo/helpers.js
+++ b/src/database/mongo/helpers.js
@@ -6,7 +6,7 @@ helpers.toMap = function (data) {
var map = {};
for (var i = 0; i < data.length; i += 1) {
map[data[i]._key] = data[i];
- data[i]._key = undefined;
+ delete data[i]._key;
}
return map;
};
diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js
index 5d461043fe..feeed1e20a 100644
--- a/src/database/mongo/sorted.js
+++ b/src/database/mongo/sorted.js
@@ -480,32 +480,33 @@ module.exports = function (db, module) {
return !done;
},
function (next) {
- cursor.next(function (err, item) {
- if (err) {
- return next(err);
- }
- if (item === null) {
- done = true;
- } else {
- ids.push(item.value);
- }
-
- if (ids.length < options.batch && (!done || ids.length === 0)) {
- return next(null);
- }
-
- process(ids, function (err) {
- if (err) {
- return next(err);
+ async.waterfall([
+ function (next) {
+ cursor.next(next);
+ },
+ function (item, _next) {
+ if (item === null) {
+ done = true;
+ } else {
+ ids.push(item.value);
+ }
+
+ if (ids.length < options.batch && (!done || ids.length === 0)) {
+ return next(null);
}
+ process(ids, function (err) {
+ _next(err);
+ });
+ },
+ function (next) {
ids = [];
if (options.interval) {
setTimeout(next, options.interval);
} else {
next();
}
- });
- });
+ },
+ ], next);
},
callback
);
diff --git a/src/meta/css.js b/src/meta/css.js
index 3db85a50f3..0f20ed7aca 100644
--- a/src/meta/css.js
+++ b/src/meta/css.js
@@ -137,15 +137,9 @@ function getBundleMetadata(target, callback) {
var imports = cssImports + '\n' + lessImports;
imports = buildImports[target](imports);
- next(null, imports);
+ next(null, { paths: paths, imports: imports });
},
- ], function (err, imports) {
- if (err) {
- return callback(err);
- }
-
- callback(null, { paths: paths, imports: imports });
- });
+ ], callback);
}
CSS.buildBundle = function (target, fork, callback) {
diff --git a/src/middleware/header.js b/src/middleware/header.js
index d5c3d53e6f..fe95b11a27 100644
--- a/src/middleware/header.js
+++ b/src/middleware/header.js
@@ -9,6 +9,7 @@ var meta = require('../meta');
var plugins = require('../plugins');
var navigation = require('../navigation');
var translator = require('../translator');
+var utils = require('../utils');
var controllers = {
api: require('../controllers/api'),
@@ -152,6 +153,8 @@ module.exports = function (middleware) {
return { src: script };
});
+ addTimeagoLocaleScript(templateValues.scripts, res.locals.config.userLang);
+
if (req.route && req.route.path === '/') {
modifyTitle(templateValues);
}
@@ -168,6 +171,11 @@ module.exports = function (middleware) {
], callback);
};
+ function addTimeagoLocaleScript(scripts, userLang) {
+ var languageCode = utils.userLangToTimeagoCode(userLang);
+ scripts.push({ src: nconf.get('relative_path') + '/assets/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '.js' });
+ }
+
middleware.renderFooter = function (req, res, data, callback) {
async.waterfall([
function (next) {
diff --git a/src/middleware/headers.js b/src/middleware/headers.js
index 190de28b0e..eb11ff8718 100644
--- a/src/middleware/headers.js
+++ b/src/middleware/headers.js
@@ -23,17 +23,5 @@ module.exports = function (middleware) {
next();
};
-
- middleware.addExpiresHeaders = function (req, res, next) {
- if (req.app.enabled('cache')) {
- res.setHeader('Cache-Control', 'public, max-age=5184000');
- res.setHeader('Expires', new Date(Date.now() + 5184000000).toUTCString());
- } else {
- res.setHeader('Cache-Control', 'public, max-age=0');
- res.setHeader('Expires', new Date().toUTCString());
- }
-
- next();
- };
};
diff --git a/src/middleware/render.js b/src/middleware/render.js
index e37b994445..bf3cb03638 100644
--- a/src/middleware/render.js
+++ b/src/middleware/render.js
@@ -7,6 +7,7 @@ var winston = require('winston');
var plugins = require('../plugins');
var translator = require('../translator');
+var widgets = require('../widgets');
module.exports = function (middleware) {
middleware.processRender = function (req, res, next) {
@@ -49,6 +50,17 @@ module.exports = function (middleware) {
function (data, next) {
options = data.templateData;
+ widgets.render(req.uid, {
+ template: template + '.tpl',
+ url: options.url,
+ templateData: options,
+ req: req,
+ res: res,
+ }, next);
+ },
+ function (data, next) {
+ options.widgets = data;
+
res.locals.template = template;
options._locals = undefined;
diff --git a/src/routes/api.js b/src/routes/api.js
index 9b5a7f77c7..34f14f5a8c 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -9,7 +9,6 @@ module.exports = function (app, middleware, controllers) {
app.use('/api', router);
router.get('/config', middleware.applyCSRF, controllers.api.getConfig);
- router.get('/widgets/render', controllers.api.renderWidgets);
router.get('/me', middleware.checkGlobalPrivacySettings, controllers.user.getCurrentUser);
router.get('/user/uid/:uid', middleware.checkGlobalPrivacySettings, controllers.user.getUserByUID);
diff --git a/src/routes/debug.js b/src/routes/debug.js
index 2ec3f23934..460534fcdc 100644
--- a/src/routes/debug.js
+++ b/src/routes/debug.js
@@ -2,77 +2,10 @@
var express = require('express');
var nconf = require('nconf');
-var winston = require('winston');
-var user = require('../user');
-var categories = require('../categories');
-var topics = require('../topics');
-var posts = require('../posts');
module.exports = function (app) {
var router = express.Router();
- router.get('/uid/:uid', function (req, res) {
- if (!req.params.uid) {
- return res.redirect('/404');
- }
-
- user.getUserData(req.params.uid, function (err, data) {
- if (err) {
- winston.error(err);
- }
-
- if (data) {
- res.send(data);
- } else {
- res.status(404).json({
- error: "User doesn't exist!",
- });
- }
- });
- });
-
- router.get('/cid/:cid', function (req, res) {
- categories.getCategoryData(req.params.cid, function (err, data) {
- if (err) {
- winston.error(err);
- }
-
- if (data) {
- res.send(data);
- } else {
- res.status(404).send("Category doesn't exist!");
- }
- });
- });
-
- router.get('/tid/:tid', function (req, res) {
- topics.getTopicData(req.params.tid, function (err, data) {
- if (err) {
- winston.error(err);
- }
-
- if (data) {
- res.send(data);
- } else {
- res.status(404).send("Topic doesn't exist!");
- }
- });
- });
-
- router.get('/pid/:pid', function (req, res) {
- posts.getPostData(req.params.pid, function (err, data) {
- if (err) {
- winston.error(err);
- }
-
- if (data) {
- res.send(data);
- } else {
- res.status(404).send("Post doesn't exist!");
- }
- });
- });
-
router.get('/test', function (req, res) {
res.redirect(404);
});
diff --git a/src/routes/feeds.js b/src/routes/feeds.js
index 3884c76a50..4cfa52c373 100644
--- a/src/routes/feeds.js
+++ b/src/routes/feeds.js
@@ -12,6 +12,7 @@ var categories = require('../categories');
var meta = require('../meta');
var helpers = require('../controllers/helpers');
var privileges = require('../privileges');
+var db = require('../database');
var controllers404 = require('../controllers/404.js');
module.exports = function (app, middleware) {
@@ -40,7 +41,7 @@ function validateTokenIfRequiresLogin(requiresLogin, cid, req, res, callback) {
async.waterfall([
function (next) {
- user.getUserField(uid, 'rss_token', next);
+ db.getObjectField('user:' + uid, 'rss_token', next);
},
function (_token, next) {
if (token === _token) {
diff --git a/src/topics/bookmarks.js b/src/topics/bookmarks.js
index b47d5f2278..975a0d54f8 100644
--- a/src/topics/bookmarks.js
+++ b/src/topics/bookmarks.js
@@ -8,6 +8,9 @@ var posts = require('../posts');
module.exports = function (Topics) {
Topics.getUserBookmark = function (tid, uid, callback) {
+ if (!parseInt(uid, 10)) {
+ return callback(null, null);
+ }
db.sortedSetScore('tid:' + tid + ':bookmarks', uid, callback);
};
diff --git a/src/topics/posts.js b/src/topics/posts.js
index 8377215609..ebfbd106c0 100644
--- a/src/topics/posts.js
+++ b/src/topics/posts.js
@@ -169,11 +169,9 @@ module.exports = function (Topics) {
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;
- });
+ var parentUids = _.uniq(parentPosts.map(function (postObj) {
+ return postObj && parseInt(postObj.uid, 10);
+ }));
user.getUsersFields(parentUids, ['username'], next);
},
@@ -391,59 +389,70 @@ module.exports = function (Topics) {
};
function getPostReplies(pids, callerUid, callback) {
- async.map(pids, function (pid, next) {
- var replyPids;
- var uids = [];
- async.waterfall([
- function (next) {
- db.getSortedSetRange('pid:' + pid + ':replies', 0, -1, next);
- },
- function (_replyPids, next) {
- replyPids = _replyPids;
-
- var count = 0;
-
- async.until(function () {
- return count === replyPids.length || uids.length === 6;
- }, function (next) {
- async.waterfall([
- function (next) {
- posts.getPostField(replyPids[count], 'uid', next);
- },
- function (uid, next) {
- uid = parseInt(uid, 10);
- if (uids.indexOf(uid) === -1) {
- uids.push(uid);
- }
- count += 1;
- next();
- },
- ], next);
- }, next);
- },
- function (next) {
- async.parallel({
- users: function (next) {
- user.getUsersWithFields(uids, ['uid', 'username', 'userslug', 'picture'], callerUid, next);
- },
- timestampISO: function (next) {
- posts.getPostField(replyPids[0], 'timestamp', function (err, timestamp) {
- next(err, utils.toISOString(timestamp));
- });
- },
- }, next);
- },
- function (replies, next) {
- if (replies.users.length > 5) {
- replies.users.shift();
- replies.hasMore = true;
+ var arrayOfReplyPids;
+ var replyData;
+ var uniqueUids;
+ var uniquePids;
+ async.waterfall([
+ function (next) {
+ var keys = pids.map(function (pid) {
+ return 'pid:' + pid + ':replies';
+ });
+ db.getSortedSetsMembers(keys, next);
+ },
+ function (arrayOfPids, next) {
+ arrayOfReplyPids = arrayOfPids;
+
+ uniquePids = _.uniq(_.flatten(arrayOfPids));
+
+ posts.getPostsFields(uniquePids, ['pid', 'uid', 'timestamp'], next);
+ },
+ function (_replyData, next) {
+ replyData = _replyData;
+ var uids = replyData.map(function (replyData) {
+ return replyData && replyData.uid;
+ });
+
+ uniqueUids = _.uniq(uids);
+
+ user.getUsersWithFields(uniqueUids, ['uid', 'username', 'userslug', 'picture'], callerUid, next);
+ },
+ function (userData, next) {
+ var uidMap = _.zipObject(uniqueUids, userData);
+ var pidMap = _.zipObject(uniquePids, replyData);
+
+ var returnData = arrayOfReplyPids.map(function (replyPids) {
+ var uidsUsed = {};
+ var currentData = {
+ hasMore: false,
+ users: [],
+ text: replyPids.length > 1 ? '[[topic:replies_to_this_post, ' + replyPids.length + ']]' : '[[topic:one_reply_to_this_post]]',
+ count: replyPids.length,
+ timestampISO: replyPids.length ? utils.toISOString(pidMap[replyPids[0]].timestamp) : undefined,
+ };
+
+ replyPids.sort(function (a, b) {
+ return parseInt(a, 10) - parseInt(b, 10);
+ });
+
+ replyPids.forEach(function (replyPid) {
+ var replyData = pidMap[replyPid];
+ if (!uidsUsed[replyData.uid] && currentData.users.length < 6) {
+ currentData.users.push(uidMap[replyData.uid]);
+ uidsUsed[replyData.uid] = true;
+ }
+ });
+
+ if (currentData.users.length > 5) {
+ currentData.users.pop();
+ currentData.hasMore = true;
}
- replies.count = replyPids.length;
- replies.text = replies.count > 1 ? '[[topic:replies_to_this_post, ' + replies.count + ']]' : '[[topic:one_reply_to_this_post]]';
- next(null, replies);
- },
- ], next);
- }, callback);
+ return currentData;
+ });
+
+ next(null, returnData);
+ },
+ ], callback);
}
};
diff --git a/src/topics/recent.js b/src/topics/recent.js
index 7190c0e36b..135a15859b 100644
--- a/src/topics/recent.js
+++ b/src/topics/recent.js
@@ -95,7 +95,7 @@ module.exports = function (Topics) {
], callback);
}
-
+ /* not an orphan method, used in widget-essentials */
Topics.getLatestTopics = function (uid, start, stop, term, callback) {
async.waterfall([
function (next) {
diff --git a/src/upgrades/1.5.2/rss_token_wipe.js b/src/upgrades/1.5.2/rss_token_wipe.js
new file mode 100644
index 0000000000..2bfa9cd02b
--- /dev/null
+++ b/src/upgrades/1.5.2/rss_token_wipe.js
@@ -0,0 +1,22 @@
+'use strict';
+
+var async = require('async');
+var batch = require('../../batch');
+var db = require('../../database');
+
+module.exports = {
+ name: 'Wipe all existing RSS tokens',
+ timestamp: Date.UTC(2017, 6, 5),
+ method: function (callback) {
+ var progress = this.progress;
+
+ batch.processSortedSet('users:joindate', function (uids, next) {
+ async.eachLimit(uids, 500, function (uid, next) {
+ progress.incr();
+ db.deleteObjectField('user:' + uid, 'rss_token', next);
+ }, next);
+ }, {
+ progress: progress,
+ }, callback);
+ },
+};
diff --git a/src/upgrades/1.6.0/robots-config-change.js b/src/upgrades/1.6.0/robots-config-change.js
new file mode 100644
index 0000000000..3a18872bbc
--- /dev/null
+++ b/src/upgrades/1.6.0/robots-config-change.js
@@ -0,0 +1,35 @@
+'use strict';
+
+var async = require('async');
+var db = require('../../database');
+
+module.exports = {
+ name: 'Fix incorrect robots.txt schema',
+ timestamp: Date.UTC(2017, 6, 10),
+ method: function (callback) {
+ async.waterfall([
+ function (next) {
+ db.getObject('config', next);
+ },
+ function (config, next) {
+ if (!config) {
+ return callback();
+ }
+ // fix mongo nested data
+ if (config.robots && config.robots.txt) {
+ db.setObjectField('config', 'robots:txt', config.robots.txt, next);
+ } else if (typeof config['robots.txt'] === 'string' && config['robots.txt']) {
+ db.setObjectField('config', 'robots:txt', config['robots.txt'], next);
+ } else {
+ next();
+ }
+ },
+ function (next) {
+ db.deleteObjectField('config', 'robots', next);
+ },
+ function (next) {
+ db.deleteObjectField('config', 'robots.txt', next);
+ },
+ ], callback);
+ },
+};
diff --git a/src/user.js b/src/user.js
index db8f9774ed..db265f76bc 100644
--- a/src/user.js
+++ b/src/user.js
@@ -78,12 +78,24 @@ User.getUsersWithFields = function (uids, fields, uid, callback) {
function (results, next) {
results.userData.forEach(function (user, index) {
if (user) {
- user.status = User.getStatus(user);
user.administrator = results.isAdmin[index];
- user.banned = parseInt(user.banned, 10) === 1;
- user.banned_until = parseInt(user['banned:expire'], 10) || 0;
- user.banned_until_readable = user.banned_until ? new Date(user.banned_until).toString() : 'Not Banned';
- user['email:confirmed'] = parseInt(user['email:confirmed'], 10) === 1;
+
+ if (user.hasOwnProperty('status')) {
+ user.status = User.getStatus(user);
+ }
+
+ if (user.hasOwnProperty('banned')) {
+ user.banned = parseInt(user.banned, 10) === 1;
+ }
+
+ if (user.hasOwnProperty('banned:expire')) {
+ user.banned_until = parseInt(user['banned:expire'], 10) || 0;
+ user.banned_until_readable = user.banned_until ? new Date(user.banned_until).toString() : 'Not Banned';
+ }
+
+ if (user.hasOwnProperty(['email:confirmed'])) {
+ user['email:confirmed'] = parseInt(user['email:confirmed'], 10) === 1;
+ }
}
});
plugins.fireHook('filter:userlist.get', { users: results.userData, uid: uid }, next);
@@ -277,12 +289,30 @@ User.getModeratorUids = function (callback) {
async.waterfall([
async.apply(db.getSortedSetRange, 'categories:cid', 0, -1),
function (cids, next) {
- var groupNames = cids.map(function (cid) {
- return 'cid:' + cid + ':privileges:moderate';
- });
+ var groupNames = cids.reduce(function (memo, cid) {
+ memo.push('cid:' + cid + ':privileges:moderate');
+ memo.push('cid:' + cid + ':privileges:groups:moderate');
+ return memo;
+ }, []);
groups.getMembersOfGroups(groupNames, next);
},
+ function (memberSets, next) {
+ // Every other set is actually a list of user groups, not uids, so convert those to members
+ var sets = memberSets.reduce(function (memo, set, idx) {
+ if (idx % 2) {
+ memo.working.push(set);
+ } else {
+ memo.regular.push(set);
+ }
+
+ return memo;
+ }, { working: [], regular: [] });
+
+ groups.getMembersOfGroups(sets.working, function (err, memberSets) {
+ next(err, sets.regular.concat(memberSets || []));
+ });
+ },
function (memberSets, next) {
next(null, _.union.apply(_, memberSets));
},
diff --git a/src/user/auth.js b/src/user/auth.js
index 4347c860d1..1fbc316f18 100644
--- a/src/user/auth.js
+++ b/src/user/auth.js
@@ -55,7 +55,7 @@ module.exports = function (User) {
var token;
async.waterfall([
function (next) {
- User.getUserField(uid, 'rss_token', next);
+ db.getObjectField('user:' + uid, 'rss_token', next);
},
function (_token, next) {
token = _token || utils.generateUUID();
diff --git a/src/user/data.js b/src/user/data.js
index 1342c4c657..3c79a9095f 100644
--- a/src/user/data.js
+++ b/src/user/data.js
@@ -122,6 +122,10 @@ module.exports = function (User) {
user.password = undefined;
}
+ if (user.rss_token) {
+ user.rss_token = undefined;
+ }
+
if (!parseInt(user.uid, 10)) {
user.uid = 0;
user.username = '[[global:guest]]';
diff --git a/src/user/digest.js b/src/user/digest.js
index b9cb689622..61b727de3a 100644
--- a/src/user/digest.js
+++ b/src/user/digest.js
@@ -23,44 +23,31 @@ Digest.execute = function (payload, callback) {
return callback();
}
- var subscribers;
async.waterfall([
function (next) {
- async.parallel({
- topics: async.apply(topics.getLatestTopics, 0, 0, 9, payload.interval),
- subscribers: function (next) {
- if (payload.subscribers) {
- setImmediate(next, undefined, payload.subscribers);
- } else {
- Digest.getSubscribers(payload.interval, next);
- }
- },
- }, next);
+ if (payload.subscribers) {
+ setImmediate(next, undefined, payload.subscribers);
+ } else {
+ Digest.getSubscribers(payload.interval, next);
+ }
},
- function (data, next) {
- subscribers = data.subscribers;
- if (!data.subscribers.length) {
+ function (subscribers, next) {
+ if (!subscribers.length) {
return callback();
}
- // Fix relative paths in topic data
- data.topics.topics = data.topics.topics.map(function (topicObj) {
- var user = topicObj.hasOwnProperty('teaser') && topicObj.teaser !== undefined ? topicObj.teaser.user : topicObj.user;
- if (user && user.picture && utils.isRelativeUrl(user.picture)) {
- user.picture = nconf.get('base_url') + user.picture;
- }
-
- return topicObj;
- });
+ var data = {
+ interval: payload.interval,
+ subscribers: subscribers,
+ };
- data.interval = payload.interval;
Digest.send(data, next);
},
- ], function (err) {
+ ], function (err, count) {
if (err) {
winston.error('[user/jobs] Could not send digests (' + payload.interval + '): ' + err.message);
} else {
- winston.info('[user/jobs] Digest (' + payload.interval + ') scheduling completed. ' + subscribers.length + ' email(s) sent.');
+ winston.info('[user/jobs] Digest (' + payload.interval + ') scheduling completed. ' + count + ' email(s) sent.');
}
callback(err);
@@ -116,12 +103,16 @@ Digest.send = function (data, callback) {
async.eachLimit(users, 100, function (userObj, next) {
async.waterfall([
function (next) {
- user.notifications.getDailyUnread(userObj.uid, next);
+ async.parallel({
+ notifications: async.apply(user.notifications.getDailyUnread, userObj.uid),
+ topics: async.apply(topics.getPopular, data.interval, userObj.uid, 10),
+ }, next);
},
- function (notifications, next) {
- notifications = notifications.filter(Boolean);
+ function (data, next) {
+ var notifications = data.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) {
+ if (!notifications.length && !data.topics.length) {
return next();
}
@@ -131,6 +122,16 @@ Digest.send = function (data, callback) {
}
});
+ // Fix relative paths in topic data
+ data.topics = data.topics.map(function (topicObj) {
+ var user = topicObj.hasOwnProperty('teaser') && topicObj.teaser !== undefined ? topicObj.teaser.user : topicObj.user;
+ if (user && user.picture && utils.isRelativeUrl(user.picture)) {
+ user.picture = nconf.get('base_url') + user.picture;
+ }
+
+ return topicObj;
+ });
+
emailer.send('digest', userObj.uid, {
subject: '[' + meta.config.title + '] [[email:digest.subject, ' + (now.getFullYear() + '/' + (now.getMonth() + 1) + '/' + now.getDate()) + ']]',
username: userObj.username,
@@ -138,7 +139,7 @@ Digest.send = function (data, callback) {
url: nconf.get('url'),
site_title: meta.config.title || meta.config.browserTitle || 'NodeBB',
notifications: notifications,
- recent: data.topics.topics,
+ recent: data.topics,
interval: data.interval,
});
next();
@@ -147,6 +148,6 @@ Digest.send = function (data, callback) {
}, next);
},
], function (err) {
- callback(err);
+ callback(err, data.subscribers.length);
});
};
diff --git a/src/views/admin/extend/widgets.tpl b/src/views/admin/extend/widgets.tpl
index 07b8b5c193..0e2d2a848c 100644
--- a/src/views/admin/extend/widgets.tpl
+++ b/src/views/admin/extend/widgets.tpl
@@ -32,32 +32,32 @@
diff --git a/src/views/admin/partials/categories/category-rows.tpl b/src/views/admin/partials/categories/category-rows.tpl
index 1ceeefb9d4..c4bfd008a5 100644
--- a/src/views/admin/partials/categories/category-rows.tpl
+++ b/src/views/admin/partials/categories/category-rows.tpl
@@ -4,8 +4,11 @@
+
+
+
-
{categories.descriptionParsed}
diff --git a/src/views/admin/settings/web-crawler.tpl b/src/views/admin/settings/web-crawler.tpl
index 276a4fb0aa..45f7156ed9 100644
--- a/src/views/admin/settings/web-crawler.tpl
+++ b/src/views/admin/settings/web-crawler.tpl
@@ -5,7 +5,7 @@
diff --git a/src/widgets/admin.js b/src/widgets/admin.js
index dde3aca43d..4ca05571f5 100644
--- a/src/widgets/admin.js
+++ b/src/widgets/admin.js
@@ -72,7 +72,7 @@ admin.get = function (callback) {
callback(false, {
templates: templates,
areas: widgetData.areas,
- widgets: widgetData.widgets,
+ availableWidgets: widgetData.widgets,
});
});
});
diff --git a/src/widgets/index.js b/src/widgets/index.js
index 2e228dab63..982b40c696 100644
--- a/src/widgets/index.js
+++ b/src/widgets/index.js
@@ -3,27 +3,35 @@
var async = require('async');
var winston = require('winston');
var templates = require('templates.js');
+var _ = require('lodash');
var plugins = require('../plugins');
var translator = require('../translator');
var db = require('../database');
+var apiController = require('../controllers/api');
var widgets = module.exports;
-widgets.render = function (uid, area, req, res, callback) {
- if (!area.locations || !area.template) {
+widgets.render = function (uid, options, callback) {
+ if (!options.template) {
return callback(new Error('[[error:invalid-data]]'));
}
async.waterfall([
function (next) {
- widgets.getAreas(['global', area.template], area.locations, next);
+ widgets.getWidgetDataForTemplates(['global', options.template], next);
},
function (data, next) {
var widgetsByLocation = {};
- async.map(area.locations, function (location, done) {
- widgetsByLocation[location] = data.global[location].concat(data[area.template][location]);
+ delete data.global.drafts;
+
+ var locations = _.uniq(Object.keys(data.global).concat(Object.keys(data[options.template])));
+
+ var returnData = {};
+
+ async.each(locations, function (location, done) {
+ widgetsByLocation[location] = (data.global[location] || []).concat(data[options.template][location] || []);
if (!widgetsByLocation[location].length) {
return done(null, { location: location, widgets: [] });
@@ -33,28 +41,43 @@ widgets.render = function (uid, area, req, res, callback) {
if (!widget || !widget.data ||
(!!widget.data['hide-registered'] && uid !== 0) ||
(!!widget.data['hide-guests'] && uid === 0) ||
- (!!widget.data['hide-mobile'] && area.isMobile)) {
+ (!!widget.data['hide-mobile'] && options.req.useragent.isMobile)) {
return next();
}
- renderWidget(widget, uid, area, req, res, next);
- }, function (err, result) {
- done(err, { location: location, widgets: result.filter(Boolean) });
+ renderWidget(widget, uid, options, next);
+ }, function (err, renderedWidgets) {
+ if (err) {
+ return done(err);
+ }
+ returnData[location] = renderedWidgets.filter(Boolean);
+ done();
});
- }, next);
+ }, function (err) {
+ next(err, returnData);
+ });
},
], callback);
};
-function renderWidget(widget, uid, area, req, res, callback) {
+function renderWidget(widget, uid, options, callback) {
async.waterfall([
function (next) {
+ if (options.res.locals.isAPI) {
+ apiController.loadConfig(options.req, next);
+ } else {
+ next(null, options.res.locals.config);
+ }
+ },
+ function (config, next) {
+ var templateData = _.assign(options.templateData, { config: config });
plugins.fireHook('filter:widget.render:' + widget.widget, {
uid: uid,
- area: area,
+ area: options,
+ templateData: templateData,
data: widget.data,
- req: req,
- res: res,
+ req: options.req,
+ res: options.res,
}, next);
},
function (data, next) {
@@ -84,23 +107,28 @@ function renderWidget(widget, uid, area, req, res, callback) {
], callback);
}
-widgets.getAreas = function (templates, locations, callback) {
+widgets.getWidgetDataForTemplates = function (templates, callback) {
var keys = templates.map(function (tpl) {
return 'widgets:' + tpl;
});
+
async.waterfall([
function (next) {
- db.getObjectsFields(keys, locations, next);
+ db.getObjects(keys, next);
},
function (data, next) {
var returnData = {};
templates.forEach(function (template, index) {
returnData[template] = returnData[template] || {};
+
+ var templateWidgetData = data[index] || {};
+ var locations = Object.keys(templateWidgetData);
+
locations.forEach(function (location) {
- if (data && data[index] && data[index][location]) {
+ if (templateWidgetData && templateWidgetData[location]) {
try {
- returnData[template][location] = JSON.parse(data[index][location]);
+ returnData[template][location] = JSON.parse(templateWidgetData[location]);
} catch (err) {
winston.error('can not parse widget data. template: ' + template + ' location: ' + location);
returnData[template][location] = [];
diff --git a/test/controllers.js b/test/controllers.js
index 34c515fc7a..6a3882ea5e 100644
--- a/test/controllers.js
+++ b/test/controllers.js
@@ -687,22 +687,23 @@ describe('Controllers', function () {
], done);
});
- it('should return {} if there is no template or locations', function (done) {
- request(nconf.get('url') + '/api/widgets/render', { json: true }, function (err, res, body) {
+ it('should return {} if there are no widgets', function (done) {
+ request(nconf.get('url') + '/api/category/' + cid, { json: true }, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
- assert(body);
- assert.equal(Object.keys(body), 0);
+ assert(body.widgets);
+ assert.equal(Object.keys(body.widgets), 0);
done();
});
});
it('should render templates', function (done) {
- var url = nconf.get('url') + '/api/widgets/render?template=categories.tpl&url=&isMobile=false&locations%5B%5D=sidebar&locations%5B%5D=footer&locations%5B%5D=header';
+ var url = nconf.get('url') + '/api/categories';
request(url, { json: true }, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
- assert(body);
+ assert(body.widgets);
+ assert(body.widgets.sidebar);
done();
});
});
diff --git a/test/user.js b/test/user.js
index d7118e4a0b..a7204b01ca 100644
--- a/test/user.js
+++ b/test/user.js
@@ -155,6 +155,51 @@ describe('User', function () {
});
});
+ describe('.getModeratorUids()', function () {
+ before(function (done) {
+ groups.join('cid:1:privileges:moderate', 1, done);
+ });
+
+ it('should retrieve all users with moderator bit in category privilege', function (done) {
+ User.getModeratorUids(function (err, uids) {
+ assert.ifError(err);
+ assert.strictEqual(1, uids.length);
+ assert.strictEqual(1, parseInt(uids[0], 10));
+ done();
+ });
+ });
+
+ after(function (done) {
+ groups.leave('cid:1:privileges:moderate', 1, done);
+ });
+ });
+
+ describe('.getModeratorUids()', function () {
+ before(function (done) {
+ async.series([
+ async.apply(groups.create, { name: 'testGroup' }),
+ async.apply(groups.join, 'cid:1:privileges:groups:moderate', 'testGroup'),
+ async.apply(groups.join, 'testGroup', 1),
+ ], done);
+ });
+
+ it('should retrieve all users with moderator bit in category privilege', function (done) {
+ User.getModeratorUids(function (err, uids) {
+ assert.ifError(err);
+ assert.strictEqual(1, uids.length);
+ assert.strictEqual(1, parseInt(uids[0], 10));
+ done();
+ });
+ });
+
+ after(function (done) {
+ async.series([
+ async.apply(groups.leave, 'cid:1:privileges:groups:moderate', 'testGroup'),
+ async.apply(groups.destroy, 'testGroup'),
+ ], done);
+ });
+ });
+
describe('.isReadyToPost()', function () {
it('should error when a user makes two posts in quick succession', function (done) {
Meta.config = Meta.config || {};