diff --git a/public/less/admin/admin.less b/public/less/admin/admin.less index 74a4b459ff..a3a2bf8938 100644 --- a/public/less/admin/admin.less +++ b/public/less/admin/admin.less @@ -1,8 +1,10 @@ @import "./bootstrap/bootstrap"; @import "./mixins"; +@import "./vars"; @import "./general/dashboard"; @import "./general/navigation"; +@import "./manage/categories"; @import "./manage/tags"; @import "./manage/flags"; @import "./manage/users"; diff --git a/public/less/admin/manage/categories.less b/public/less/admin/manage/categories.less new file mode 100644 index 0000000000..7a2082877d --- /dev/null +++ b/public/less/admin/manage/categories.less @@ -0,0 +1,71 @@ +div.categories { + + ul { + list-style-type: none; + margin: 0; + padding: 0; + } + + .fa-ul { + li { + min-height: 0; + display: inline; + margin: 0 @acp-margin 0 0; + left: 0; + } + } + + li { + min-height: @acp-line-height; + margin: @acp-base-line 0; + } + + > ul > li + li:before { + content: ""; + display: block; + height: 1px; + width: 100%; + margin: @acp-base-line; + background: #F5F5F5; + } + + .disabled { + + .icon, .header, .description { + opacity: 0.5; + } + + .stats { + opacity: 0.3; + } + } + + .icon { + width: @acp-line-height; + height: @acp-line-height; + border-radius: 50%; + line-height: @acp-line-height; + text-align: center; + vertical-align: bottom; + background-size: cover; + float: left; + margin-right: @acp-margin; + } + + .information { + float:left; + } + + .header { + margin-top: 0; + margin-bottom: @acp-base-line; + } + + .description { + margin: 0; + } + + .stats, .btn-group { + float: left; + } +} \ No newline at end of file diff --git a/public/less/admin/vars.less b/public/less/admin/vars.less new file mode 100644 index 0000000000..8e7bc2bb30 --- /dev/null +++ b/public/less/admin/vars.less @@ -0,0 +1,3 @@ +@acp-base-line: 8px; +@acp-line-height: @acp-base-line * 6; +@acp-margin: @acp-base-line * 2; \ No newline at end of file diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js index 535d902dfe..ff110540d1 100644 --- a/public/src/admin/manage/categories.js +++ b/public/src/admin/manage/categories.js @@ -5,7 +5,13 @@ define('admin/manage/categories', function() { var Categories = {}; Categories.init = function() { - var bothEl = $('#active-categories, #disabled-categories'); + socket.emit('admin.categories.getAll', function(error, payload){ + if(error){ + return app.alertError(error.message); + } + + Categories.render(payload); + }); function updateCategoryOrders(evt, ui) { var categories = $(evt.target).children(), @@ -22,31 +28,6 @@ define('admin/manage/categories', function() { socket.emit('admin.categories.update', modified); } - bothEl.sortable({ - stop: updateCategoryOrders, - distance: 15 - }); - - // Category enable/disable - bothEl.on('click', '[data-action="toggle"]', function(ev) { - var btnEl = $(this), - cid = btnEl.parents('tr').attr('data-cid'), - disabled = btnEl.attr('data-disabled') === 'false' ? '1' : '0', - payload = {}; - - payload[cid] = { - disabled: disabled - }; - - socket.emit('admin.categories.update', payload, function(err, result) { - if (err) { - return app.alertError(err.message); - } else { - ajaxify.refresh(); - } - }); - }); - $('button[data-action="create"]').on('click', Categories.create); }; @@ -78,5 +59,111 @@ define('admin/manage/categories', function() { }); }; + Categories.render = function(categories){ + var container = $('.categories'); + + if(!categories || categories.length == 0){ + $('
') + .addClass('alert alert-info text-center') + .text('You have no active categories.') + .appendTo(container); + }else{ + renderList(categories, 0, container); + } + }; + + function renderList(categories, level, parent){ + var i = 0, len = categories.length, category, list = $(''), marginLeft = 48, listItem; + + for(i; i < len; ++i){ + category = categories[i]; + + listItem = $('
  • ') + .append(renderListItem(category)) + .appendTo(list); + + if(level > 0){ + listItem.css('margin-left', marginLeft); + } + + if(category.disabled){ + listItem.addClass('disabled'); + } + + if(category.children.length > 0){ + renderList(category.children, level + 1, listItem); + } + } + + list.appendTo(parent); + } + + function renderListItem(categoryEntity){ + var listItem = $(templates.parse( + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '' + + '
    ' + + '
    ' + + '
    {name}
    ' + + '

    {description}

    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
      ' + + '
    • {topic_count}
    • ' + + '
    • {post_count}
    • ' + + '
    ' + + '
    ' + + '' + + 'Edit' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + categoryEntity + )); + + var icon = listItem.find('.icon'), + button = listItem.find('[data-action="toggle"]'); + + if(categoryEntity.backgroundImage){ + icon.css('background-image', 'url(' + categoryEntity.backgroundImage + ')'); + } + + icon + .css('color', categoryEntity.color) + .css('background-color', categoryEntity.bgColor); + + if(categoryEntity.disabled){ + button.text('Enable').addClass('btn-success'); + }else{ + button.text('Disable').addClass('btn-danger'); + } + + // Category enable/disable + button.on('click', function(e) { + var payload = {}; + + payload[categoryEntity.cid] = { + disabled: !categoryEntity.disabled | 0 + }; + + socket.emit('admin.categories.update', payload, function(err, result) { + if (err) { + return app.alertError(err.message); + } else { + ajaxify.refresh(); + } + }); + }); + + return listItem; + } + return Categories; }); \ No newline at end of file diff --git a/src/categories.js b/src/categories.js index 699b1abc7b..38cfa39c1e 100644 --- a/src/categories.js +++ b/src/categories.js @@ -310,4 +310,25 @@ var async = require('async'), ], callback); }; + /** + * Recursively build tree + * + * @param categories {array} flat list of categories + * @param parentCid {number} start from 0 to build full tree + */ + Categories.getTree = function(categories, parentCid) { + var tree = [], i = 0, len = categories.length, category; + + for(i; i < len; ++i){ + category = categories[i]; + + if(category.parentCid == parentCid){ + tree.push(category); + category.children = Categories.getTree(categories, category.cid); + } + } + + return tree; + }; + }(exports)); diff --git a/src/controllers/admin.js b/src/controllers/admin.js index 59e6680e8b..0dafe9767c 100644 --- a/src/controllers/admin.js +++ b/src/controllers/admin.js @@ -166,28 +166,8 @@ adminController.categories.get = function(req, res, next) { }; adminController.categories.getAll = function(req, res, next) { - var active = [], - disabled = []; - - async.waterfall([ - async.apply(db.getSortedSetRangeByScore, 'categories:cid', 0, -1, 0, Date.now()), - async.apply(categories.getCategoriesData), - function(categories, next) { - plugins.fireHook('filter:admin.categories.get', {req: req, res: res, categories: categories}, next); - } - ], function(err, data) { - if (err) { - return next(err); - } - data.categories.filter(Boolean).forEach(function(category) { - (category.disabled ? disabled : active).push(category); - }); - - res.render('admin/manage/categories', { - active: active, - disabled: disabled - }); - }); + //Categories list will be rendered on client side with recursion, etc. + res.render('admin/manage/categories', {}); }; adminController.tags.get = function(req, res, next) { diff --git a/src/socket.io/admin/categories.js b/src/socket.io/admin/categories.js index 848d175a09..a63f203dc7 100644 --- a/src/socket.io/admin/categories.js +++ b/src/socket.io/admin/categories.js @@ -2,10 +2,12 @@ var async = require('async'), + db = require('../../database'), groups = require('../../groups'), user = require('../../user'), categories = require('../../categories'), privileges = require('../../privileges'), + plugins = require('../../plugins'), Categories = {}; Categories.create = function(socket, data, callback) { @@ -16,6 +18,26 @@ Categories.create = function(socket, data, callback) { categories.create(data, callback); }; +Categories.getAll = function(socket, data, callback) { + async.waterfall([ + async.apply(db.getSortedSetRangeByScore, 'categories:cid', 0, -1, 0, Date.now()), + async.apply(categories.getCategoriesData), + function(categories, next) { + //Hook changes, there is no req, and res + plugins.fireHook('filter:admin.categories.get', {categories: categories}, next); + }, + function(result, next){ + next(null, categories.getTree(result.categories, 0)); + } + ], function(err, categoriesTree) { + if (err) { + return callback(err); + } + + callback(null, categoriesTree); + }); +}; + Categories.purge = function(socket, cid, callback) { categories.purge(cid, callback); }; diff --git a/src/views/admin/manage/categories.tpl b/src/views/admin/manage/categories.tpl index 39d2a15c9b..c57996551a 100644 --- a/src/views/admin/manage/categories.tpl +++ b/src/views/admin/manage/categories.tpl @@ -1,120 +1,20 @@
    -
    -
    -
    Active Categories
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameDescriptionTopicsPosts
    - - - - {active.name}{active.description}{active.topic_count}{active.post_count} -
    - Edit - -
    -
    -
    - You have no active categories. -
    -
    -
    -
    +
    +
    +
    Categories
    +
    +
    +
    +
    +
    -
    -
    Disabled Categories
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameDescriptionTopicsPosts
    - - - - {disabled.name}{disabled.description}{disabled.topic_count}{disabled.post_count} -
    - Edit - -
    -
    -
    - You have no disabled categories. -
    -
    -
    -
    -
    - -
    -
    -
    Categories Control Panel
    -
    - -
    -
    -
    +
    +
    +
    Categories Control Panel
    +
    + +
    +
    +