mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-01-07 16:12:53 +01:00
Merge branch 'master' of https://github.com/designcreateplay/NodeBB
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
var widgetLocations = [];
|
||||
|
||||
app.refreshTitle(url);
|
||||
$(window).trigger('action:ajaxify.end', { url: url });
|
||||
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;
|
||||
|
||||
@@ -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> <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;
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
958
public/src/modules/vendor/async.js
vendored
Normal file
958
public/src/modules/vendor/async.js
vendored
Normal file
@@ -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"> </button>
|
||||
<button data-class="panel-primary" class="btn btn-xs btn-primary"> </button>
|
||||
<button data-class="panel-success" class="btn btn-xs btn-success"> </button>
|
||||
<button data-class="panel-info" class="btn btn-xs btn-info"> </button>
|
||||
<button data-class="panel-warning" class="btn btn-xs btn-warning"> </button>
|
||||
<button data-class="panel-danger" class="btn btn-xs btn-danger"> </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"> </button>
|
||||
<button data-class="alert-info" class="btn btn-xs btn-info"> </button>
|
||||
<button data-class="alert-warning" class="btn btn-xs btn-warning"> </button>
|
||||
<button data-class="alert-danger" class="btn btn-xs btn-danger"> </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -97,40 +97,10 @@
|
||||
</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>
|
||||
|
||||
@@ -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 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_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>
|
||||
<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 -->
|
||||
@@ -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);
|
||||
|
||||
@@ -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> [[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> [[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> [[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> [[global:motd.follow]]</span>\
|
||||
</a>\
|
||||
</div>\
|
||||
', function(motd) {
|
||||
motdString = motd;
|
||||
assemble();
|
||||
});
|
||||
} else {
|
||||
motdString = meta.config.motd;
|
||||
assemble();
|
||||
}
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
11
src/socket.io/widgets.js
Normal file
11
src/socket.io/widgets.js
Normal file
@@ -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;
|
||||
128
src/upgrade.js
128
src/upgrade.js
@@ -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, 20).getTime();
|
||||
|
||||
db.get('schemaDate', function(err, value) {
|
||||
if (parseInt(value, 10) >= latestSchema) {
|
||||
@@ -685,6 +685,132 @@ 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, 20).getTime();
|
||||
|
||||
if (schemaDate < thisSchemaDate) {
|
||||
updatesMade = true;
|
||||
|
||||
db.setAdd('plugins:active', 'nodebb-widget-essentials', function(err) {
|
||||
winston.info('[2014/2/20] Activating NodeBB Essential Widgets');
|
||||
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!!!
|
||||
|
||||
66
src/widgets.js
Normal file
66
src/widgets.js
Normal file
@@ -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));
|
||||
Reference in New Issue
Block a user