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(
+ '' +
+ '
' +
+ '
' +
+ '
' +
+ '' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '- {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
-
-
-
-
- |
- Name |
- Description |
- Topics |
- Posts |
- |
-
-
-
-
-
-
- |
-
-
-
- |
- {active.name} |
- {active.description} |
- {active.topic_count} |
- {active.post_count} |
-
-
- |
-
-
-
-
- |
-
- You have no active categories.
-
- |
-
-
-
-
-
-
+
-
-
Disabled Categories
-
-
-
-
- |
- Name |
- Description |
- Topics |
- Posts |
- |
-
-
-
-
-
-
- |
-
-
-
- |
- {disabled.name} |
- {disabled.description} |
- {disabled.topic_count} |
- {disabled.post_count} |
-
-
- |
-
-
-
-
- |
-
- You have no disabled categories.
-
- |
-
-
-
-
-
-
-
-
-
+