feat: allow re-ordering of auto-categorization rules

This commit is contained in:
Julian Lam
2026-02-11 12:53:59 -05:00
parent 78d7130c7a
commit fd43368a92
5 changed files with 67 additions and 21 deletions

View File

@@ -7,6 +7,8 @@ define('admin/settings/activitypub', [
'api', 'api',
'alerts', 'alerts',
'translator', 'translator',
'jquery-ui/widgets/sortable',
], function (Benchpress, bootbox, categorySelector, api, alerts, translator) { ], function (Benchpress, bootbox, categorySelector, api, alerts, translator) {
const ActivityPub = {}; const ActivityPub = {};
@@ -106,30 +108,55 @@ define('admin/settings/activitypub', [
function setupRules() { function setupRules() {
const rulesEl = document.getElementById('rules'); const rulesEl = document.getElementById('rules');
if (rulesEl) { if (!rulesEl) {
rulesEl.addEventListener('click', (e) => { return;
const subselector = e.target.closest('[data-action]'); }
if (subselector) {
const action = subselector.getAttribute('data-action');
switch (action) {
case 'rules.add': {
ActivityPub.throwRulesModal();
break;
}
case 'rules.delete': { rulesEl.addEventListener('click', (e) => {
const rid = subselector.closest('tr').getAttribute('data-rid'); const subselector = e.target.closest('[data-action]');
api.del(`/admin/activitypub/rules/${rid}`, {}).then(async (data) => { if (subselector) {
const html = await Benchpress.render('admin/settings/activitypub', { rules: data }, 'rules'); const action = subselector.getAttribute('data-action');
const tbodyEl = document.querySelector('#rules tbody'); switch (action) {
if (tbodyEl) { case 'rules.add': {
tbodyEl.innerHTML = html; ActivityPub.throwRulesModal();
} break;
}).catch(alerts.error); }
}
case 'rules.delete': {
const rid = subselector.closest('tr').getAttribute('data-rid');
api.del(`/admin/activitypub/rules/${rid}`, {}).then(async (data) => {
const html = await Benchpress.render('admin/settings/activitypub', { rules: data }, 'rules');
const tbodyEl = document.querySelector('#rules tbody');
if (tbodyEl) {
tbodyEl.innerHTML = html;
}
}).catch(alerts.error);
} }
} }
}
});
const tbodyEl = $(rulesEl).find('tbody');
tbodyEl.sortable({
handle: '.drag-handle',
helper: fixWidthHelper,
placeholder: 'ui-state-highlight',
axis: 'y',
update: function () {
var rids = [];
tbodyEl.find('tr').each(function () {
rids.push($(this).data('rid'));
});
api.put('/admin/activitypub/rules/order', { rids }).catch(alerts.error);
},
});
function fixWidthHelper(e, ui) {
ui.children().each(function () {
$(this).width($(this).width());
}); });
return ui;
} }
} }

View File

@@ -41,4 +41,12 @@ Rules.delete = async (rid) => {
db.sortedSetRemove('categorization:rid', rid), db.sortedSetRemove('categorization:rid', rid),
db.delete(`rid:${rid}`), db.delete(`rid:${rid}`),
]); ]);
};
Rules.reorder = async (rids) => {
const exists = await db.isSortedSetMembers('categorization:rid', rids);
rids = rids.filter((_, idx) => exists[idx]);
const scores = Array.from({ length: rids.length }, (_, idx) => idx);
await db.sortedSetAdd('categorization:rid', scores, rids);
}; };

View File

@@ -104,6 +104,12 @@ Admin.activitypub.deleteRule = async (req, res) => {
helpers.formatApiResponse(200, res, await activitypub.rules.list()); helpers.formatApiResponse(200, res, await activitypub.rules.list());
}; };
Admin.activitypub.reorderRules = async (req, res) => {
const { rids } = req.body;
await activitypub.rules.reorder(rids);
helpers.formatApiResponse(200, res, await activitypub.rules.list());
};
Admin.activitypub.addRelay = async (req, res) => { Admin.activitypub.addRelay = async (req, res) => {
const { url } = req.body; const { url } = req.body;

View File

@@ -27,6 +27,7 @@ module.exports = function () {
setupApiRoute(router, 'post', '/activitypub/rules', [...middlewares, middleware.checkRequired.bind(null, ['cid', 'value', 'type'])], controllers.write.admin.activitypub.addRule); setupApiRoute(router, 'post', '/activitypub/rules', [...middlewares, middleware.checkRequired.bind(null, ['cid', 'value', 'type'])], controllers.write.admin.activitypub.addRule);
setupApiRoute(router, 'delete', '/activitypub/rules/:rid', [...middlewares], controllers.write.admin.activitypub.deleteRule); setupApiRoute(router, 'delete', '/activitypub/rules/:rid', [...middlewares], controllers.write.admin.activitypub.deleteRule);
setupApiRoute(router, 'put', '/activitypub/rules/order', [...middlewares, middleware.checkRequired.bind(null, ['rids'])], controllers.write.admin.activitypub.reorderRules);
setupApiRoute(router, 'post', '/activitypub/relays', [...middlewares, middleware.checkRequired.bind(null, ['url'])], controllers.write.admin.activitypub.addRelay); setupApiRoute(router, 'post', '/activitypub/relays', [...middlewares, middleware.checkRequired.bind(null, ['url'])], controllers.write.admin.activitypub.addRelay);
setupApiRoute(router, 'delete', '/activitypub/relays/:url', [...middlewares], controllers.write.admin.activitypub.removeRelay); setupApiRoute(router, 'delete', '/activitypub/relays/:url', [...middlewares], controllers.write.admin.activitypub.removeRelay);

View File

@@ -51,14 +51,18 @@
<p>[[admin/settings/activitypub:rules-intro]]</p> <p>[[admin/settings/activitypub:rules-intro]]</p>
<table class="table table-striped" id="rules"> <table class="table table-striped" id="rules">
<thead> <thead>
<th></th>
<th>[[admin/settings/activitypub:rules.type]]</th> <th>[[admin/settings/activitypub:rules.type]]</th>
<th>[[admin/settings/activitypub:rules.value]]</th> <th>[[admin/settings/activitypub:rules.value]]</th>
<th>[[admin/settings/activitypub:rules.cid]]</th> <th>[[admin/settings/activitypub:rules.cid]]</th>
<th></th> <th></th>
</thead> </thead>
<tbody> <tbody class="sortable-container">
{{{ each rules }}} {{{ each rules }}}
<tr data-rid="{./rid}"> <tr data-rid="{./rid}">
<td class="align-items-center" style="cursor: move;">
<i class="fa fa-grip-lines text-muted drag-handle"></i>
</td>
<td>{./type}</td> <td>{./type}</td>
<td>{./value}</td> <td>{./value}</td>
<td>{./cid}</td> <td>{./cid}</td>