mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-08 06:55:46 +01:00
tree view
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
@import "./bootstrap/bootstrap";
|
@import "./bootstrap/bootstrap";
|
||||||
@import "./mixins";
|
@import "./mixins";
|
||||||
|
@import "./vars";
|
||||||
|
|
||||||
@import "./general/dashboard";
|
@import "./general/dashboard";
|
||||||
@import "./general/navigation";
|
@import "./general/navigation";
|
||||||
|
@import "./manage/categories";
|
||||||
@import "./manage/tags";
|
@import "./manage/tags";
|
||||||
@import "./manage/flags";
|
@import "./manage/flags";
|
||||||
@import "./manage/users";
|
@import "./manage/users";
|
||||||
|
|||||||
71
public/less/admin/manage/categories.less
Normal file
71
public/less/admin/manage/categories.less
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
public/less/admin/vars.less
Normal file
3
public/less/admin/vars.less
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@acp-base-line: 8px;
|
||||||
|
@acp-line-height: @acp-base-line * 6;
|
||||||
|
@acp-margin: @acp-base-line * 2;
|
||||||
@@ -5,7 +5,13 @@ define('admin/manage/categories', function() {
|
|||||||
var Categories = {};
|
var Categories = {};
|
||||||
|
|
||||||
Categories.init = function() {
|
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) {
|
function updateCategoryOrders(evt, ui) {
|
||||||
var categories = $(evt.target).children(),
|
var categories = $(evt.target).children(),
|
||||||
@@ -22,31 +28,6 @@ define('admin/manage/categories', function() {
|
|||||||
socket.emit('admin.categories.update', modified);
|
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);
|
$('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){
|
||||||
|
$('<div></div>')
|
||||||
|
.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 = $('<ul></ul>'), marginLeft = 48, listItem;
|
||||||
|
|
||||||
|
for(i; i < len; ++i){
|
||||||
|
category = categories[i];
|
||||||
|
|
||||||
|
listItem = $('<li></li>')
|
||||||
|
.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(
|
||||||
|
'<div class="row">' +
|
||||||
|
'<div class="col-md-9">' +
|
||||||
|
'<div class="clearfix">' +
|
||||||
|
'<div class="icon">' +
|
||||||
|
'<i data-name="icon" value="{icon}" class="fa {icon}"></i>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="information">' +
|
||||||
|
'<h5 class="header">{name}</h5>' +
|
||||||
|
'<p class="description">{description}</p>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="col-md-3">' +
|
||||||
|
'<div class="clearfix pull-right">' +
|
||||||
|
'<ul class="fa-ul stats">' +
|
||||||
|
'<li class="fa-li"><i class="fa fa-book"></i> {topic_count}</li>' +
|
||||||
|
'<li class="fa-li"><i class="fa fa-pencil"></i> {post_count}</li>' +
|
||||||
|
'</ul>' +
|
||||||
|
'<div class="btn-group">' +
|
||||||
|
'<button data-action="toggle" data-disabled="{disabled}" class="btn btn-xs"></button>' +
|
||||||
|
'<a href="./categories/{cid}" class="btn btn-default btn-xs">Edit</a>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>',
|
||||||
|
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;
|
return Categories;
|
||||||
});
|
});
|
||||||
@@ -310,4 +310,25 @@ var async = require('async'),
|
|||||||
], callback);
|
], 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));
|
}(exports));
|
||||||
|
|||||||
@@ -166,28 +166,8 @@ adminController.categories.get = function(req, res, next) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
adminController.categories.getAll = function(req, res, next) {
|
adminController.categories.getAll = function(req, res, next) {
|
||||||
var active = [],
|
//Categories list will be rendered on client side with recursion, etc.
|
||||||
disabled = [];
|
res.render('admin/manage/categories', {});
|
||||||
|
|
||||||
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
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
adminController.tags.get = function(req, res, next) {
|
adminController.tags.get = function(req, res, next) {
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
var async = require('async'),
|
var async = require('async'),
|
||||||
|
|
||||||
|
db = require('../../database'),
|
||||||
groups = require('../../groups'),
|
groups = require('../../groups'),
|
||||||
user = require('../../user'),
|
user = require('../../user'),
|
||||||
categories = require('../../categories'),
|
categories = require('../../categories'),
|
||||||
privileges = require('../../privileges'),
|
privileges = require('../../privileges'),
|
||||||
|
plugins = require('../../plugins'),
|
||||||
Categories = {};
|
Categories = {};
|
||||||
|
|
||||||
Categories.create = function(socket, data, callback) {
|
Categories.create = function(socket, data, callback) {
|
||||||
@@ -16,6 +18,26 @@ Categories.create = function(socket, data, callback) {
|
|||||||
categories.create(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 = function(socket, cid, callback) {
|
||||||
categories.purge(cid, callback);
|
categories.purge(cid, callback);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,120 +1,20 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-9">
|
<div class="col-lg-9">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><i class="fa fa-folder"></i> Active Categories</div>
|
<div class="panel-heading"><i class="fa fa-folder"></i> Categories</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<table class="table table-striped table-hover table-reordering">
|
<div class="categories"></div>
|
||||||
<thead>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<th></th>
|
</div>
|
||||||
<th>Name</th>
|
|
||||||
<th>Description</th>
|
|
||||||
<th class="text-center">Topics</th>
|
|
||||||
<th class="text-center">Posts</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="active-categories">
|
|
||||||
<!-- IF active.length -->
|
|
||||||
<!-- BEGIN active -->
|
|
||||||
<tr data-cid="{active.cid}">
|
|
||||||
<td>
|
|
||||||
<span class="label" style="
|
|
||||||
<!-- IF active.backgroundImage -->background-image: url({active.backgroundImage});<!-- ENDIF active.backgroundImage -->
|
|
||||||
<!-- IF active.bgColor -->background-color: {active.bgColor};<!-- ENDIF active.bgColor -->
|
|
||||||
color: {active.color};
|
|
||||||
background-size:cover;
|
|
||||||
">
|
|
||||||
<i data-name="icon" value="{active.icon}" class="fa fa-fw {active.icon}"></i>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>{active.name}</td>
|
|
||||||
<td>{active.description}</td>
|
|
||||||
<td class="text-center">{active.topic_count}</td>
|
|
||||||
<td class="text-center">{active.post_count}</td>
|
|
||||||
<td>
|
|
||||||
<div class="btn-group">
|
|
||||||
<a href="./categories/{active.cid}" class="btn btn-default btn-xs">Edit</a>
|
|
||||||
<button data-action="toggle" data-disabled="{active.disabled}" class="btn btn-default btn-xs">Disable</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- END active -->
|
|
||||||
<!-- ELSE -->
|
|
||||||
<tr>
|
|
||||||
<td colspan="6">
|
|
||||||
<div class="alert alert-info text-center">
|
|
||||||
You have no active categories.
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- ENDIF active.length -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
<div class="col-lg-3 acp-sidebar">
|
||||||
<div class="panel-heading"><i class="fa fa-folder"></i> Disabled Categories</div>
|
<div class="panel panel-default">
|
||||||
<div class="panel-body">
|
<div class="panel-heading">Categories Control Panel</div>
|
||||||
<table class="table table-striped table-hover table-reordering">
|
<div class="panel-body">
|
||||||
<thead>
|
<button type="button" class="btn btn-primary btn-block" data-action="create">Create New Category
|
||||||
<tr>
|
</button>
|
||||||
<th></th>
|
</div>
|
||||||
<th>Name</th>
|
</div>
|
||||||
<th>Description</th>
|
</div>
|
||||||
<th class="text-center">Topics</th>
|
|
||||||
<th class="text-center">Posts</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="disabled-categories">
|
|
||||||
<!-- IF disabled.length -->
|
|
||||||
<!-- BEGIN disabled -->
|
|
||||||
<tr data-cid="{disabled.cid}">
|
|
||||||
<td>
|
|
||||||
<span class="label" style="
|
|
||||||
<!-- IF disabled.backgroundImage -->background-image: url({disabled.backgroundImage});<!-- ENDIF disabled.backgroundImage -->
|
|
||||||
<!-- IF disabled.bgColor -->background-color: {disabled.bgColor};<!-- ENDIF disabled.bgColor -->
|
|
||||||
color: {disabled.color};
|
|
||||||
background-size:cover;
|
|
||||||
">
|
|
||||||
<i data-name="icon" value="{disabled.icon}" class="fa fa-fw {disabled.icon}"></i>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>{disabled.name}</td>
|
|
||||||
<td>{disabled.description}</td>
|
|
||||||
<td class="text-center">{disabled.topic_count}</td>
|
|
||||||
<td class="text-center">{disabled.post_count}</td>
|
|
||||||
<td>
|
|
||||||
<div class="btn-group">
|
|
||||||
<a href="./categories/{disabled.cid}" class="btn btn-default btn-xs">Edit</a>
|
|
||||||
<button data-action="toggle" data-disabled="{disabled.disabled}" class="btn btn-default btn-xs">Enable</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- END disabled -->
|
|
||||||
<!-- ELSE -->
|
|
||||||
<tr>
|
|
||||||
<td colspan="6">
|
|
||||||
<div class="alert alert-info text-center">
|
|
||||||
You have no disabled categories.
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- ENDIF disabled.length -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-lg-3 acp-sidebar">
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">Categories Control Panel</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<button type="button" class="btn btn-primary btn-block" data-action="create">Create New Category</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user