diff --git a/package.json b/package.json index 424f97d689..2dd6ff5718 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/public/language/en_GB/category.json b/public/language/en_GB/category.json index d29e23eb95..861c7b4ec6 100644 --- a/public/language/en_GB/category.json +++ b/public/language/en_GB/category.json @@ -1,9 +1,6 @@ { "new_topic_button": "New Topic", "no_topics": "There are no topics in this category.
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", diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 58e6eceb76..2505113ce2 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -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; diff --git a/public/src/forum/admin/themes.js b/public/src/forum/admin/themes.js index 699796e36b..a3b22983b1 100644 --- a/public/src/forum/admin/themes.js +++ b/public/src/forum/admin/themes.js @@ -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('
 
') + .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; }); \ No newline at end of file diff --git a/public/src/forum/category.js b/public/src/forum/category.js index fd4b6efe42..810adaa78f 100644 --- a/public/src/forum/category.js +++ b/public/src/forum/category.js @@ -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; }); \ No newline at end of file diff --git a/public/src/forum/home.js b/public/src/forum/home.js index c38568bd6c..cc6bd63fdf 100644 --- a/public/src/forum/home.js +++ b/public/src/forum/home.js @@ -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; diff --git a/public/src/modules/vendor/async.js b/public/src/modules/vendor/async.js new file mode 100644 index 0000000000..1eebb153fd --- /dev/null +++ b/public/src/modules/vendor/async.js @@ -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 - + @@ -34,7 +34,6 @@ } }); - @@ -106,7 +105,6 @@
  • Languages
  • Settings
  • Database
  • -
  • MOTD
  • Events
  • diff --git a/public/templates/admin/motd.tpl b/public/templates/admin/motd.tpl deleted file mode 100644 index a79ac0f5fd..0000000000 --- a/public/templates/admin/motd.tpl +++ /dev/null @@ -1,33 +0,0 @@ -

    MOTD

    -
    -
    -

    - The Message of the Day (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. -

    -

    - You can enter full HTML/Javascript. -

    -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - - - \ No newline at end of file diff --git a/public/templates/admin/themes.tpl b/public/templates/admin/themes.tpl index 331d85f6e8..95501268de 100644 --- a/public/templates/admin/themes.tpl +++ b/public/templates/admin/themes.tpl @@ -5,6 +5,7 @@
    @@ -49,6 +50,82 @@
    +
    +

    Widgets

    + +
    +
    + +
    +

    {areas.name} {areas.template} / {areas.location}

    +
    + +
    +
    + +
    +
    +
    +

    Available Widgets Drag and drop widgets into templates

    +
    + +
    +
    + {widgets.name} {widgets.description} +
    + +
    + +
    +
    +
    +
    +

    Available Containers Drag and drop on top of any widget

    +
    +
    + None +
    +
    + Well +
    +
    + Jumbotron +
    +
    +
    + Panel Header +
    + + + + + + +
    +
    +
    + Panel Body +
    +
    + +
    + Alert +
    + + + + +
    +
    +
    +
    +
    +
    +
    diff --git a/public/templates/category.tpl b/public/templates/category.tpl index 48971a5265..7489279134 100644 --- a/public/templates/category.tpl +++ b/public/templates/category.tpl @@ -97,40 +97,10 @@ -
    -
    -
    [[category:sidebar.recent_replies]]
    -
    -
      -
      -
      - -
      -
      [[category:sidebar.active_participants]]
      -
      - - - -
      -
      - - -
      -
      [[category:sidebar.moderators]]
      -
      - - - -
      -
      - - - -
      -
      {sidebars.header}
      -
      {sidebars.content}
      -
      - +
      + + {widgets.html} +
      diff --git a/public/templates/home.tpl b/public/templates/home.tpl index b852449f21..dac82f3a8f 100644 --- a/public/templates/home.tpl +++ b/public/templates/home.tpl @@ -1,5 +1,9 @@ -
      - {motd} +
      @@ -45,25 +49,10 @@
      -