Merge pull request #3748 from riking/category-reorder
Category reordering fixes
This commit is contained in:
commit
6d7915eae1
|
@ -0,0 +1,39 @@
|
||||||
|
import { iconHTML } from 'discourse/helpers/fa-icon';
|
||||||
|
import DropdownButton from 'discourse/components/dropdown-button';
|
||||||
|
import computed from "ember-addons/ember-computed-decorators";
|
||||||
|
|
||||||
|
export default DropdownButton.extend({
|
||||||
|
buttonExtraClasses: 'no-text',
|
||||||
|
title: '',
|
||||||
|
text: iconHTML('bars') + ' ' + iconHTML('caret-down'),
|
||||||
|
classNames: ['category-notification-menu', 'category-admin-menu'],
|
||||||
|
|
||||||
|
@computed()
|
||||||
|
dropDownContent() {
|
||||||
|
const includeReorder = this.get('siteSettings.fixed_category_positions');
|
||||||
|
const items = [
|
||||||
|
{ id: 'create',
|
||||||
|
title: I18n.t('category.create'),
|
||||||
|
description: I18n.t('category.create_long'),
|
||||||
|
styleClasses: 'fa fa-plus' }
|
||||||
|
];
|
||||||
|
if (includeReorder) {
|
||||||
|
items.push({
|
||||||
|
id: 'reorder',
|
||||||
|
title: I18n.t('categories.reorder.title'),
|
||||||
|
description: I18n.t('categories.reorder.title_long'),
|
||||||
|
styleClasses: 'fa fa-random'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
|
||||||
|
actionNames: {
|
||||||
|
create: 'createCategory',
|
||||||
|
reorder: 'reorderCategories'
|
||||||
|
},
|
||||||
|
|
||||||
|
clicked(id) {
|
||||||
|
this.sendAction('actionNames.' + id);
|
||||||
|
}
|
||||||
|
});
|
|
@ -29,9 +29,7 @@ export default Ember.Component.extend(StringBuffer, {
|
||||||
buffer.push("<h4 class='title'>" + title + "</h4>");
|
buffer.push("<h4 class='title'>" + title + "</h4>");
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.push("<button class='btn standard dropdown-toggle' data-toggle='dropdown'>");
|
buffer.push(`<button class='btn standard dropdown-toggle ${this.get('buttonExtraClasses')}' data-toggle='dropdown'>${this.get('text')}</button>`);
|
||||||
buffer.push(this.get('text'));
|
|
||||||
buffer.push("</button>");
|
|
||||||
buffer.push("<ul class='dropdown-menu'>");
|
buffer.push("<ul class='dropdown-menu'>");
|
||||||
|
|
||||||
const contents = this.get('dropDownContent');
|
const contents = this.get('dropDownContent');
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||||
const BufferedProxy = window.BufferedProxy; // import BufferedProxy from 'ember-buffered-proxy/proxy';
|
const BufferedProxy = window.BufferedProxy; // import BufferedProxy from 'ember-buffered-proxy/proxy';
|
||||||
import binarySearch from 'discourse/lib/binary-search';
|
|
||||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
import computed from "ember-addons/ember-computed-decorators";
|
import computed from "ember-addons/ember-computed-decorators";
|
||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
const SortableArrayProxy = Ember.ArrayProxy.extend(Ember.SortableMixin);
|
||||||
|
|
||||||
export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, {
|
export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, {
|
||||||
|
|
||||||
@computed("site.categories")
|
@computed("site.categories")
|
||||||
|
@ -13,24 +14,23 @@ export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, {
|
||||||
return categories.map(c => bufProxy.create({ content: c }));
|
return categories.map(c => bufProxy.create({ content: c }));
|
||||||
},
|
},
|
||||||
|
|
||||||
// uses propertyDidChange()
|
categoriesOrdered: function() {
|
||||||
@computed('categoriesBuffered')
|
return SortableArrayProxy.create({
|
||||||
categoriesGrouped(cats) {
|
sortProperties: ['content.position'],
|
||||||
const map = {};
|
content: this.get('categoriesBuffered')
|
||||||
cats.forEach((cat) => {
|
});
|
||||||
const p = cat.get('position') || 0;
|
}.property('categoriesBuffered'),
|
||||||
if (!map[p]) {
|
|
||||||
map[p] = {pos: p, cats: [cat]};
|
showFixIndices: function() {
|
||||||
} else {
|
const cats = this.get('categoriesOrdered');
|
||||||
map[p].cats.push(cat);
|
const len = cats.get('length');
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
if (cats.objectAt(i).get('position') !== i) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
const result = [];
|
return false;
|
||||||
Object.keys(map).map(p => parseInt(p)).sort((a,b) => a-b).forEach(function(pos) {
|
}.property('categoriesOrdered.@each.position'),
|
||||||
result.push(map[pos]);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
|
|
||||||
showApplyAll: function() {
|
showApplyAll: function() {
|
||||||
let anyChanged = false;
|
let anyChanged = false;
|
||||||
|
@ -38,29 +38,19 @@ export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, {
|
||||||
return anyChanged;
|
return anyChanged;
|
||||||
}.property('categoriesBuffered.@each.hasBufferedChanges'),
|
}.property('categoriesBuffered.@each.hasBufferedChanges'),
|
||||||
|
|
||||||
saveDisabled: Ember.computed.alias('showApplyAll'),
|
saveDisabled: Ember.computed.or('showApplyAll', 'showFixIndices'),
|
||||||
|
|
||||||
moveDir(cat, dir) {
|
moveDir(cat, dir) {
|
||||||
const grouped = this.get('categoriesGrouped'),
|
const cats = this.get('categoriesOrdered');
|
||||||
curPos = cat.get('position'),
|
const curIdx = cats.indexOf(cat);
|
||||||
curGroupIdx = binarySearch(grouped, curPos, "pos"),
|
const desiredIdx = curIdx + dir;
|
||||||
curGroup = grouped[curGroupIdx];
|
if (desiredIdx >= 0 && desiredIdx < cats.get('length')) {
|
||||||
|
const curPos = cat.get('position');
|
||||||
if (curGroup.cats.length === 1 && ((dir === -1 && curGroupIdx !== 0) || (dir === 1 && curGroupIdx !== (grouped.length - 1)))) {
|
|
||||||
const nextGroup = grouped[curGroupIdx + dir],
|
|
||||||
nextPos = nextGroup.pos;
|
|
||||||
cat.set('position', nextPos);
|
|
||||||
} else {
|
|
||||||
cat.set('position', curPos + dir);
|
cat.set('position', curPos + dir);
|
||||||
|
const otherCat = cats.objectAt(desiredIdx);
|
||||||
|
otherCat.set('position', curPos - dir);
|
||||||
|
this.send('commit');
|
||||||
}
|
}
|
||||||
cat.applyBufferedChanges();
|
|
||||||
Ember.run.next(this, () => {
|
|
||||||
this.propertyDidChange('categoriesGrouped');
|
|
||||||
Ember.run.schedule('afterRender', this, () => {
|
|
||||||
this.set('scrollIntoViewId', cat.get('id'));
|
|
||||||
this.trigger('scrollIntoView');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -72,13 +62,22 @@ export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, {
|
||||||
this.moveDir(cat, 1);
|
this.moveDir(cat, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fixIndices() {
|
||||||
|
const cats = this.get('categoriesOrdered');
|
||||||
|
const len = cats.get('length');
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
cats.objectAt(i).set('position', i);
|
||||||
|
}
|
||||||
|
this.send('commit');
|
||||||
|
},
|
||||||
|
|
||||||
commit() {
|
commit() {
|
||||||
this.get('categoriesBuffered').forEach(bc => {
|
this.get('categoriesBuffered').forEach(bc => {
|
||||||
if (bc.get('hasBufferedChanges')) {
|
if (bc.get('hasBufferedChanges')) {
|
||||||
bc.applyBufferedChanges();
|
bc.applyBufferedChanges();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.propertyDidChange('categoriesGrouped');
|
this.propertyDidChange('categoriesBuffered');
|
||||||
},
|
},
|
||||||
|
|
||||||
saveOrder() {
|
saveOrder() {
|
||||||
|
|
|
@ -34,9 +34,8 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
|
||||||
setupController(controller, model) {
|
setupController(controller, model) {
|
||||||
controller.set("model", model);
|
controller.set("model", model);
|
||||||
|
|
||||||
// Only show either the Create Category or Create Topic button
|
|
||||||
this.controllerFor("navigation/categories").set("canCreateCategory", model.get("can_create_category"));
|
this.controllerFor("navigation/categories").set("canCreateCategory", model.get("can_create_category"));
|
||||||
this.controllerFor("navigation/categories").set("canCreateTopic", model.get("can_create_topic") && !model.get("can_create_category"));
|
this.controllerFor("navigation/categories").set("canCreateTopic", model.get("can_create_topic"));
|
||||||
|
|
||||||
this.openTopicDraft(model);
|
this.openTopicDraft(model);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,36 +1,33 @@
|
||||||
<div>
|
<div class="modal-body reorder-categories full-height-modal">
|
||||||
<div class="modal-body reorder-categories">
|
<div id="rc-scroll-anchor"></div>
|
||||||
<div id="rc-scroll-anchor"></div>
|
<table>
|
||||||
<table>
|
<thead>
|
||||||
<thead>
|
<th class="th-pos">{{i18n "categories.reorder.position"}}</th>
|
||||||
<th class="th-pos">Position</th>
|
<th class="th-cat">{{i18n "categories.category"}}</th>
|
||||||
<th class="th-cat">Category</th>
|
</thead>
|
||||||
</thead>
|
{{#each categoriesOrdered as |cat|}}
|
||||||
{{#each categoriesGrouped as |group|}}
|
<tr data-category-id="{{cat.id}}">
|
||||||
<tbody>
|
<td>
|
||||||
{{#each group.cats as |cat|}}
|
{{number-field number=cat.position}}
|
||||||
<tr data-category-id="{{cat.id}}">
|
{{d-button class="no-text" action="moveUp" actionParam=cat icon="arrow-up"}}
|
||||||
<td>
|
{{d-button class="no-text" action="moveDown" actionParam=cat icon="arrow-down"}}
|
||||||
{{number-field number=cat.position}}
|
{{#if cat.hasBufferedChanges}}
|
||||||
{{d-button class="no-text" action="moveUp" actionParam=cat icon="arrow-up"}}
|
{{d-button class="no-text" action="commit" icon="check"}}
|
||||||
{{d-button class="no-text" action="moveDown" actionParam=cat icon="arrow-down"}}
|
{{/if}}
|
||||||
{{#if cat.hasBufferedChanges}}
|
</td>
|
||||||
{{d-button class="no-text" action="commit" icon="check"}}
|
<td>{{category-badge cat allowUncategorized="true"}}</td>
|
||||||
{{/if}}
|
</tr>
|
||||||
</td>
|
{{/each}}
|
||||||
<td>{{category-badge cat allowUncategorized="true"}}</td>
|
</table>
|
||||||
</tr>
|
<div id="rc-scroll-bottom"></div>
|
||||||
{{/each}}
|
</div>
|
||||||
</tbody>
|
|
||||||
{{/each}}
|
<div class="modal-footer">
|
||||||
</table>
|
{{#if showFixIndices}}
|
||||||
<div id="rc-scroll-bottom"></div>
|
{{d-button action="fixIndices" icon="random" label="categories.reorder.fix_order" title="categories.reorder.fix_order_tooltip"}}
|
||||||
</div>
|
{{/if}}
|
||||||
|
{{#if showApplyAll}}
|
||||||
<div class="modal-footer">
|
{{d-button action="commit" icon="check" label="categories.reorder.apply_all"}}
|
||||||
{{#if showApplyAll}}
|
{{/if}}
|
||||||
{{d-button action="commit" icon="check" label="categories.reorder.apply_all"}}
|
{{d-button class="btn-primary" disabled=saveDisabled action="saveOrder" label="categories.reorder.save"}}
|
||||||
{{/if}}
|
|
||||||
{{d-button class="btn-primary" disabled=saveDisabled action="saveOrder" label="categories.reorder.save"}}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,10 +3,7 @@
|
||||||
{{navigation-bar navItems=navItems filterMode=filterMode}}
|
{{navigation-bar navItems=navItems filterMode=filterMode}}
|
||||||
|
|
||||||
{{#if canCreateCategory}}
|
{{#if canCreateCategory}}
|
||||||
{{d-button action="createCategory" icon="plus" label="category.create"}}
|
{{categories-admin-dropdown}}
|
||||||
{{#if siteSettings.fixed_category_positions}}
|
|
||||||
{{d-button action="reorderCategories" icon="random" label="category.reorder"}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if canCreateTopic}}
|
{{#if canCreateTopic}}
|
||||||
<button id="create-topic" class='btn btn-default' {{action "createTopic"}}><i class='fa fa-plus'></i>{{i18n 'topic.create'}}</button>
|
<button id="create-topic" class='btn btn-default' {{action "createTopic"}}><i class='fa fa-plus'></i>{{i18n 'topic.create'}}</button>
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
@import "common/admin/admin_base";
|
@import "common/admin/admin_base";
|
||||||
@import "common/admin/cat_reorder";
|
|
||||||
|
|
|
@ -26,3 +26,6 @@
|
||||||
padding-bottom: 150px;
|
padding-bottom: 150px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.category-admin-menu ul {
|
||||||
|
width: 320px;
|
||||||
|
}
|
|
@ -129,6 +129,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-body {
|
.modal-body {
|
||||||
|
&.full-height-modal {
|
||||||
|
max-height: calc(100vh - 150px);
|
||||||
|
}
|
||||||
textarea {
|
textarea {
|
||||||
width: 99%;
|
width: 99%;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
|
|
|
@ -370,8 +370,12 @@ en:
|
||||||
category: "Category"
|
category: "Category"
|
||||||
reorder:
|
reorder:
|
||||||
title: "Reorder Categories"
|
title: "Reorder Categories"
|
||||||
|
title_long: "Reorganize the category list"
|
||||||
|
fix_order: "Fix Positions"
|
||||||
|
fix_order_tooltip: "Not all categories have a unique position number, which may cause unexpected results."
|
||||||
save: "Save Order"
|
save: "Save Order"
|
||||||
apply_all: "Apply"
|
apply_all: "Apply"
|
||||||
|
position: "Position"
|
||||||
posts: "Posts"
|
posts: "Posts"
|
||||||
topics: "Topics"
|
topics: "Topics"
|
||||||
latest: "Latest"
|
latest: "Latest"
|
||||||
|
@ -1519,6 +1523,7 @@ en:
|
||||||
topic_template: "Topic Template"
|
topic_template: "Topic Template"
|
||||||
delete: 'Delete Category'
|
delete: 'Delete Category'
|
||||||
create: 'New Category'
|
create: 'New Category'
|
||||||
|
create_long: 'Create a new category'
|
||||||
save: 'Save Category'
|
save: 'Save Category'
|
||||||
slug: 'Category Slug'
|
slug: 'Category Slug'
|
||||||
slug_placeholder: '(Optional) dashed-words for url'
|
slug_placeholder: '(Optional) dashed-words for url'
|
||||||
|
@ -1555,7 +1560,6 @@ en:
|
||||||
add_permission: "Add Permission"
|
add_permission: "Add Permission"
|
||||||
this_year: "this year"
|
this_year: "this year"
|
||||||
position: "position"
|
position: "position"
|
||||||
reorder: "Reorder"
|
|
||||||
default_position: "Default Position"
|
default_position: "Default Position"
|
||||||
position_disabled: "Categories will be displayed in order of activity. To control the order of categories in lists, "
|
position_disabled: "Categories will be displayed in order of activity. To control the order of categories in lists, "
|
||||||
position_disabled_click: 'enable the "fixed category positions" setting.'
|
position_disabled_click: 'enable the "fixed category positions" setting.'
|
||||||
|
|
Loading…
Reference in New Issue