Merge branch 'master' into search_hooks

v1.18.x
Julian Lam 11 years ago
commit 0e26fdf1da

@ -39,9 +39,10 @@
"validator": "~3.2.1",
"nodebb-plugin-mentions": "~0.4",
"nodebb-plugin-markdown": "~0.3",
"nodebb-theme-vanilla": "~0.0.13",
"nodebb-theme-cerulean": "~0.0.12",
"nodebb-theme-lavender": "~0.0",
"nodebb-widget-essentials": "~0.0",
"nodebb-theme-vanilla": "~0.0.14",
"nodebb-theme-cerulean": "~0.0.13",
"nodebb-theme-lavender": "~0.0.21",
"cron": "~1.0.1",
"semver": "~2.2.1",
"string": "~1.7.0",

@ -1,9 +1,6 @@
{
"new_topic_button": "New Topic",
"no_topics": "<strong>There are no topics in this category.</strong><br />Why don't you try posting one?",
"sidebar.recent_replies": "Recent Replies",
"sidebar.active_participants": "Active Participants",
"sidebar.moderators": "Moderators",
"posts": "posts",
"views": "views",
"posted": "posted",

@ -8,6 +8,7 @@
"user.edit": "Editing \"%1\"",
"user.following": "People %1 Follows",
"user.followers": "People who Follow %1",
"user.posts": "Posts made by %1",
"user.favourites": "%1's Favourite Posts",
"user.settings": "User Settings"
}

@ -93,7 +93,7 @@ var ajaxify = {};
translator.load(tpl_url);
jQuery('#footer, #content').removeClass('hide').addClass('ajaxifying');
$('#footer, #content').removeClass('hide').addClass('ajaxifying');
templates.flush();
templates.load_template(function () {
@ -110,11 +110,31 @@ var ajaxify = {};
app.processPage();
jQuery('#content, #footer').stop(true, true).removeClass('ajaxifying');
ajaxify.initialLoad = false;
app.refreshTitle(url);
$(window).trigger('action:ajaxify.end', { url: url });
var widgetLocations = [];
require(['vendor/async'], function(async) {
$('#content [widget-area]').each(function() {
widgetLocations.push(this.getAttribute('widget-area'));
});
async.each(widgetLocations, function(location, next) {
var area = $('#content [widget-area="' + location + '"]');
socket.emit('widgets.render', {template: tpl_url + '.tpl', url: url, location: location}, function(err, renderedWidgets) {
area.html(templates.prepare(area.html()).parse({
widgets: renderedWidgets
})).removeClass('hidden');
next(err);
});
}, function(err) {
$('#content, #footer').stop(true, true).removeClass('ajaxifying');
ajaxify.initialLoad = false;
app.refreshTitle(url);
$(window).trigger('action:ajaxify.end', { url: url });
});
});
}, url);
return true;

@ -15,6 +15,9 @@ var socket,
url: RELATIVE_PATH + '/api/config',
success: function (data) {
config = data;
exposeConfigToTemplates();
if(socket) {
socket.disconnect();
setTimeout(function() {
@ -591,13 +594,17 @@ var socket,
});
createHeaderTooltips();
templates.setGlobal('relative_path', RELATIVE_PATH);
templates.setGlobal('usePagination', config.usePagination);
templates.setGlobal('topicsPerPage', config.topicsPerPage);
templates.setGlobal('postsPerPage', config.postsPerPage);
});
function exposeConfigToTemplates() {
$(document).ready(function() {
templates.setGlobal('relative_path', RELATIVE_PATH);
for(var key in config) {
templates.setGlobal('config.' + key, config[key]);
}
});
}
function createHeaderTooltips() {
$('#header-menu li i[title]').each(function() {
$(this).parents('a').tooltip({

@ -102,6 +102,8 @@ define(['forum/admin/settings'], function(Settings) {
tabIndent.config.tab = ' ';
tabIndent.render(customCSSEl);
Themes.prepareWidgets();
Settings.prepare();
}
@ -132,5 +134,155 @@ define(['forum/admin/settings'], function(Settings) {
themeContainer.appendChild(themeFrag);
}
Themes.prepareWidgets = function() {
$('#widgets .available-widgets .panel').draggable({
helper: function(e) {
return $(e.target).parents('.panel').clone().addClass('block').width($(e.target.parentNode).width());
},
connectToSortable: ".widget-area"
});
$('#widgets .available-containers .containers > [data-container-html]').draggable({
helper: function(e) {
var target = $(e.target);
target = target.attr('data-container-html') ? target : target.parents('[data-container-html]');
return target.clone().addClass('block').width(target.width()).css('opacity', '0.5');
}
});
function appendToggle(el) {
if (!el.hasClass('block')) {
el.addClass('block')
.droppable({
drop: function(event, ui) {
var el = $(this);
el.find('.panel-body .container-html').val(ui.draggable.attr('data-container-html'));
el.find('.panel-body').removeClass('hidden');
},
hoverClass: "panel-info"
})
.children('.panel-heading')
.append('<div class="pull-right pointer"><span class="delete-widget"><i class="fa fa-times-circle"></i></span>&nbsp;<span class="toggle-widget"><i class="fa fa-chevron-down"></i></span></div>')
.children('small').html('');
}
}
$('#widgets .widget-area').sortable({
update: function (event, ui) {
appendToggle(ui.item);
},
connectWith: "div"
}).on('click', '.toggle-widget', function() {
$(this).parents('.panel').children('.panel-body').toggleClass('hidden');
}).on('click', '.delete-widget', function() {
var panel = $(this).parents('.panel');
bootbox.confirm('Are you sure you wish to delete this widget?', function(confirm) {
if (confirm) {
panel.remove();
}
});
});
$('#widgets .btn[data-template]').on('click', function() {
var btn = $(this),
template = btn.attr('data-template'),
location = btn.attr('data-location'),
area = btn.parents('.area').children('.widget-area'),
widgets = [];
area.find('.panel[data-widget]').each(function() {
var widgetData = {},
data = $(this).find('form').serializeArray();
for (var d in data) {
if (data.hasOwnProperty(d)) {
if (data[d].name) {
widgetData[data[d].name] = data[d].value;
}
}
}
widgets.push({
widget: this.getAttribute('data-widget'),
data: widgetData
});
});
socket.emit('admin.widgets.set', {
template: template,
location: location,
widgets: widgets
}, function(err) {
app.alert({
alert_id: 'admin:widgets',
type: err ? 'danger' : 'success',
title: err ? 'Error' : 'Widgets Updated',
message: err ? err : 'Successfully updated widgets',
timeout: 2500
});
});
});
function populateWidget(widget, data) {
widget.find('input, textarea').each(function() {
var input = $(this),
value = data[input.attr('name')];
if (this.type === 'checkbox') {
input.attr('checked', !!value);
} else {
input.val(value);
}
});
return widget;
}
$.get(RELATIVE_PATH + '/api/admin/themes', function(data) {
var areas = data.areas;
for (var a in areas) {
if (areas.hasOwnProperty(a)) {
var area = areas[a],
widgetArea = $('#widgets .area [data-template="' + area.template + '"][data-location="' + area.location + '"]').parents('.area').find('.widget-area');
for (var i in area.data) {
if (area.data.hasOwnProperty(i)) {
var data = area.data[i],
widgetEl = $('.available-widgets [data-widget="' + data.widget + '"]').clone();
widgetArea.append(populateWidget(widgetEl, data.data));
appendToggle(widgetEl);
}
}
}
}
});
$('.color-selector').on('click', '.btn', function() {
var btn = $(this),
selector = btn.parents('.color-selector'),
container = selector.parents('[data-container-html]'),
classList = [];
selector.children().each(function() {
classList.push($(this).attr('data-class'));
});
container
.removeClass(classList.join(' '))
.addClass(btn.attr('data-class'));
container.attr('data-container-html', container.attr('data-container-html')
.replace(/class="[a-zA-Z0-9-\s]+"/, 'class="' + container[0].className.replace(' pointer ui-draggable', '') + '"')
);
});
};
return Themes;
});

@ -23,14 +23,15 @@ define(function() {
elements.each(function(index, element) {
var banBtn = $(element);
var uid = getUID(banBtn);
if (isUserAdmin(banBtn) || uid === yourid)
if (isUserAdmin(banBtn) || uid === yourid) {
banBtn.addClass('disabled');
else if (isUserBanned(banBtn))
} else if (isUserBanned(banBtn)) {
banBtn.addClass('btn-warning');
else if (!isUserAdmin(banBtn))
} else if (!isUserAdmin(banBtn)) {
banBtn.removeClass('disabled');
else
} else {
banBtn.removeClass('btn-warning');
}
});
}
@ -43,86 +44,77 @@ define(function() {
if (uid === yourid) {
adminBtn.addClass('disabled');
}
}
else if (isUserBanned(adminBtn))
} else if (isUserBanned(adminBtn)) {
adminBtn.addClass('disabled');
else if (!isUserBanned(adminBtn))
} else if (!isUserBanned(adminBtn)) {
adminBtn.removeClass('disabled');
else
} else {
adminBtn.removeClass('btn-warning');
}
});
}
function initUsers() {
updateUserBanButtons(jQuery('.ban-btn'));
updateUserAdminButtons(jQuery('.admin-btn'));
function updateButtons() {
updateUserBanButtons($('.ban-btn'));
updateUserAdminButtons($('.admin-btn'));
}
$('#users-container').on('click', '.ban-btn', function() {
var banBtn = $(this);
var isAdmin = isUserAdmin(banBtn);
var isBanned = isUserBanned(banBtn);
var parent = banBtn.parents('.users-box');
var uid = getUID(banBtn);
$('#users-container').on('click', '.ban-btn', function() {
var banBtn = $(this);
var parent = banBtn.parents('.users-box');
var uid = getUID(banBtn);
if (!isAdmin) {
if (isBanned) {
socket.emit('admin.user.unbanUser', uid);
banBtn.removeClass('btn-warning');
parent.attr('data-banned', 0);
updateUserAdminButtons(jQuery('.admin-btn'));
} else {
bootbox.confirm('Do you really want to ban "' + parent.attr('data-username') + '"?', function(confirm) {
if (confirm) {
socket.emit('admin.user.banUser', uid);
banBtn.addClass('btn-warning');
parent.attr('data-banned', 1);
updateUserAdminButtons(jQuery('.admin-btn'));
}
});
}
if (!isUserAdmin(banBtn)) {
if (isUserBanned(banBtn)) {
socket.emit('admin.user.unbanUser', uid);
banBtn.removeClass('btn-warning');
parent.attr('data-banned', 0);
updateUserAdminButtons($('.admin-btn'));
} else {
bootbox.confirm('Do you really want to ban "' + parent.attr('data-username') + '"?', function(confirm) {
if (confirm) {
socket.emit('admin.user.banUser', uid);
banBtn.addClass('btn-warning');
parent.attr('data-banned', 1);
updateUserAdminButtons($('.admin-btn'));
}
});
}
}
return false;
});
$('#users-container').on('click', '.admin-btn', function() {
var adminBtn = $(this);
var isAdmin = isUserAdmin(adminBtn);
var parent = adminBtn.parents('.users-box');
var isBanned = isUserBanned(adminBtn);
var uid = getUID(adminBtn);
return false;
});
if(uid === yourid){
app.alert({
title: 'Error',
message: 'You can\'t remove yourself as Administrator!',
type: 'danger',
timeout: 5000
});
}
else if (!isAdmin) {
socket.emit('admin.user.makeAdmin', uid);
adminBtn.attr('value', 'UnMake Admin').html('Remove Admin');
parent.attr('data-admin', 1);
updateUserBanButtons(jQuery('.ban-btn'));
} else if(uid !== yourid) {
bootbox.confirm('Do you really want to remove this user as admin "' + parent.attr('data-username') + '"?', function(confirm) {
if (confirm) {
socket.emit('admin.user.removeAdmin', uid);
adminBtn.attr('value', 'Make Admin').html('Make Admin');
parent.attr('data-admin', 0);
updateUserBanButtons(jQuery('.ban-btn'));
}
});
$('#users-container').on('click', '.admin-btn', function() {
var adminBtn = $(this);
var parent = adminBtn.parents('.users-box');
var uid = getUID(adminBtn);
if(uid === yourid) {
app.alert({
title: 'Error',
message: 'You can\'t remove yourself as Administrator!',
type: 'danger',
timeout: 5000
});
} else if (!isUserAdmin(adminBtn)) {
socket.emit('admin.user.makeAdmin', uid);
adminBtn.attr('value', 'UnMake Admin').html('Remove Admin');
parent.attr('data-admin', 1);
updateUserBanButtons($('.ban-btn'));
} else if(uid !== yourid) {
bootbox.confirm('Do you really want to remove this user as admin "' + parent.attr('data-username') + '"?', function(confirm) {
if (confirm) {
socket.emit('admin.user.removeAdmin', uid);
adminBtn.attr('value', 'Make Admin').html('Make Admin');
parent.attr('data-admin', 0);
updateUserBanButtons($('.ban-btn'));
}
return false;
});
}
});
}
return false;
});
function handleUserCreate() {
var errorEl = $('#create-modal-error');
@ -217,12 +209,12 @@ define(function() {
.removeClass('label-danger');
}
initUsers();
updateButtons();
});
}, 250);
});
initUsers();
updateButtons();
handleUserCreate();

@ -37,8 +37,6 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
socket.on('event:new_topic', Category.onNewTopic);
socket.emit('categories.getRecentReplies', cid, renderRecentReplies);
enableInfiniteLoading();
};
@ -84,9 +82,6 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
}
topic.hide().fadeIn('slow');
socket.emit('categories.getRecentReplies', templates.get('category_id'), renderRecentReplies);
addActiveUser(data);
socket.emit('categories.getPageCount', templates.get('category_id'), function(err, newPageCount) {
pagination.recreatePaginationLinks(newPageCount);
@ -97,21 +92,6 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
});
}
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({
topics: topics
@ -159,32 +139,5 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
});
}
function renderRecentReplies(err, posts) {
if (err || !posts || posts.length === 0) {
return;
}
var recentReplies = $('#category_recent_replies');
templates.preload_template('recentreplies', function() {
templates['recentreplies'].parse({posts:[]});
var html = templates.prepare(templates['recentreplies'].blocks['posts']).parse({
posts: posts
});
translator.translate(html, function(translatedHTML) {
translatedHTML = $(translatedHTML);
translatedHTML.find('img').addClass('img-responsive');
recentReplies.html(translatedHTML);
$('#category_recent_replies span.timeago').timeago();
app.createUserTooltips();
});
});
};
return Category;
});

@ -3,33 +3,6 @@ define(function() {
home.init = function() {
ajaxify.register_events([
'user.count',
'meta.getUsageStats',
'user.getActiveUsers'
]);
socket.emit('user.count', updateUserCount);
socket.on('user.count', updateUserCount);
function updateUserCount(err, data) {
$('#stats_users').html(utils.makeNumberHumanReadable(data.count)).attr('title', data.count);
}
socket.emit('meta.getUsageStats', updateUsageStats);
socket.on('meta.getUsageStats', updateUsageStats);
function updateUsageStats(err, 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('user.getActiveUsers', updateActiveUsers);
socket.on('user.getActiveUsers', updateActiveUsers);
function updateActiveUsers(err, data) {
$('#stats_online').html(data.users);
}
}
return home;

@ -18,23 +18,23 @@ define(function() {
url: RELATIVE_PATH + '/login',
data: loginData,
success: function(data, textStatus, jqXHR) {
$('#login').html('Redirecting...');
if(!app.previousUrl) {
app.previousUrl = '/';
}
if (!data.success) {
$('#login-error-notify').show();
$('#login').removeAttr('disabled').html('Login');
if(app.previousUrl.indexOf('/reset/') !== -1) {
window.location.replace(RELATIVE_PATH + "/?loggedin");
} else {
$('#login').html('Redirecting...');
if(!app.previousUrl) {
app.previousUrl = '/';
}
if(app.previousUrl.indexOf('/reset/') != -1)
window.location.replace(RELATIVE_PATH + "/?loggedin");
else
var index = app.previousUrl.indexOf('#');
if(index !== -1) {
window.location.replace(app.previousUrl.slice(0, index) + '?loggedin' + app.previousUrl.slice(index));
} else {
window.location.replace(app.previousUrl + "?loggedin");
app.loadConfig();
}
}
app.loadConfig();
},
error: function(data, textStatus, jqXHR) {
$('#login-error-notify').show();

@ -0,0 +1,958 @@
/*global setImmediate: false, setTimeout: false, console: false */
(function () {
var async = {};
// global on the server, window in the browser
var root, previous_async;
root = this;
if (root != null) {
previous_async = root.async;
}
async.noConflict = function () {
root.async = previous_async;
return async;
};
function only_once(fn) {
var called = false;
return function() {
if (called) throw new Error("Callback was already called.");
called = true;
fn.apply(root, arguments);
}
}
//// cross-browser compatiblity functions ////
var _each = function (arr, iterator) {
if (arr.forEach) {
return arr.forEach(iterator);
}
for (var i = 0; i < arr.length; i += 1) {
iterator(arr[i], i, arr);
}
};
var _map = function (arr, iterator) {
if (arr.map) {
return arr.map(iterator);
}
var results = [];
_each(arr, function (x, i, a) {
results.push(iterator(x, i, a));
});
return results;
};
var _reduce = function (arr, iterator, memo) {
if (arr.reduce) {
return arr.reduce(iterator, memo);
}
_each(arr, function (x, i, a) {
memo = iterator(memo, x, i, a);
});
return memo;
};
var _keys = function (obj) {
if (Object.keys) {
return Object.keys(obj);
}
var keys = [];
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
keys.push(k);
}
}
return keys;
};
//// exported async module functions ////
//// nextTick implementation with browser-compatible fallback ////
if (typeof process === 'undefined' || !(process.nextTick)) {
if (typeof setImmediate === 'function') {
async.nextTick = function (fn) {
// not a direct alias for IE10 compatibility
setImmediate(fn);
};
async.setImmediate = async.nextTick;
}
else {
async.nextTick = function (fn) {
setTimeout(fn, 0);
};
async.setImmediate = async.nextTick;
}
}
else {
async.nextTick = process.nextTick;
if (typeof setImmediate !== 'undefined') {
async.setImmediate = function (fn) {
// not a direct alias for IE10 compatibility
setImmediate(fn);
};
}
else {
async.setImmediate = async.nextTick;
}
}
async.each = function (arr, iterator, callback) {
callback = callback || function () {};
if (!arr.length) {
return callback();
}
var completed = 0;
_each(arr, function (x) {
iterator(x, only_once(function (err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
if (completed >= arr.length) {
callback(null);
}
}
}));
});
};
async.forEach = async.each;
async.eachSeries = function (arr, iterator, callback) {
callback = callback || function () {};
if (!arr.length) {
return callback();
}
var completed = 0;
var iterate = function () {
iterator(arr[completed], function (err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
if (completed >= arr.length) {
callback(null);
}
else {
iterate();
}
}
});
};
iterate();
};
async.forEachSeries = async.eachSeries;
async.eachLimit = function (arr, limit, iterator, callback) {
var fn = _eachLimit(limit);
fn.apply(null, [arr, iterator, callback]);
};
async.forEachLimit = async.eachLimit;
var _eachLimit = function (limit) {
return function (arr, iterator, callback) {
callback = callback || function () {};
if (!arr.length || limit <= 0) {
return callback();
}
var completed = 0;
var started = 0;
var running = 0;
(function replenish () {
if (completed >= arr.length) {
return callback();
}
while (running < limit && started < arr.length) {
started += 1;
running += 1;
iterator(arr[started - 1], function (err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
running -= 1;
if (completed >= arr.length) {
callback();
}
else {
replenish();
}
}
});
}
})();
};
};
var doParallel = function (fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return fn.apply(null, [async.each].concat(args));
};
};
var doParallelLimit = function(limit, fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return fn.apply(null, [_eachLimit(limit)].concat(args));
};
};
var doSeries = function (fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return fn.apply(null, [async.eachSeries].concat(args));
};
};
var _asyncMap = function (eachfn, arr, iterator, callback) {
var results = [];
arr = _map(arr, function (x, i) {
return {index: i, value: x};
});
eachfn(arr, function (x, callback) {
iterator(x.value, function (err, v) {
results[x.index] = v;
callback(err);
});
}, function (err) {
callback(err, results);
});
};
async.map = doParallel(_asyncMap);
async.mapSeries = doSeries(_asyncMap);
async.mapLimit = function (arr, limit, iterator, callback) {
return _mapLimit(limit)(arr, iterator, callback);
};
var _mapLimit = function(limit) {
return doParallelLimit(limit, _asyncMap);
};
// reduce only has a series version, as doing reduce in parallel won't
// work in many situations.
async.reduce = function (arr, memo, iterator, callback) {
async.eachSeries(arr, function (x, callback) {
iterator(memo, x, function (err, v) {
memo = v;
callback(err);
});
}, function (err) {
callback(err, memo);
});
};
// inject alias
async.inject = async.reduce;
// foldl alias
async.foldl = async.reduce;
async.reduceRight = function (arr, memo, iterator, callback) {
var reversed = _map(arr, function (x) {
return x;
}).reverse();
async.reduce(reversed, memo, iterator, callback);
};
// foldr alias
async.foldr = async.reduceRight;
var _filter = function (eachfn, arr, iterator, callback) {
var results = [];
arr = _map(arr, function (x, i) {
return {index: i, value: x};
});
eachfn(arr, function (x, callback) {
iterator(x.value, function (v) {
if (v) {
results.push(x);
}
callback();
});
}, function (err) {
callback(_map(results.sort(function (a, b) {
return a.index - b.index;
}), function (x) {
return x.value;
}));
});
};
async.filter = doParallel(_filter);
async.filterSeries = doSeries(_filter);
// select alias
async.select = async.filter;
async.selectSeries = async.filterSeries;
var _reject = function (eachfn, arr, iterator, callback) {
var results = [];
arr = _map(arr, function (x, i) {
return {index: i, value: x};
});
eachfn(arr, function (x, callback) {
iterator(x.value, function (v) {
if (!v) {
results.push(x);
}
callback();
});
}, function (err) {
callback(_map(results.sort(function (a, b) {
return a.index - b.index;
}), function (x) {
return x.value;
}));
});
};
async.reject = doParallel(_reject);
async.rejectSeries = doSeries(_reject);
var _detect = function (eachfn, arr, iterator, main_callback) {
eachfn(arr, function (x, callback) {
iterator(x, function (result) {
if (result) {
main_callback(x);
main_callback = function () {};
}
else {
callback();
}
});
}, function (err) {
main_callback();
});
};
async.detect = doParallel(_detect);
async.detectSeries = doSeries(_detect);
async.some = function (arr, iterator, main_callback) {
async.each(arr, function (x, callback) {
iterator(x, function (v) {
if (v) {
main_callback(true);
main_callback = function () {};
}
callback();
});
}, function (err) {
main_callback(false);
});
};
// any alias
async.any = async.some;
async.every = function (arr, iterator, main_callback) {
async.each(arr, function (x, callback) {
iterator(x, function (v) {
if (!v) {
main_callback(false);
main_callback = function () {};
}
callback();
});
}, function (err) {
main_callback(true);
});
};
// all alias
async.all = async.every;
async.sortBy = function (arr, iterator, callback) {
async.map(arr, function (x, callback) {
iterator(x, function (err, criteria) {
if (err) {
callback(err);
}
else {
callback(null, {value: x, criteria: criteria});
}
});
}, function (err, results) {
if (err) {
return callback(err);
}
else {
var fn = function (left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
};
callback(null, _map(results.sort(fn), function (x) {
return x.value;
}));
}
});
};
async.auto = function (tasks, callback) {
callback = callback || function () {};
var keys = _keys(tasks);
if (!keys.length) {
return callback(null);
}
var results = {};
var listeners = [];
var addListener = function (fn) {
listeners.unshift(fn);
};
var removeListener = function (fn) {
for (var i = 0; i < listeners.length; i += 1) {
if (listeners[i] === fn) {
listeners.splice(i, 1);
return;
}
}
};
var taskComplete = function () {
_each(listeners.slice(0), function (fn) {
fn();
});
};
addListener(function () {
if (_keys(results).length === keys.length) {
callback(null, results);
callback = function () {};
}
});
_each(keys, function (k) {
var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k];
var taskCallback = function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
if (err) {
var safeResults = {};
_each(_keys(results), function(rkey) {
safeResults[rkey] = results[rkey];
});
safeResults[k] = args;
callback(err, safeResults);
// stop subsequent errors hitting callback multiple times
callback = function () {};
}
else {
results[k] = args;
async.setImmediate(taskComplete);
}
};
var requires = task.slice(0, Math.abs(task.length - 1)) || [];
var ready = function () {
return _reduce(requires, function (a, x) {
return (a && results.hasOwnProperty(x));
}, true) && !results.hasOwnProperty(k);
};
if (ready()) {
task[task.length - 1](taskCallback, results);
}
else {
var listener = function () {
if (ready()) {
removeListener(listener);
task[task.length - 1](taskCallback, results);
}
};
addListener(listener);
}
});
};
async.waterfall = function (tasks, callback) {
callback = callback || function () {};
if (tasks.constructor !== Array) {
var err = new Error('First argument to waterfall must be an array of functions');
return callback(err);
}
if (!tasks.length) {
return callback();
}
var wrapIterator = function (iterator) {
return function (err) {
if (err) {
callback.apply(null, arguments);
callback = function () {};
}
else {
var args = Array.prototype.slice.call(arguments, 1);
var next = iterator.next();
if (next) {
args.push(wrapIterator(next));
}
else {
args.push(callback);
}
async.setImmediate(function () {
iterator.apply(null, args);
});
}
};
};
wrapIterator(async.iterator(tasks))();
};
var _parallel = function(eachfn, tasks, callback) {
callback = callback || function () {};
if (tasks.constructor === Array) {
eachfn.map(tasks, function (fn, callback) {
if (fn) {
fn(function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
callback.call(null, err, args);
});
}
}, callback);
}
else {
var results = {};
eachfn.each(_keys(tasks), function (k, callback) {
tasks[k](function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
results[k] = args;
callback(err);
});
}, function (err) {
callback(err, results);
});
}
};
async.parallel = function (tasks, callback) {
_parallel({ map: async.map, each: async.each }, tasks, callback);
};
async.parallelLimit = function(tasks, limit, callback) {
_parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);
};
async.series = function (tasks, callback) {
callback = callback || function () {};
if (tasks.constructor === Array) {
async.mapSeries(tasks, function (fn, callback) {
if (fn) {
fn(function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
callback.call(null, err, args);
});
}
}, callback);
}
else {
var results = {};
async.eachSeries(_keys(tasks), function (k, callback) {
tasks[k](function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
results[k] = args;
callback(err);
});
}, function (err) {
callback(err, results);
});
}
};
async.iterator = function (tasks) {
var makeCallback = function (index) {
var fn = function () {
if (tasks.length) {
tasks[index].apply(null, arguments);
}
return fn.next();
};
fn.next = function () {
return (index < tasks.length - 1) ? makeCallback(index + 1): null;
};
return fn;
};
return makeCallback(0);
};
async.apply = function (fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function () {
return fn.apply(
null, args.concat(Array.prototype.slice.call(arguments))
);
};
};
var _concat = function (eachfn, arr, fn, callback) {
var r = [];
eachfn(arr, function (x, cb) {
fn(x, function (err, y) {
r = r.concat(y || []);
cb(err);
});
}, function (err) {
callback(err, r);
});
};
async.concat = doParallel(_concat);
async.concatSeries = doSeries(_concat);
async.whilst = function (test, iterator, callback) {
if (test()) {
iterator(function (err) {
if (err) {
return callback(err);
}
async.whilst(test, iterator, callback);
});
}
else {
callback();
}
};
async.doWhilst = function (iterator, test, callback) {
iterator(function (err) {
if (err) {
return callback(err);
}
if (test()) {
async.doWhilst(iterator, test, callback);
}
else {
callback();
}
});
};
async.until = function (test, iterator, callback) {
if (!test()) {
iterator(function (err) {
if (err) {
return callback(err);
}
async.until(test, iterator, callback);
});
}
else {
callback();
}
};
async.doUntil = function (iterator, test, callback) {
iterator(function (err) {
if (err) {
return callback(err);
}
if (!test()) {
async.doUntil(iterator, test, callback);
}
else {
callback();
}
});
};
async.queue = function (worker, concurrency) {
if (concurrency === undefined) {
concurrency = 1;
}
function _insert(q, data, pos, callback) {
if(data.constructor !== Array) {
data = [data];
}
_each(data, function(task) {
var item = {
data: task,
callback: typeof callback === 'function' ? callback : null
};
if (pos) {
q.tasks.unshift(item);
} else {
q.tasks.push(item);
}
if (q.saturated && q.tasks.length === concurrency) {
q.saturated();
}
async.setImmediate(q.process);
});
}
var workers = 0;
var q = {
tasks: [],
concurrency: concurrency,
saturated: null,
empty: null,
drain: null,
push: function (data, callback) {
_insert(q, data, false, callback);
},
unshift: function (data, callback) {
_insert(q, data, true, callback);
},
process: function () {
if (workers < q.concurrency && q.tasks.length) {
var task = q.tasks.shift();
if (q.empty && q.tasks.length === 0) {
q.empty();
}
workers += 1;
var next = function () {
workers -= 1;
if (task.callback) {
task.callback.apply(task, arguments);
}
if (q.drain && q.tasks.length + workers === 0) {
q.drain();
}
q.process();
};
var cb = only_once(next);
worker(task.data, cb);
}
},
length: function () {
return q.tasks.length;
},
running: function () {
return workers;
}
};
return q;
};
async.cargo = function (worker, payload) {
var working = false,
tasks = [];
var cargo = {
tasks: tasks,
payload: payload,
saturated: null,
empty: null,
drain: null,
push: function (data, callback) {
if(data.constructor !== Array) {
data = [data];
}
_each(data, function(task) {
tasks.push({
data: task,
callback: typeof callback === 'function' ? callback : null
});
if (cargo.saturated && tasks.length === payload) {
cargo.saturated();
}
});
async.setImmediate(cargo.process);
},
process: function process() {
if (working) return;
if (tasks.length === 0) {
if(cargo.drain) cargo.drain();
return;
}
var ts = typeof payload === 'number'
? tasks.splice(0, payload)
: tasks.splice(0);
var ds = _map(ts, function (task) {
return task.data;
});
if(cargo.empty) cargo.empty();
working = true;
worker(ds, function () {
working = false;
var args = arguments;
_each(ts, function (data) {
if (data.callback) {
data.callback.apply(null, args);
}
});
process();
});
},
length: function () {
return tasks.length;
},
running: function () {
return working;
}
};
return cargo;
};
var _console_fn = function (name) {
return function (fn) {
var args = Array.prototype.slice.call(arguments, 1);
fn.apply(null, args.concat([function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (typeof console !== 'undefined') {
if (err) {
if (console.error) {
console.error(err);
}
}
else if (console[name]) {
_each(args, function (x) {
console[name](x);
});
}
}
}]));
};
};
async.log = _console_fn('log');
async.dir = _console_fn('dir');
/*async.info = _console_fn('info');
async.warn = _console_fn('warn');
async.error = _console_fn('error');*/
async.memoize = function (fn, hasher) {
var memo = {};
var queues = {};
hasher = hasher || function (x) {
return x;
};
var memoized = function () {
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
var key = hasher.apply(null, args);
if (key in memo) {
callback.apply(null, memo[key]);
}
else if (key in queues) {
queues[key].push(callback);
}
else {
queues[key] = [callback];
fn.apply(null, args.concat([function () {
memo[key] = arguments;
var q = queues[key];
delete queues[key];
for (var i = 0, l = q.length; i < l; i++) {
q[i].apply(null, arguments);
}
}]));
}
};
memoized.memo = memo;
memoized.unmemoized = fn;
return memoized;
};
async.unmemoize = function (fn) {
return function () {
return (fn.unmemoized || fn).apply(null, arguments);
};
};
async.times = function (count, iterator, callback) {
var counter = [];
for (var i = 0; i < count; i++) {
counter.push(i);
}
return async.map(counter, iterator, callback);
};
async.timesSeries = function (count, iterator, callback) {
var counter = [];
for (var i = 0; i < count; i++) {
counter.push(i);
}
return async.mapSeries(counter, iterator, callback);
};
async.compose = function (/* functions... */) {
var fns = Array.prototype.reverse.call(arguments);
return function () {
var that = this;
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
async.reduce(fns, args, function (newargs, fn, cb) {
fn.apply(that, newargs.concat([function () {
var err = arguments[0];
var nextargs = Array.prototype.slice.call(arguments, 1);
cb(err, nextargs);
}]))
},
function (err, results) {
callback.apply(that, [err].concat(results));
});
};
};
var _applyEach = function (eachfn, fns /*args...*/) {
var go = function () {
var that = this;
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
return eachfn(fns, function (fn, cb) {
fn.apply(that, args.concat([cb]));
},
callback);
};
if (arguments.length > 2) {
var args = Array.prototype.slice.call(arguments, 2);
return go.apply(this, args);
}
else {
return go;
}
};
async.applyEach = doParallel(_applyEach);
async.applyEachSeries = doSeries(_applyEach);
async.forever = function (fn, callback) {
function next(err) {
if (err) {
if (callback) {
return callback(err);
}
throw err;
}
fn(next);
}
next();
};
// AMD / RequireJS
if (typeof define !== 'undefined' && define.amd) {
define([], function () {
return async;
});
}
// Node.js
else if (typeof module !== 'undefined' && module.exports) {
module.exports = async;
}
// included directly via <script> tag
else {
root.async = async;
}
}());

@ -7,7 +7,7 @@
var RELATIVE_PATH = "{relative_path}";
</script>
<link rel="stylesheet" href="{relative_path}/vendor/fontawesome/css/font-awesome.min.css">
<script src="//code.jquery.com/jquery.js"></script>
<script src="{relative_path}/vendor/jquery/js/jquery.js"></script>
<script src="{relative_path}/vendor/bootstrap/js/bootstrap.min.js"></script>
<link rel="stylesheet" type="text/css" href="{relative_path}/vendor/colorpicker/colorpicker.css">
<script src="{relative_path}/socket.io/socket.io.js"></script>
@ -34,7 +34,6 @@
}
});
</script>
<link rel="stylesheet" type="text/css" href="//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css">
<script src="//code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<script src="{relative_path}/src/utils.js"></script>
@ -106,7 +105,6 @@
<li><a href="{relative_path}/admin/languages"><i class="fa fa-fw fa-comments-o"></i> Languages</a></li>
<li><a href="{relative_path}/admin/settings"><i class="fa fa-fw fa-cogs"></i> Settings</a></li>
<li><a href="{relative_path}/admin/database"><i class="fa fa-fw fa-hdd-o"></i> Database</a></li>
<li><a href="{relative_path}/admin/motd"><i class="fa fa-fw fa-comment"></i> MOTD</a></li>
<li><a href="{relative_path}/admin/events"><i class="fa fa-fw fa-calendar-o"></i> Events</a></li>
</ul>
</div>

@ -1,33 +0,0 @@
<h1><i class="fa fa-comment"></i> MOTD</h1>
<hr />
<div class="alert alert-warning motd">
<p>
The <strong>Message of the Day</strong> (MOTD) is typically a message shown to users when they first log into a forum or chat room.
In NodeBB, the MOTD is present at the top of the forum homepage, and can be customized much like a header.
</p>
<p>
You can enter full HTML/Javascript.
</p>
<br />
<textarea class="form-control" placeholder="Welcome to NodeBB!" data-field="motd" rows="10"></textarea>
<br />
<form class="form-inline">
<label>MOTD Class</label>
<input class="form-control" type="text" placeholder="CSS class to add to MOTD" data-field="motd_class" />
</form>
<form class="form-inline">
<div class="checkbox">
<label for="show_motd">
<input type="checkbox" id="show_motd" data-field="show_motd" /> Show the Message of the Day
</label>
</div>
</form>
</div>
<button class="btn btn-primary" id="save" checked>Save</button>
<script>
require(['forum/admin/settings'], function(Settings) {
Settings.prepare();
});
</script>

@ -5,6 +5,7 @@
<ul class="nav nav-tabs">
<li class="active"><a href="#" data-target="#themes" data-toggle="tab">Themes</a></li>
<li><a href="#" data-target="#customise" data-toggle="tab">Customise</a></li>
<li><a href="#" data-target="#widgets" data-toggle="tab">Widgets</a></li>
</ul>
<div class="tab-content">
@ -49,6 +50,82 @@
<button class="btn btn-primary" id="save">Save</button>
</div>
<div class="tab-pane" id="widgets">
<h3>Widgets</h3>
<div class="row">
<div class="col-xs-6 pull-right">
<!-- BEGIN areas -->
<div class="area">
<h4>{areas.name} <small>{areas.template} / {areas.location}</small> <button data-template="{areas.template}" data-location="{areas.location}" class="btn btn-success btn-xs pull-right">Save</button></h4>
<div class="well widget-area">
</div>
</div>
<!-- END areas -->
</div>
<div class="col-xs-6 pull-left">
<div class="available-widgets">
<h4>Available Widgets <small>Drag and drop widgets into templates</small></h4>
<div>
<!-- BEGIN widgets -->
<div data-widget="{widgets.widget}" class="panel panel-default pointer">
<div class="panel-heading">
<strong>{widgets.name}</strong> <small>{widgets.description}</small>
</div>
<div class="panel-body hidden">
<form>
{widgets.content}
</form>
</div>
</div>
<!-- END widgets -->
</div>
</div>
<hr />
<div class="available-containers">
<h4>Available Containers <small>Drag and drop on top of any widget</small></h4>
<div class="containers">
<div class="pointer" style="padding: 20px; border: 1px dotted #dedede; margin-bottom: 20px;" data-container-html=" ">
None
</div>
<div class="well pointer" data-container-html='<div class="well">{body}</div>'>
Well
</div>
<div class="jumbotron pointer" data-container-html='<div class="jumbotron">{body}</div>'>
Jumbotron
</div>
<div class="panel panel-default pointer" data-container-html='<div class="panel panel-default"><div class="panel-heading">{title}</div><div class="panel-body">{body}</div></div>'>
<div class="panel-heading">
Panel Header
<div class="pull-right color-selector">
<button data-class="panel-default" class="btn btn-xs">&nbsp;&nbsp;</button>
<button data-class="panel-primary" class="btn btn-xs btn-primary">&nbsp;&nbsp;</button>
<button data-class="panel-success" class="btn btn-xs btn-success">&nbsp;&nbsp;</button>
<button data-class="panel-info" class="btn btn-xs btn-info">&nbsp;&nbsp;</button>
<button data-class="panel-warning" class="btn btn-xs btn-warning">&nbsp;&nbsp;</button>
<button data-class="panel-danger" class="btn btn-xs btn-danger">&nbsp;&nbsp;</button>
</div>
</div>
<div class="panel-body">
Panel Body
</div>
</div>
<div class="alert alert-info pointer" data-container-html='<div class="alert alert-info">{body}</div>'>
Alert
<div class="pull-right color-selector">
<button data-class="alert-success" class="btn btn-xs btn-success">&nbsp;&nbsp;</button>
<button data-class="alert-info" class="btn btn-xs btn-info">&nbsp;&nbsp;</button>
<button data-class="alert-warning" class="btn btn-xs btn-warning">&nbsp;&nbsp;</button>
<button data-class="alert-danger" class="btn btn-xs btn-danger">&nbsp;&nbsp;</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

@ -86,51 +86,21 @@
</li>
<!-- END topics -->
</ul>
<!-- IF usePagination -->
<!-- IF config.usePagination -->
<div class="text-center">
<ul class="pagination">
<li class="previous pull-left"><a href="#"><i class="fa fa-chevron-left"></i> [[global:previouspage]]</a></li>
<li class="next pull-right"><a href="#">[[global:nextpage]] <i class="fa fa-chevron-right"></i></a></li>
</ul>
</div>
<!-- ENDIF usePagination -->
<!-- ENDIF config.usePagination -->
</div>
<!-- IF topics.length -->
<div class="col-md-3 col-xs-12 category-sidebar">
<div class="panel panel-default">
<div class="panel-heading">[[category:sidebar.recent_replies]]</div>
<div class="panel-body recent-replies">
<ul id="category_recent_replies"></ul>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">[[category:sidebar.active_participants]]</div>
<div class="panel-body active-users">
<!-- BEGIN active_users -->
<a data-uid="{active_users.uid}" href="../../user/{active_users.userslug}"><img title="{active_users.username}" src="{active_users.picture}" class="img-rounded user-img" /></a>
<!-- END active_users -->
</div>
</div>
<!-- IF moderators.length -->
<div class="panel panel-default">
<div class="panel-heading">[[category:sidebar.moderators]]</div>
<div class="panel-body moderators">
<!-- BEGIN moderators -->
<a data-uid="{moderators.uid}" href="../../user/{moderators.userslug}"><img title="{moderators.username}" src="{moderators.picture}" class="img-rounded user-img" /></a>
<!-- END moderators -->
</div>
</div>
<!-- ENDIF moderators.length -->
<!-- BEGIN sidebars -->
<div class="panel panel-default">
<div class="panel panel-default {sidebars.block_class}">{sidebars.header}</div>
<div class="panel-body">{sidebars.content}</div>
</div>
<!-- END sidebars -->
<div widget-area="sidebar" class="col-md-3 col-xs-12 category-sidebar">
<!-- BEGIN widgets -->
{widgets.html}
<!-- END widgets -->
</div>
<!-- ENDIF topics.length -->
</div>

@ -14,6 +14,10 @@
<!-- BEGIN pluginCSS -->
<link rel="stylesheet" href="{pluginCSS.path}?{cache-buster}">
<!-- END pluginCSS -->
<!-- IF useCustomCSS -->
<style type="text/css">{customCSS}</style>
<!-- ENDIF useCustomCSS -->
<script>
var RELATIVE_PATH = "{relative_path}";
</script>
@ -31,12 +35,6 @@
}
});
</script>
<!-- TODO : this has to be refactored, maybe configured from ACP? -baris -->
<link rel="stylesheet" type="text/css" href="//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css">
<!-- IF useCustomCSS -->
<style type="text/css">{customCSS}</style>
<!-- ENDIF useCustomCSS -->
</head>
<body>

@ -1,5 +1,9 @@
<div class="motd {motd_class}">
{motd}
<div widget-area="motd" class="hidden">
<!-- BEGIN widgets -->
<div class="motd">
{widgets.html}
</div>
<!-- END widgets -->
</div>
<div class="row home" itemscope itemtype="http://www.schema.org/ItemList">
@ -45,25 +49,10 @@
<!-- END categories -->
</div>
<div class="row footer-stats">
<div class="col-md-3 col-xs-6">
<div class="stats-card well">
<h2><span id="stats_online"></span><br /><small>[[footer:stats.online]]</small></h2>
</div>
</div>
<div class="col-md-3 col-xs-6">
<div class="stats-card well">
<h2><span id="stats_users"></span><br /><small>[[footer:stats.users]]</small></h2>
</div>
</div>
<div class="col-md-3 col-xs-6">
<div class="stats-card well">
<h2><span id="stats_topics"></span><br /><small>[[footer:stats.topics]]</small></h2>
</div>
<div widget-area="footer" class="hidden">
<!-- BEGIN widgets -->
<div class="footer">
{widgets.html}
</div>
<div class="col-md-3 col-xs-6">
<div class="stats-card well">
<h2><span id="stats_posts"></span><br /><small>[[footer:stats.posts]]</small></h2>
</div>
</div>
</div>
<!-- END widgets -->
</div>

@ -1,14 +0,0 @@
<!-- BEGIN posts -->
<li data-pid="{posts.pid}" class="clearfix">
<a href="{relative_path}/user/{posts.userslug}">
<img title="{posts.username}" class="img-rounded user-img" src="{posts.picture}" />
</a>
<strong><span>{posts.username}</span></strong>
<p>{posts.content}</p>
<span class="pull-right">
<a href="{relative_path}/topic/{posts.topicSlug}#{posts.pid}">[[category:posted]]</a>
<span class="timeago" title="{posts.relativeTime}"></span>
</span>
</li>
<!-- END posts -->

@ -214,14 +214,14 @@
<div style="clear:both;"></div>
</div>
<!-- IF usePagination -->
<!-- IF config.usePagination -->
<div class="text-center">
<ul class="pagination">
<li class="previous pull-left"><a href="#"><i class="fa fa-chevron-left"></i> [[global:previouspage]]</a></li>
<li class="next pull-right"><a href="#">[[global:nextpage]] <i class="fa fa-chevron-right"></i></a></li>
</ul>
</div>
<!-- ENDIF usePagination -->
<!-- ENDIF config.usePagination -->
<div id="move_thread_modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="Move Topic" aria-hidden="true">
<div class="modal-dialog">

@ -58,25 +58,6 @@ var db = require('./database'),
Categories.getCategoryTopics(category_id, start, end, current_user, next);
}
function getActiveUsers(next) {
Categories.getActiveUsers(category_id, function(err, uids) {
if(err) {
return next(err);
}
user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture'], next);
});
}
function getModerators(next) {
Categories.getModerators(category_id, next);
}
function getSidebars(next) {
plugins.fireHook('filter:category.build_sidebars', [], function(err, sidebars) {
next(err, sidebars);
});
}
function getPageCount(next) {
Categories.getPageCount(category_id, current_user, next);
}
@ -84,9 +65,6 @@ var db = require('./database'),
async.parallel({
'category': getCategoryData,
'topics': getTopics,
'active_users': getActiveUsers,
'moderators': getModerators,
'sidebars': getSidebars,
'pageCount': getPageCount
}, function(err, results) {
if(err) {
@ -100,13 +78,10 @@ var db = require('./database'),
'disabled': results.category.disabled,
'topic_row_size': 'col-md-9',
'category_id': category_id,
'active_users': results.active_users,
'moderators': results.moderators,
'topics': results.topics.topics,
'nextStart': results.topics.nextStart,
'pageCount': results.pageCount,
'disableSocialButtons': meta.config.disableSocialButtons !== undefined ? parseInt(meta.config.disableSocialButtons, 10) !== 0 : false,
'sidebars': results.sidebars
};
callback(null, category);

@ -171,8 +171,6 @@ var fs = require('fs'),
var user = require('./user');
Meta.title.parseFragment(decodeURIComponent(urlFragment), function(err, title) {
var title;
if (err) {
title = Meta.config.browserTitle || 'NodeBB';
} else {

@ -375,8 +375,10 @@ var fs = require('fs'),
dirs = dirs.map(function(file) {
return path.join(npmPluginPath, file);
}).filter(function(file) {
var stats = fs.statSync(file);
if (stats.isDirectory() && file.substr(npmPluginPath.length + 1, 14) === 'nodebb-plugin-') return true;
var stats = fs.statSync(file),
isPlugin = file.substr(npmPluginPath.length + 1, 14) === 'nodebb-plugin-' || file.substr(npmPluginPath.length + 1, 14) === 'nodebb-widget-';
if (stats.isDirectory() && isPlugin) return true;
else return false;
});

@ -12,6 +12,7 @@ var nconf = require('nconf'),
categories = require('./../categories'),
meta = require('../meta'),
plugins = require('../plugins'),
widgets = require('../widgets'),
image = require('./../image'),
file = require('./../file'),
Languages = require('../languages'),
@ -414,7 +415,32 @@ var nconf = require('nconf'),
});
app.get('/themes', function (req, res) {
res.json(200, {});
async.parallel({
areas: function(next) {
plugins.fireHook('filter:widgets.getAreas', [], next);
},
widgets: function(next) {
plugins.fireHook('filter:widgets.getWidgets', [], next);
}
}, function(err, data) {
async.each(data.areas, function(area, next) {
widgets.getArea(area.template, area.location, function(err, areaData) {
area.data = areaData;
next(err);
});
}, function(err) {
for (var w in data.widgets) {
if (data.widgets.hasOwnProperty(w)) {
data.widgets[w].content += "<br /><label>Title:</label><input type=\"text\" class=\"form-control\" name=\"title\" placeholder=\"Title (only shown on some containers)\" /><br /><label>Container:</label><textarea rows=\"4\" class=\"form-control container-html\" name=\"container\" placeholder=\"Drag and drop a container or enter HTML here.\"></textarea>";
}
}
res.json(200, {
areas: data.areas,
widgets: data.widgets
});
});
});
});
app.get('/testing/categories', function (req, res) {

@ -108,45 +108,7 @@ var path = require('path'),
data.categories = visibleCategories;
async.each(data.categories, getRecentReplies, function (err) {
var motdString,
assemble = function() {
data.motd_class = (parseInt(meta.config.show_motd, 10) === 1 || meta.config.show_motd === undefined) ? '' : ' none';
data.motd_class += (meta.config.motd && meta.config.motd.length > 0) ? '' : ' default';
data.motd_class += meta.config.motd_class ? ' ' + meta.config.motd_class : '';
data.motd = motdString;
res.json(data);
};
if (!meta.config.motd) {
translator.translate('\n\n<h1>NodeBB</h1> <small><span>v' + pkg.version + '</span></small>\n\n<h5>[[global:motd.welcome]]</h5>\
<div class="btn-group">\
<a target="_blank" href="https://www.nodebb.org" class="btn btn-link btn-md">\
<i class="fa fa-comment"></i>\
<span>&nbsp;[[global:motd.get]]</span>\
</a>\
<a target="_blank" href="https://github.com/designcreateplay/NodeBB" class="btn btn-link btn-md">\
<i class="fa fa-github"></i>\
<span>&nbsp;[[global:motd.fork]]</span>\
</a>\
<a target="_blank" href="https://facebook.com/NodeBB" class="btn btn-link btn-md">\
<i class="fa fa-facebook"></i>\
<span>&nbsp;[[global:motd.like]]</span>\
</a>\
<a target="_blank" href="https://twitter.com/NodeBB" class="btn btn-link btn-md">\
<i class="fa fa-twitter"></i>\
<span>&nbsp;[[global:motd.follow]]</span>\
</a>\
</div>\
', function(motd) {
motdString = motd;
assemble();
});
} else {
motdString = meta.config.motd;
assemble();
}
res.json(data);
});
});
});

@ -13,16 +13,6 @@
login_strategies = [];
passport.use(new passportLocal(function(user, password, next) {
Auth.login(user, password, function(err, login) {
if (!err) {
next(null, login.user);
} else {
next(null, false, err);
}
});
}));
plugins.ready(function() {
plugins.fireHook('filter:auth.init', login_strategies, function(err) {
if (err) {
@ -33,16 +23,6 @@
});
});
passport.serializeUser(function(user, done) {
done(null, user.uid);
});
passport.deserializeUser(function(uid, done) {
done(null, {
uid: uid
});
});
Auth.initialize = function(app) {
app.use(passport.initialize());
app.use(passport.session());
@ -107,11 +87,9 @@
if (err) {
return next(err);
}
if (!user) {
return res.send({
success: false,
message: info.message
});
return res.json(403, info);
}
// Alter user cookie depending on passed-in option
@ -127,10 +105,7 @@
req.login({
uid: user.uid
}, function() {
res.send({
success: true,
message: 'authentication succeeded'
});
res.json(info);
});
})(req, res, next);
});
@ -163,50 +138,60 @@
Auth.login = function(username, password, next) {
if (!username || !password) {
return next({
status: 'error',
message: 'invalid-user'
});
} else {
return next(new Error('invalid-user'));
}
var userslug = utils.slugify(username);
user.getUidByUserslug(userslug, function(err, uid) {
if (err) {
return next(err);
}
var userslug = utils.slugify(username);
if(!uid) {
return next(null, false, 'user doesn\'t exist');
}
user.getUidByUserslug(userslug, function(err, uid) {
user.getUserFields(uid, ['password', 'banned'], function(err, userData) {
if (err) {
return next(new Error('redis-error'));
} else if (uid == null) {
return next(new Error('invalid-user'));
return next(err);
}
user.getUserFields(uid, ['password', 'banned'], function(err, userData) {
if (err) return next(err);
if (!userData || !userData.password) {
return next(new Error('invalid userdata or password'));
}
if (userData.banned && parseInt(userData.banned, 10) === 1) {
return next({
status: "error",
message: "user-banned"
});
if (userData.banned && parseInt(userData.banned, 10) === 1) {
return next(null, false, 'User banned');
}
bcrypt.compare(password, userData.password, function(err, res) {
if (err) {
winston.err(err.message);
return next(new Error('bcrypt compare error'));
}
bcrypt.compare(password, userData.password, function(err, res) {
if (err) {
winston.err(err.message);
next(new Error('bcrypt compare error'));
return;
}
if (res) {
next(null, {
user: {
uid: uid
}
});
} else {
next(new Error('invalid-password'));
}
});
if (!res) {
next(null, false, 'invalid-password');
}
next(null, {
uid: uid
}, 'Authentication successful');
});
});
}
});
}
passport.use(new passportLocal(Auth.login));
passport.serializeUser(function(user, done) {
done(null, user.uid);
});
passport.deserializeUser(function(uid, done) {
done(null, {
uid: uid
});
});
}(exports));

@ -45,18 +45,17 @@ var fs = require('fs'),
app.namespace('/user', function () {
function createRoute(routeName, path, templateName) {
app.get(routeName, function(req, res, next) {
if (!req.params.userslug) {
return next();
}
function createRoute(routeName, path, templateName, access) {
function isAllowed(req, res, next) {
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
if (!req.user && (path === '/favourites' || !!parseInt(meta.config.privateUserInfo, 10))) {
if (!callerUID && !!parseInt(meta.config.privateUserInfo, 10)) {
return res.redirect('/403');
}
user.getUidByUserslug(req.params.userslug, function (err, uid) {
if(err) {
if (err) {
return next(err);
}
@ -64,84 +63,50 @@ var fs = require('fs'),
return res.redirect('/404');
}
app.build_header({
req: req,
res: res
}, function (err, header) {
if(err) {
return next(err);
}
res.send(header + app.create_route('user/' + req.params.userslug + path, templateName) + templates['footer']);
});
});
})
}
if (parseInt(uid, 10) === callerUID) {
return next();
}
createRoute('/:userslug', '', 'account');
createRoute('/:userslug/following', '/following', 'following');
createRoute('/:userslug/followers', '/followers', 'followers');
createRoute('/:userslug/favourites', '/favourites', 'favourites');
createRoute('/:userslug/posts', '/posts', 'accountposts');
if (req.path.indexOf('/edit') !== -1) {
user.isAdministrator(callerUID, function(err, isAdmin) {
if(err) {
return next(err);
}
app.get('/:userslug/edit', function (req, res, next) {
if(!isAdmin) {
return res.redirect('/403');
}
if (!req.user) {
return res.redirect('/403');
next();
});
} else if (req.path.indexOf('/settings') !== -1 || req.path.indexOf('/favourites') !== -1) {
res.redirect('/403')
} else {
next();
}
});
}
user.getUserField(req.user.uid, 'userslug', function (err, userslug) {
function done() {
app.build_header({
req: req,
res: res
}, function (err, header) {
res.send(header + app.create_route('user/' + req.params.userslug + '/edit', 'accountedit') + templates['footer']);
});
}
if(err || !userslug) {
return next(err);
}
if (userslug === req.params.userslug) {
return done();
}
user.isAdministrator(req.user.uid, function(err, isAdmin) {
app.get(routeName, isAllowed, function(req, res, next) {
app.build_header({
req: req,
res: res
}, function (err, header) {
if(err) {
return next(err);
}
if(!isAdmin) {
return res.redirect('/403');
}
done();
res.send(header + app.create_route('user/' + req.params.userslug + path, templateName) + templates['footer']);
});
});
});
app.get('/:userslug/settings', function (req, res) {
if (!req.user) {
return res.redirect('/403');
}
user.getUserField(req.user.uid, 'userslug', function (err, userslug) {
if (req.params.userslug && userslug === req.params.userslug) {
app.build_header({
req: req,
res: res
}, function (err, header) {
res.send(header + app.create_route('user/' + req.params.userslug + '/settings', 'accountsettings') + templates['footer']);
})
} else {
return res.redirect('/404');
}
});
});
}
createRoute('/:userslug', '', 'account');
createRoute('/:userslug/following', '/following', 'following');
createRoute('/:userslug/followers', '/followers', 'followers');
createRoute('/:userslug/favourites', '/favourites', 'favourites');
createRoute('/:userslug/posts', '/posts', 'accountposts');
createRoute('/:userslug/edit', '/edit', 'accountedit');
createRoute('/:userslug/settings', '/settings', 'accountsettings');
app.post('/uploadpicture', function (req, res) {
if (!req.user) {
@ -257,121 +222,114 @@ var fs = require('fs'),
next();
}
app.get('/api/user/:userslug/following', isAllowed, function (req, res, next) {
var callerUID = req.user ? req.user.uid : '0';
app.get('/api/user/:userslug/following', isAllowed, getUserFollowing);
app.get('/api/user/:userslug/followers', isAllowed, getUserFollowers);
app.get('/api/user/:userslug/edit', isAllowed, getUserEdit);
app.get('/api/user/:userslug/settings', isAllowed, getUserSettings);
app.get('/api/user/:userslug/favourites', isAllowed, getUserFavourites);
app.get('/api/user/:userslug/posts', isAllowed, getUserPosts);
app.get('/api/user/uid/:uid', isAllowed, getUserData);
app.get('/api/user/:userslug', isAllowed, getUserProfile);
app.get('/api/users', isAllowed, getOnlineUsers);
app.get('/api/users/sort-posts', isAllowed, getUsersSortedByPosts);
app.get('/api/users/sort-reputation', isAllowed, getUsersSortedByReputation);
app.get('/api/users/latest', isAllowed, getUsersSortedByJoinDate);
app.get('/api/users/online', isAllowed, getOnlineUsers);
app.get('/api/users/search', isAllowed, getUsersForSearch);
function getUserProfile(req, res, next) {
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
getUserDataByUserSlug(req.params.userslug, callerUID, function (err, userData) {
if(err) {
return next(err);
}
if (userData) {
user.getFollowing(userData.uid, function (err, followingData) {
if(err) {
return next(err);
}
userData.following = followingData;
userData.followingCount = followingData.length;
res.json(userData);
});
} else {
res.json(404, {
if(!userData) {
return res.json(404, {
error: 'User not found!'
});
}
});
});
app.get('/api/user/:userslug/followers', isAllowed, function (req, res, next) {
var callerUID = req.user ? req.user.uid : '0';
user.isFollowing(callerUID, userData.theirid, function (isFollowing) {
getUserDataByUserSlug(req.params.userslug, callerUID, function (err, userData) {
if(err) {
return next(err);
}
posts.getPostsByUid(callerUID, userData.theirid, 0, 9, function (err, userPosts) {
if (userData) {
user.getFollowers(userData.uid, function (err, followersData) {
if(err) {
return next(err);
}
userData.followers = followersData;
userData.followersCount = followersData.length;
res.json(userData);
});
} else {
res.json(404, {
error: 'User not found!'
});
}
});
});
app.get('/api/user/:userslug/edit', function (req, res, next) {
var callerUID = req.user ? req.user.uid : '0';
userData.posts = userPosts.posts.filter(function (p) {
return p && parseInt(p.deleted, 10) !== 1;
});
userData.isFollowing = isFollowing;
if(!parseInt(callerUID, 10)) {
return res.json(403, {
error: 'Not allowed!'
if (!userData.profileviews) {
userData.profileviews = 1;
}
if (callerUID !== parseInt(userData.uid, 10) && callerUID) {
user.incrementUserFieldBy(userData.uid, 'profileviews', 1);
}
postTools.parse(userData.signature, function (err, signature) {
userData.signature = signature;
res.json(userData);
});
});
});
}
});
}
getUserDataByUserSlug(req.params.userslug, callerUID, function (err, userData) {
if(err) {
return next(err);
}
function getUserData(req, res, next) {
var uid = req.params.uid ? req.params.uid : 0;
user.getUserData(uid, function(err, userData) {
res.json(userData);
});
});
app.get('/api/user/:userslug/settings', function(req, res, next) {
var callerUID = req.user ? req.user.uid : '0';
}
user.getUidByUserslug(req.params.userslug, function(err, uid) {
if (err) {
return next(err);
}
function getUserPosts(req, res, next) {
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
user.getUidByUserslug(req.params.userslug, function (err, uid) {
if (!uid) {
return res.json(404, {
error: 'User not found!'
});
}
if (uid != callerUID || callerUID == '0') {
return res.json(403, {
error: 'Not allowed!'
});
}
plugins.fireHook('filter:user.settings', [], function(err, settings) {
user.getUserFields(uid, ['username', 'userslug'], function (err, userData) {
if (err) {
return next(err);
}
user.getUserFields(uid, ['username', 'userslug'], function(err, userData) {
if (!userData) {
return res.json(404, {
error: 'User not found!'
});
}
posts.getPostsByUid(callerUID, uid, 0, 19, function (err, userPosts) {
if (err) {
return next(err);
}
if(!userData) {
return res.json(404, {
error: 'User not found!'
});
}
userData.yourid = req.user.uid;
userData.uid = uid;
userData.theirid = uid;
userData.settings = settings;
userData.yourid = callerUID;
userData.posts = userPosts.posts;
userData.nextStart = userPosts.nextStart;
res.json(userData);
});
});
});
});
}
app.get('/api/user/:userslug/favourites', isAllowed, function (req, res, next) {
var callerUID = req.user ? req.user.uid : '0';
function getUserFavourites(req, res, next) {
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
user.getUidByUserslug(req.params.userslug, function (err, uid) {
if (!uid) {
@ -380,7 +338,7 @@ var fs = require('fs'),
});
}
if (uid != callerUID || callerUID == '0') {
if (parseInt(uid, 10) !== callerUID) {
return res.json(403, {
error: 'Not allowed!'
});
@ -411,107 +369,114 @@ var fs = require('fs'),
});
});
});
});
}
app.get('/api/user/:userslug/posts', isAllowed, function (req, res, next) {
var callerUID = req.user ? req.user.uid : '0';
function getUserSettings(req, res, next) {
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
user.getUidByUserslug(req.params.userslug, function(err, uid) {
if (err) {
return next(err);
}
user.getUidByUserslug(req.params.userslug, function (err, uid) {
if (!uid) {
return res.json(404, {
error: 'User not found!'
});
}
user.getUserFields(uid, ['username', 'userslug'], function (err, userData) {
if (parseInt(uid, 10) !== callerUID) {
return res.json(403, {
error: 'Not allowed!'
});
}
plugins.fireHook('filter:user.settings', [], function(err, settings) {
if (err) {
return next(err);
}
if (!userData) {
return res.json(404, {
error: 'User not found!'
});
}
posts.getPostsByUid(callerUID, uid, 0, 19, function (err, userPosts) {
user.getUserFields(uid, ['username', 'userslug'], function(err, userData) {
if (err) {
return next(err);
}
userData.uid = uid;
userData.theirid = uid;
userData.yourid = callerUID;
userData.posts = userPosts.posts;
userData.nextStart = userPosts.nextStart;
if(!userData) {
return res.json(404, {
error: 'User not found!'
});
}
userData.yourid = req.user.uid;
userData.theirid = uid;
userData.settings = settings;
res.json(userData);
});
});
});
});
}
function getUserEdit(req, res, next) {
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
app.get('/api/user/uid/:uid', isAllowed, function(req, res, next) {
var uid = req.params.uid ? req.params.uid : 0;
user.getUserData(uid, function(err, userData) {
getUserDataByUserSlug(req.params.userslug, callerUID, function (err, userData) {
if(err) {
return next(err);
}
res.json(userData);
});
});
}
app.get('/api/user/:userslug', isAllowed, function (req, res, next) {
var callerUID = req.user ? req.user.uid : '0';
function getUserFollowers(req, res, next) {
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
getUserDataByUserSlug(req.params.userslug, callerUID, function (err, userData) {
if(err) {
return next(err);
}
if(!userData) {
return res.json(404, {
if (userData) {
user.getFollowers(userData.uid, function (err, followersData) {
if(err) {
return next(err);
}
userData.followers = followersData;
userData.followersCount = followersData.length;
res.json(userData);
});
} else {
res.json(404, {
error: 'User not found!'
});
}
});
}
user.isFollowing(callerUID, userData.theirid, function (isFollowing) {
function getUserFollowing(req, res, next) {
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
posts.getPostsByUid(callerUID, userData.theirid, 0, 9, function (err, userPosts) {
getUserDataByUserSlug(req.params.userslug, callerUID, function (err, userData) {
if(err) {
return next(err);
}
if (userData) {
user.getFollowing(userData.uid, function (err, followingData) {
if(err) {
return next(err);
}
userData.posts = userPosts.posts.filter(function (p) {
return p && parseInt(p.deleted, 10) !== 1;
});
userData.isFollowing = isFollowing;
if (!userData.profileviews) {
userData.profileviews = 1;
}
if (parseInt(callerUID, 10) !== parseInt(userData.uid, 10) && parseInt(callerUID, 0)) {
user.incrementUserFieldBy(userData.uid, 'profileviews', 1);
}
postTools.parse(userData.signature, function (err, signature) {
userData.signature = signature;
res.json(userData);
});
userData.following = followingData;
userData.followingCount = followingData.length;
res.json(userData);
});
});
} else {
res.json(404, {
error: 'User not found!'
});
}
});
});
app.get('/api/users', isAllowed, getOnlineUsers);
app.get('/api/users/sort-posts', isAllowed, getUsersSortedByPosts);
app.get('/api/users/sort-reputation', isAllowed, getUsersSortedByReputation);
app.get('/api/users/latest', isAllowed, getUsersSortedByJoinDate);
app.get('/api/users/online', isAllowed, getOnlineUsers);
app.get('/api/users/search', isAllowed, getUsersForSearch);
}
function getUsersSortedByJoinDate(req, res) {
user.getUsers('users:joindate', 0, 49, function (err, data) {
@ -607,78 +572,76 @@ var fs = require('fs'),
}
function getUserDataByUserSlug(userslug, callerUID, callback) {
var userData;
async.waterfall([
function(next) {
user.getUidByUserslug(userslug, next);
},
function(uid, next) {
if (!uid) {
return next(new Error('invalid-user'));
}
user.getUserData(uid, next);
},
function(data, next) {
userData = data;
if (!userData) {
return callback(new Error('invalid-user'));
user.getUidByUserslug(userslug, function(err, uid) {
if(err || !uid) {
return callback(err || new Error('invalid-user'));
}
async.parallel({
userData : function(next) {
user.getUserData(uid, next);
},
userSettings : function(next) {
user.getSettings(uid, next);
},
isAdmin : function(next) {
user.isAdministrator(callerUID, next);
},
followStats: function(next) {
user.getFollowStats(uid, next);
}
}, function(err, results) {
if(err || !results.userData) {
return callback(err || new Error('invalid-user'));
}
user.isAdministrator(callerUID, next);
}
], function(err, isAdmin) {
if(err) {
return callback(err);
}
var userData = results.userData;
var userSettings = results.userSettings;
var isAdmin = results.isAdmin;
userData.joindate = utils.toISOString(userData.joindate);
if(userData.lastonline) {
userData.lastonline = utils.toISOString(userData.lastonline);
} else {
userData.lastonline = userData.joindate;
}
userData.joindate = utils.toISOString(userData.joindate);
if(userData.lastonline) {
userData.lastonline = utils.toISOString(userData.lastonline);
} else {
userData.lastonline = userData.joindate;
}
if (!userData.birthday) {
userData.age = '';
} else {
userData.age = Math.floor((new Date().getTime() - new Date(userData.birthday).getTime()) / 31536000000);
}
if (!userData.birthday) {
userData.age = '';
} else {
userData.age = Math.floor((new Date().getTime() - new Date(userData.birthday).getTime()) / 31536000000);
}
function canSeeEmail() {
return isAdmin || callerUID == userData.uid || (userData.email && (userData.showemail && parseInt(userData.showemail, 10) === 1));
}
function canSeeEmail() {
return isAdmin || parseInt(callerUID, 10) === parseInt(userData.uid, 10) || (userData.email && userSettings.showemail);
}
if (!canSeeEmail()) {
userData.email = "";
}
if (!canSeeEmail()) {
userData.email = "";
}
if (callerUID == userData.uid && (!userData.showemail || parseInt(userData.showemail, 10) === 0)) {
userData.emailClass = "";
} else {
userData.emailClass = "hide";
}
if (parseInt(callerUID, 10) === parseInt(userData.uid, 10) && !userSettings.showemail) {
userData.emailClass = "";
} else {
userData.emailClass = "hide";
}
userData.websiteName = userData.website.replace('http://', '').replace('https://', '');
userData.banned = parseInt(userData.banned, 10) === 1;
userData.uid = userData.uid;
userData.yourid = callerUID;
userData.theirid = userData.uid;
userData.websiteName = userData.website.replace('http://', '').replace('https://', '');
userData.banned = parseInt(userData.banned, 10) === 1;
userData.uid = userData.uid;
userData.yourid = callerUID;
userData.theirid = userData.uid;
userData.disableSignatures = meta.config.disableSignatures !== undefined && parseInt(meta.config.disableSignatures, 10) === 1;
userData.disableSignatures = meta.config.disableSignatures !== undefined && parseInt(meta.config.disableSignatures, 10) === 1;
userData.followingCount = results.followStats.followingCount;
userData.followerCount = results.followStats.followerCount;
user.getFollowStats(userData.uid, function (err, followStats) {
if(err) {
return callback(err);
}
userData.followingCount = followStats.followingCount;
userData.followerCount = followStats.followerCount;
callback(null, userData);
});
});
}
};
}(exports));

@ -3,6 +3,7 @@
var groups = require('../groups'),
meta = require('../meta'),
plugins = require('../plugins'),
widgets = require('../widgets'),
user = require('../user'),
topics = require('../topics'),
categories = require('../categories'),
@ -255,10 +256,11 @@ SocketAdmin.categories.groupsList = function(socket, cid, callback) {
});
};
/* Themes & Plugins */
/* Themes, Widgets, and Plugins */
SocketAdmin.themes = {};
SocketAdmin.plugins = {};
SocketAdmin.widgets = {};
SocketAdmin.themes.getInstalled = function(socket, data, callback) {
meta.themes.get(callback);
@ -269,7 +271,7 @@ SocketAdmin.themes.set = function(socket, data, callback) {
return callback(new Error('invalid data'));
}
meta.themes.set(data, callback);
}
};
SocketAdmin.plugins.toggle = function(socket, plugin_id) {
plugins.toggleActive(plugin_id, function(status) {
@ -277,6 +279,14 @@ SocketAdmin.plugins.toggle = function(socket, plugin_id) {
});
};
SocketAdmin.widgets.set = function(socket, data, callback) {
if(!data) {
return callback(new Error('invalid data'));
}
widgets.setArea(data, callback);
};
/* Configs */
SocketAdmin.config = {};

@ -0,0 +1,11 @@
"use strict";
var widgets = require('../widgets'),
SocketWidgets = {};
SocketWidgets.render = function(socket, data, callback) {
widgets.render(socket.uid, data, callback);
};
module.exports = SocketWidgets;

@ -19,7 +19,7 @@ var db = require('./database'),
Upgrade.check = function(callback) {
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema
var latestSchema = new Date(2014, 1, 14, 21, 50).getTime();
var latestSchema = new Date(2014, 1, 20, 20, 25).getTime();
db.get('schemaDate', function(err, value) {
if (parseInt(value, 10) >= latestSchema) {
@ -685,6 +685,134 @@ Upgrade.upgrade = function(callback) {
winston.info('[2014/2/14] Added posts to sorted set - skipped');
next();
}
},
function(next) {
thisSchemaDate = new Date(2014, 1, 19, 18, 15).getTime();
if (schemaDate < thisSchemaDate) {
updatesMade = true;
db.setObjectField('widgets:home.tpl', 'motd', JSON.stringify([
{
"widget": "html",
"data": {
"html": Meta.config['motd'] || "Welcome to NodeBB, if you are an administrator of this forum visit the <a target='_blank' href='/admin/themes'>Themes</a> ACP to modify and add widgets."
}
}
]), function(err) {
Meta.configs.remove('motd');
Meta.configs.remove('motd_class');
Meta.configs.remove('show_motd');
winston.info('[2014/2/19] Updated MOTD to use the HTML widget.');
next(err);
});
} else {
winston.info('[2014/2/19] Updating MOTD to use the HTML widget - skipped');
next();
}
},
function(next) {
thisSchemaDate = new Date(2014, 1, 20, 15, 30).getTime();
if (schemaDate < thisSchemaDate) {
updatesMade = true;
var container = '<div class="panel panel-default"><div class="panel-heading">{title}</div><div class="panel-body">{body}</div></div>';
db.setObjectField('widgets:category.tpl', 'sidebar', JSON.stringify([
{
"widget": "recentreplies",
"data": {
"title": "Recent Replies",
"container": container
}
},
{
"widget": "activeusers",
"data": {
"title": "Active Users",
"container": container
}
},
{
"widget": "moderators",
"data": {
"title": "Moderators",
"container": container
}
}
]), function(err) {
winston.info('[2014/2/20] Adding Recent Replies, Active Users, and Moderator widgets to category sidebar.');
next(err);
});
} else {
winston.info('[2014/2/20] Adding Recent Replies, Active Users, and Moderator widgets to category sidebar - skipped');
next();
}
},
function(next) {
thisSchemaDate = new Date(2014, 1, 20, 16, 15).getTime();
if (schemaDate < thisSchemaDate) {
updatesMade = true;
db.setObjectField('widgets:home.tpl', 'footer', JSON.stringify([
{
"widget": "forumstats",
"data": {}
}
]), function(err) {
winston.info('[2014/2/20] Adding Forum Stats Widget to the Homepage Footer.');
next(err);
});
} else {
winston.info('[2014/2/20] Adding Forum Stats Widget to the Homepage Footer - skipped');
next();
}
},
function(next) {
thisSchemaDate = new Date(2014, 1, 20, 19, 45).getTime();
if (schemaDate < thisSchemaDate) {
updatesMade = true;
var container = '<div class="panel panel-default"><div class="panel-heading">{title}</div><div class="panel-body">{body}</div></div>';
db.setObjectField('widgets:home.tpl', 'sidebar', JSON.stringify([
{
"widget": "html",
"data": {
"html": Meta.config['motd'] || "Welcome to NodeBB, if you are an administrator of this forum visit the <a target='_blank' href='/admin/themes'>Themes</a> ACP to modify and add widgets.",
"container": container,
"title": "MOTD"
}
}
]), function(err) {
winston.info('[2014/2/20] Updating Lavender MOTD');
next(err);
});
} else {
winston.info('[2014/2/20] Updating Lavender MOTD - skipped');
next();
}
},
function(next) {
thisSchemaDate = new Date(2014, 1, 20, 20, 25).getTime();
if (schemaDate < thisSchemaDate) {
updatesMade = true;
db.setAdd('plugins:active', 'nodebb-widget-essentials', function(err) {
winston.info('[2014/2/20] Activating NodeBB Essential Widgets');
Plugins.reload(function() {
next(err);
});
});
} else {
winston.info('[2014/2/20] Activating NodeBB Essential Widgets - skipped');
next();
}
}
// Add new schema updates here
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 17!!!

@ -104,43 +104,46 @@ var bcrypt = require('bcryptjs'),
'postcount': 0,
'lastposttime': 0,
'banned': 0,
'status': 'online',
'showemail': 0
'status': 'online'
};
db.setObject('user:' + uid, userData);
db.setObject('user:' + uid, userData, function(err) {
db.setObjectField('username:uid', userData.username, uid);
db.setObjectField('userslug:uid', userData.userslug, uid);
if(err) {
return callback(err);
}
db.setObjectField('username:uid', userData.username, uid);
db.setObjectField('userslug:uid', userData.userslug, uid);
if (userData.email !== undefined) {
db.setObjectField('email:uid', userData.email, uid);
if (parseInt(uid, 10) !== 1) {
User.email.verify(uid, userData.email);
if (userData.email !== undefined) {
db.setObjectField('email:uid', userData.email, uid);
if (parseInt(uid, 10) !== 1) {
User.email.verify(uid, userData.email);
}
}
}
plugins.fireHook('action:user.create', userData);
db.incrObjectField('global', 'userCount');
plugins.fireHook('action:user.create', userData);
db.incrObjectField('global', 'userCount');
db.sortedSetAdd('users:joindate', timestamp, uid);
db.sortedSetAdd('users:postcount', 0, uid);
db.sortedSetAdd('users:reputation', 0, uid);
db.sortedSetAdd('users:joindate', timestamp, uid);
db.sortedSetAdd('users:postcount', 0, uid);
db.sortedSetAdd('users:reputation', 0, uid);
groups.joinByGroupName('registered-users', uid);
groups.joinByGroupName('registered-users', uid);
if (password) {
User.hashPassword(password, function(err, hash) {
if(err) {
return callback(err);
}
if (password) {
User.hashPassword(password, function(err, hash) {
if(err) {
return callback(err);
}
User.setUserField(uid, 'password', hash);
User.setUserField(uid, 'password', hash);
callback(null, uid);
});
} else {
callback(null, uid);
});
} else {
callback(null, uid);
}
}
});
});
});
};
@ -209,7 +212,7 @@ var bcrypt = require('bcryptjs'),
settings = {}
}
settings.showemail = settings.showemail ? parseInt(settings.showemail, 10) !== 0 : parseInt(meta.config.usePagination, 10) !== 0;
settings.showemail = settings.showemail ? parseInt(settings.showemail, 10) !== 0 : false;
settings.usePagination = settings.usePagination ? parseInt(settings.usePagination, 10) !== 0 : parseInt(meta.config.usePagination, 10) !== 0;
settings.topicsPerPage = settings.topicsPerPage ? parseInt(settings.topicsPerPage, 10) : parseInt(meta.config.topicsPerPage, 10) || 20;
settings.postsPerPage = settings.postsPerPage ? parseInt(settings.postsPerPage, 10) : parseInt(meta.config.postsPerPage, 10) || 10;

@ -88,10 +88,10 @@ module.exports.server = server;
property: 'keywords',
content: meta.config.keywords || ''
}],
defaultLinkTags = [/*{
defaultLinkTags = [{
rel: 'apple-touch-icon',
href: meta.config['brand:logo'] || nconf.get('relative_path') + '/logo.png'
}*/],
href: '/apple-touch-icon'
}],
templateValues = {
bootswatchCSS: meta.config['theme:src'],
pluginCSS: plugins.cssFiles.map(function(file) { return { path: nconf.get('relative_path') + file.replace(/\\/g, '/') }; }),
@ -203,7 +203,17 @@ module.exports.server = server;
logger.init(app);
// favicon & apple-touch-icon middleware
app.use(express.favicon(path.join(__dirname, '../', 'public', meta.config['brand:favicon'] ? meta.config['brand:favicon'] : 'favicon.ico')));
app.use('/apple-touch-icon', function(req, res) {
if (meta.config['brand:logo'] && validator.isURL(meta.config['brand:logo'])) {
return res.redirect(meta.config['brand:logo']);
} else {
return res.sendfile(path.join(__dirname, '../public', meta.config['brand:logo'] || nconf.get('relative_path') + '/logo.png'), {
maxAge: app.enabled('cache') ? 5184000000 : 0
});
}
});
app.use(require('less-middleware')({
src: path.join(__dirname, '../', 'public'),

@ -0,0 +1,66 @@
var async = require('async'),
winston = require('winston'),
plugins = require('./plugins'),
db = require('./database'),
templates = require('./../public/src/templates');
(function(Widgets) {
Widgets.render = function(uid, area, callback) {
if (!area.location || !area.template) {
callback({
error: 'Missing location and template data'
});
}
var rendered = [];
Widgets.getArea(area.template, area.location, function(err, widgets) {
async.eachSeries(widgets, function(widget, next) {
plugins.fireHook('filter:widget.render:' + widget.widget, {
uid: uid,
area: area,
data: widget.data
}, function(err, html){
if (widget.data.container && widget.data.container.match('{body}')) {
html = templates.prepare(widget.data.container).parse({
title: widget.data.title,
body: html
});
}
rendered.push({
html: html
});
next(err);
});
}, function(err) {
callback(err, rendered);
});
});
};
Widgets.getArea = function(template, location, callback) {
db.getObjectField('widgets:' + template, location, function(err, widgets) {
if (!widgets) {
return callback(err, []);
}
callback(err, JSON.parse(widgets));
})
};
Widgets.setArea = function(area, callback) {
if (!area.location || !area.template) {
callback({
error: 'Missing location and template data'
});
}
db.setObjectField('widgets:' + area.template, area.location, JSON.stringify(area.widgets), function(err) {
callback(err);
});
};
}(exports));
Loading…
Cancel
Save