Merge branch 'categories-parent-acp' of https://github.com/NicolasSiver/NodeBB into NicolasSiver-categories-parent-acp

v1.18.x
Julian Lam 10 years ago
commit 394497c9d5

@ -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";

@ -0,0 +1,68 @@
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;
&.placeholder {
border: 1px dashed #2196F3;
background-color: #E1F5FE;
}
}
.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;
cursor: move;
}
.information {
float: left;
}
.header {
margin-top: 0;
margin-bottom: @acp-base-line;
}
.description {
margin: 0;
}
.stats, .btn-group {
float: left;
}
}

@ -0,0 +1,3 @@
@acp-base-line: 8px;
@acp-line-height: @acp-base-line * 6;
@acp-margin: @acp-base-line * 2;

@ -2,48 +2,18 @@
/*global define, socket, app, bootbox, templates, ajaxify, RELATIVE_PATH*/
define('admin/manage/categories', function() {
var Categories = {};
var Categories = {}, newCategoryId = -1, sortables, itemTemplate;
Categories.init = function() {
var bothEl = $('#active-categories, #disabled-categories');
$.get(RELATIVE_PATH + '/templates/admin/partials/categories/category-item.tpl', function(data){
itemTemplate = data;
function updateCategoryOrders(evt, ui) {
var categories = $(evt.target).children(),
modified = {},
cid;
for(var i=0;i<categories.length;i++) {
cid = $(categories[i]).attr('data-cid');
modified[cid] = {
order: i+1
};
}
socket.emit('admin.categories.update', modified);
socket.emit('admin.categories.getAll', function(error, payload){
if(error){
return app.alertError(error.message);
}
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();
}
Categories.render(payload);
});
});
@ -78,5 +48,132 @@ 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{
sortables = {};
renderList(categories, 0, container, 0);
}
};
function itemDidAdd(e){
newCategoryId = e.to.dataset.cid;
}
function itemDragDidEnd(e){
var isCategoryUpdate = (newCategoryId != -1);
//Update needed?
if((e.newIndex != undefined && e.oldIndex != e.newIndex) || isCategoryUpdate){
var parentCategory = isCategoryUpdate ? sortables[newCategoryId] : sortables[e.from.dataset.cid],
modified = {}, i = 0, list = parentCategory.toArray(), len = list.length;
for(i; i < len; ++i) {
modified[list[i]] = {
order: (i + 1)
}
}
if(isCategoryUpdate){
modified[e.item.dataset.cid]['parentCid'] = newCategoryId;
}
newCategoryId = -1
socket.emit('admin.categories.update', modified);
}
}
/**
* Render categories - recursively
*
* @param categories {array} categories tree
* @param level {number} current sub-level of rendering
* @param container {object} parent jquery element for the list
* @param parentId {number} parent category identifier
*/
function renderList(categories, level, container, parentId){
var i = 0, len = categories.length, category, marginLeft = 48, listItem;
var list = $('<ul />', {'data-cid': parentId});
if(level > 0){
list.css('margin-left', marginLeft);
}
for(i; i < len; ++i){
category = categories[i];
listItem = $('<li></li>', {'data-cid': category.cid})
.append(renderListItem(category))
.appendTo(list);
if(category.disabled){
listItem.addClass('disabled');
}
if(category.children.length > 0){
renderList(category.children, level + 1, listItem, category.cid);
}
}
list.appendTo(container);
sortables[parentId] = Sortable.create(list[0], {
group: 'cross-categories',
animation: 150,
handle: '.icon',
dataIdAttr: 'data-cid',
ghostClass: "placeholder",
onAdd: itemDidAdd,
onEnd: itemDragDidEnd
});
}
function renderListItem(categoryEntity){
var listItem = $(templates.parse(
itemTemplate,
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;
});

File diff suppressed because it is too large Load Diff

@ -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));

@ -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) {

@ -25,6 +25,7 @@ module.exports = function(Meta) {
base: [
'public/vendor/jquery/js/jquery.js',
'public/vendor/jquery/js/jquery-ui-1.10.4.custom.js',
'public/vendor/jquery/sortable/Sortable.js',
'./node_modules/socket.io-client/socket.io.js',
'public/vendor/jquery/timeago/jquery.timeago.min.js',
'public/vendor/jquery/js/jquery.form.min.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);
};

@ -1,110 +1,9 @@
<div class="row">
<div class="col-lg-9">
<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">
<table class="table table-striped table-hover table-reordering">
<thead>
<tr>
<th></th>
<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="panel-heading"><i class="fa fa-folder"></i> Disabled Categories</div>
<div class="panel-body">
<table class="table table-striped table-hover table-reordering">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Description</th>
<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 class="categories"></div>
</div>
</div>
</div>
@ -113,7 +12,8 @@
<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>
<button type="button" class="btn btn-primary btn-block" data-action="create">Create New Category
</button>
</div>
</div>
</div>

@ -0,0 +1,26 @@
<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>
Loading…
Cancel
Save