From 5d9fe75c9453f4e708f4dea168dd742df57f0da6 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 24 Jan 2026 09:26:19 +0100 Subject: [PATCH 01/12] feat(core): implement backend for group position --- ...24_000000_add_position_to_groups_table.php | 40 +++++++++++++++++++ .../Api/Controller/OrderGroupsController.php | 40 +++++++++++++++++++ .../core/src/Api/Resource/GroupResource.php | 1 + framework/core/src/Api/routes.php | 13 ++++++ framework/core/src/Group/Group.php | 1 + framework/core/src/Group/GroupFactory.php | 1 + 6 files changed, 96 insertions(+) create mode 100644 framework/core/migrations/2026_01_24_000000_add_position_to_groups_table.php create mode 100644 framework/core/src/Api/Controller/OrderGroupsController.php diff --git a/framework/core/migrations/2026_01_24_000000_add_position_to_groups_table.php b/framework/core/migrations/2026_01_24_000000_add_position_to_groups_table.php new file mode 100644 index 0000000000..e50e160933 --- /dev/null +++ b/framework/core/migrations/2026_01_24_000000_add_position_to_groups_table.php @@ -0,0 +1,40 @@ + function (Builder $schema) { + $schema->table('groups', function (Blueprint $table) { + $table->integer('position')->after('is_hidden')->nullable(); + }); + + $db = $schema->getConnection(); + + $ids = $db->table('groups') + ->orderBy('id') + ->pluck('id'); + + $position = 0; + foreach ($ids as $id) { + $db->table('groups') + ->where('id', $id) + ->update(['position' => $position]); + + $position++; + } + }, + + 'down' => function (Builder $schema) { + $schema->table('groups', function (Blueprint $table) { + $table->dropColumn('position'); + }); + } +]; diff --git a/framework/core/src/Api/Controller/OrderGroupsController.php b/framework/core/src/Api/Controller/OrderGroupsController.php new file mode 100644 index 0000000000..ddb4a7431c --- /dev/null +++ b/framework/core/src/Api/Controller/OrderGroupsController.php @@ -0,0 +1,40 @@ +assertAdmin(); + + $order = Arr::get($request->getParsedBody(), 'order'); + + if ($order === null) { + return new EmptyResponse(422); + } + + Group::query()->update(['position' => null]); + + foreach ($order as $position => $id) { + Group::where('id', $id)->update(['position' => $position]); + } + + return new EmptyResponse(204); + } +} diff --git a/framework/core/src/Api/Resource/GroupResource.php b/framework/core/src/Api/Resource/GroupResource.php index d73706353f..0afbf8dd71 100644 --- a/framework/core/src/Api/Resource/GroupResource.php +++ b/framework/core/src/Api/Resource/GroupResource.php @@ -93,6 +93,7 @@ public function fields(): array ->writable(), Schema\Boolean::make('isHidden') ->writable(), + Schema\Integer::make('position') ]; } diff --git a/framework/core/src/Api/routes.php b/framework/core/src/Api/routes.php index 9ae4cbb091..213c229ddb 100644 --- a/framework/core/src/Api/routes.php +++ b/framework/core/src/Api/routes.php @@ -53,6 +53,19 @@ $route->toController(Controller\SendConfirmationEmailController::class) ); + /* + |-------------------------------------------------------------------------- + | Groups + |-------------------------------------------------------------------------- + */ + + // Change order of groups + $map->post( + '/groups/order', + 'groups.order', + $route->toController(Controller\OrderGroupsController::class) + ); + /* |-------------------------------------------------------------------------- | Notifications diff --git a/framework/core/src/Group/Group.php b/framework/core/src/Group/Group.php index 7681aad2af..fba77c2441 100644 --- a/framework/core/src/Group/Group.php +++ b/framework/core/src/Group/Group.php @@ -27,6 +27,7 @@ * @property string|null $color * @property string|null $icon * @property bool $is_hidden + * @property int $position * @property-read \Illuminate\Database\Eloquent\Collection $users * @property-read \Illuminate\Database\Eloquent\Collection $permissions */ diff --git a/framework/core/src/Group/GroupFactory.php b/framework/core/src/Group/GroupFactory.php index 0064e43334..521c3710d4 100644 --- a/framework/core/src/Group/GroupFactory.php +++ b/framework/core/src/Group/GroupFactory.php @@ -21,6 +21,7 @@ public function definition(): array 'color' => $this->faker->hexColor, 'icon' => null, 'is_hidden' => false, + 'position' => 0, ]; } } From af9f285971195155220494e1d8b1fbeee5a48d9c Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 24 Jan 2026 09:26:44 +0100 Subject: [PATCH 02/12] chore(core): add new position attribute to frontend model --- framework/core/js/src/common/models/Group.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/framework/core/js/src/common/models/Group.ts b/framework/core/js/src/common/models/Group.ts index 1100c572fa..120ede07b8 100644 --- a/framework/core/js/src/common/models/Group.ts +++ b/framework/core/js/src/common/models/Group.ts @@ -22,4 +22,8 @@ export default class Group extends Model { isHidden() { return Model.attribute('isHidden').call(this); } + + position() { + return Model.attribute('position').call(this); + } } From acccd8773687df8262cd67704c62cb3bd462f8c6 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 24 Jan 2026 09:27:12 +0100 Subject: [PATCH 03/12] chore(core): export reusable `sortGroups` util --- framework/core/js/src/common/common.ts | 1 + framework/core/js/src/common/utils/sortGroups.ts | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 framework/core/js/src/common/utils/sortGroups.ts diff --git a/framework/core/js/src/common/common.ts b/framework/core/js/src/common/common.ts index 7c8d25c8b5..2c6ed9129e 100644 --- a/framework/core/js/src/common/common.ts +++ b/framework/core/js/src/common/common.ts @@ -20,6 +20,7 @@ import './utils/abbreviateNumber'; import './utils/escapeRegExp'; import './utils/string'; import './utils/throttleDebounce'; +import './utils/sortGroups'; import './utils/Stream'; import './utils/SubtreeRetainer'; import './utils/setRouteWithForcedRefresh'; diff --git a/framework/core/js/src/common/utils/sortGroups.ts b/framework/core/js/src/common/utils/sortGroups.ts new file mode 100644 index 0000000000..831f1cfae8 --- /dev/null +++ b/framework/core/js/src/common/utils/sortGroups.ts @@ -0,0 +1,5 @@ +import type Group from '../models/Group'; + +export default function sortGroups(groups: Group[]) { + return groups.slice().sort((a, b) => a.position() - b.position()); +} From 325db3f4fed5e386295241da08118c35cc5bd05f Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 24 Jan 2026 09:27:48 +0100 Subject: [PATCH 04/12] feat(core, mentions): use new `sortGroups` util --- .../src/forum/mentionables/GroupMention.tsx | 9 +++-- .../admin/components/PermissionDropdown.tsx | 30 +++++++------- .../src/admin/components/PermissionsPage.tsx | 20 +++++----- .../src/common/components/EditUserModal.tsx | 40 ++++++++----------- 4 files changed, 49 insertions(+), 50 deletions(-) diff --git a/extensions/mentions/js/src/forum/mentionables/GroupMention.tsx b/extensions/mentions/js/src/forum/mentionables/GroupMention.tsx index 7703db5dd7..6793952ab7 100644 --- a/extensions/mentions/js/src/forum/mentionables/GroupMention.tsx +++ b/extensions/mentions/js/src/forum/mentionables/GroupMention.tsx @@ -5,6 +5,7 @@ import type Mithril from 'mithril'; import Badge from 'flarum/common/components/Badge'; import highlight from 'flarum/common/helpers/highlight'; import type AtMentionFormat from './formats/AtMentionFormat'; +import sortGroups from 'flarum/common/utils/sortGroups'; export default class GroupMention extends MentionableModel { type(): string { @@ -13,9 +14,11 @@ export default class GroupMention extends MentionableModel('groups').filter((g: Group) => { - return g.id() !== Group.GUEST_ID && g.id() !== Group.MEMBER_ID; - }) + sortGroups( + app.store.all('groups').filter((g: Group) => { + return g.id() !== Group.GUEST_ID && g.id() !== Group.MEMBER_ID; + }) + ) ); } diff --git a/framework/core/js/src/admin/components/PermissionDropdown.tsx b/framework/core/js/src/admin/components/PermissionDropdown.tsx index e605e9d0bf..138193418d 100644 --- a/framework/core/js/src/admin/components/PermissionDropdown.tsx +++ b/framework/core/js/src/admin/components/PermissionDropdown.tsx @@ -5,6 +5,7 @@ import Separator from '../../common/components/Separator'; import Group from '../../common/models/Group'; import Badge from '../../common/components/Badge'; import GroupBadge from '../../common/components/GroupBadge'; +import sortGroups from '../../common/utils/sortGroups'; import Mithril from 'mithril'; function badgeForId(id: string) { @@ -95,21 +96,20 @@ export default class PermissionDropdown('groups') - .filter((group) => !excludedGroups.includes(group.id()!)) - .map((group) => ( - - )); + const availableGroups = sortGroups(app.store.all('groups').filter((group) => !excludedGroups.includes(group.id()!))); + + const groupButtons = availableGroups.map((group) => ( + + )); children.push(...groupButtons); } diff --git a/framework/core/js/src/admin/components/PermissionsPage.tsx b/framework/core/js/src/admin/components/PermissionsPage.tsx index 13557c1ed9..41820f69aa 100644 --- a/framework/core/js/src/admin/components/PermissionsPage.tsx +++ b/framework/core/js/src/admin/components/PermissionsPage.tsx @@ -6,6 +6,7 @@ import PermissionGrid from './PermissionGrid'; import AdminPage from './AdminPage'; import Icon from '../../common/components/Icon'; import SettingDropdown from './SettingDropdown'; +import sortGroups from '../../common/utils/sortGroups'; export default class PermissionsPage extends AdminPage { headerInfo() { @@ -18,18 +19,19 @@ export default class PermissionsPage extends AdminPage { } content() { + const availableGroups = sortGroups( + app.store.all('groups').filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()!) === -1) + ); + return ( <>
- {app.store - .all('groups') - .filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()!) === -1) - .map((group) => ( - - ))} + {availableGroups.map((group) => ( + + ))}
, 10 From 37cc6e114945a3f0e22d3a33650b32afe067507e Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 24 Jan 2026 15:40:56 +0100 Subject: [PATCH 05/12] chore(core): add TS types for sortablejs --- framework/core/js/package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/framework/core/js/package.json b/framework/core/js/package.json index 6e8909151e..21badd79fa 100644 --- a/framework/core/js/package.json +++ b/framework/core/js/package.json @@ -27,6 +27,7 @@ "@types/jquery": "^3.5.10", "@types/mithril": "^2.0.8", "@types/punycode": "^2.1.0", + "@types/sortablejs": "^1.15.9", "@types/textarea-caret": "^3.0.1", "@types/ua-parser-js": "^0.7.36", "bundlewatch": "^0.3.2", diff --git a/yarn.lock b/yarn.lock index 2c30d56408..c4e34a2f6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1587,6 +1587,11 @@ resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.8.tgz#518609aefb797da19bf222feb199e8f653ff7627" integrity sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg== +"@types/sortablejs@^1.15.9": + version "1.15.9" + resolved "https://registry.yarnpkg.com/@types/sortablejs/-/sortablejs-1.15.9.tgz#82d2337f54d4db827914d80dee5cf65539ae3c8b" + integrity sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ== + "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" From 8af4b74076094545e5e2bfa6eebb8e1502150a1b Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 24 Jan 2026 15:47:02 +0100 Subject: [PATCH 06/12] feat(core): implement reusable `GroupBar` --- .../core/js/src/admin/components/GroupBar.tsx | 80 +++++++++++++++++++ .../src/admin/components/PermissionsPage.tsx | 20 +---- .../core/less/admin/PermissionsPage.less | 13 ++- 3 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 framework/core/js/src/admin/components/GroupBar.tsx diff --git a/framework/core/js/src/admin/components/GroupBar.tsx b/framework/core/js/src/admin/components/GroupBar.tsx new file mode 100644 index 0000000000..0c74b8f494 --- /dev/null +++ b/framework/core/js/src/admin/components/GroupBar.tsx @@ -0,0 +1,80 @@ +import sortable from 'sortablejs'; + +import app from '../../admin/app'; +import Component, { ComponentAttrs } from '../../common/Component'; +import GroupBadge from '../../common/components/GroupBadge'; +import Icon from '../../common/components/Icon'; +import EditGroupModal from './EditGroupModal'; +import sortGroups from '../../common/utils/sortGroups'; + +import type Group from '../../common/models/Group'; +import type Mithril from 'mithril'; + +export interface IGroupBarAttrs extends ComponentAttrs { + groups: Group[]; +} + +export default abstract class GroupBar extends Component { + groups: Group[] = []; + + oninit(vnode: Mithril.Vnode) { + super.oninit(vnode); + + this.groups = sortGroups(this.attrs.groups); + } + + view(): JSX.Element { + return ( +
+ {this.groups.map((group) => ( + + ))} + +
+ ); + } + + onGroupBarCreate(vnode: Mithril.VnodeDOM) { + sortable.create(vnode.dom as HTMLElement, { + group: 'groups', + delay: 50, + delayOnTouchOnly: true, + touchStartThreshold: 5, + animation: 150, + swapThreshold: 0.65, + dragClass: 'Group-Sortable-Dragging', + ghostClass: 'Group-Sortable-Placeholder', + + filter: '.Group--add', + onMove: (evt) => !evt.related.classList.contains('Group--add'), + + onSort: (e) => this.onSortUpdate(), + }); + } + + onSortUpdate() { + const order = this.$('.Group:not(.Group--add)') + .map(function () { + return $(this).data('id'); + }) + .get(); + + order.forEach((id, i) => { + app.store.getById('groups', id)?.pushData({ + attributes: { position: i }, + }); + }); + + app.request({ + url: app.forum.attribute('apiUrl') + '/groups/order', + method: 'POST', + body: { order }, + }); + } +} diff --git a/framework/core/js/src/admin/components/PermissionsPage.tsx b/framework/core/js/src/admin/components/PermissionsPage.tsx index 41820f69aa..f40cd8ed5d 100644 --- a/framework/core/js/src/admin/components/PermissionsPage.tsx +++ b/framework/core/js/src/admin/components/PermissionsPage.tsx @@ -1,12 +1,9 @@ import app from '../../admin/app'; -import GroupBadge from '../../common/components/GroupBadge'; -import EditGroupModal from './EditGroupModal'; +import GroupBar from './GroupBar'; import Group from '../../common/models/Group'; import PermissionGrid from './PermissionGrid'; import AdminPage from './AdminPage'; -import Icon from '../../common/components/Icon'; import SettingDropdown from './SettingDropdown'; -import sortGroups from '../../common/utils/sortGroups'; export default class PermissionsPage extends AdminPage { headerInfo() { @@ -19,23 +16,12 @@ export default class PermissionsPage extends AdminPage { } content() { - const availableGroups = sortGroups( - app.store.all('groups').filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()!) === -1) - ); + const availableGroups = app.store.all('groups').filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()!) === -1); return ( <>
- {availableGroups.map((group) => ( - - ))} - +
diff --git a/framework/core/less/admin/PermissionsPage.less b/framework/core/less/admin/PermissionsPage.less index 8c2a40862c..f6fb9e86fc 100644 --- a/framework/core/less/admin/PermissionsPage.less +++ b/framework/core/less/admin/PermissionsPage.less @@ -1,10 +1,15 @@ -.PermissionsPage-groups { +.GroupBar { background: var(--control-bg); - border-radius: var(--border-radius); - display: block; - overflow-x: auto; + display: flex; + gap: 10px; + flex-wrap: wrap; padding: 10px; + border-radius: var(--border-radius); } +.Group-Sortable-Placeholder { + outline: 2px dashed var(--body-bg); + border-radius: var(--border-radius); + } .Group { width: 90px; display: inline-block; From 75c54a5bac49e1e4fe42bdb4b43dc6f3aecd65d1 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 24 Jan 2026 16:32:10 +0100 Subject: [PATCH 07/12] chore(core): adjust groupfactory --- framework/core/src/Group/GroupFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/core/src/Group/GroupFactory.php b/framework/core/src/Group/GroupFactory.php index 521c3710d4..52b424cb95 100644 --- a/framework/core/src/Group/GroupFactory.php +++ b/framework/core/src/Group/GroupFactory.php @@ -21,7 +21,7 @@ public function definition(): array 'color' => $this->faker->hexColor, 'icon' => null, 'is_hidden' => false, - 'position' => 0, + 'position' => null, ]; } } From fdc1557d037917047ebe50c28ccc1fd539af4cca Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 24 Jan 2026 16:33:06 +0100 Subject: [PATCH 08/12] test(core): implement new group order test --- .../integration/api/groups/OrderTest.php | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 framework/core/tests/integration/api/groups/OrderTest.php diff --git a/framework/core/tests/integration/api/groups/OrderTest.php b/framework/core/tests/integration/api/groups/OrderTest.php new file mode 100644 index 0000000000..2c311e4128 --- /dev/null +++ b/framework/core/tests/integration/api/groups/OrderTest.php @@ -0,0 +1,117 @@ +prepareDatabase([ + User::class => [ + $this->normalUser(), + ], + Group::class => [ + ['id' => 5, 'name_singular' => 'Developer', 'name_plural' => 'Developers', 'is_hidden' => false, 'position' => 5], + ['id' => 6, 'name_singular' => 'Subscriber', 'name_plural' => 'Subscribers', 'is_hidden' => false, 'position' => 6], + ['id' => 7, 'name_singular' => 'VIP', 'name_plural' => 'VIPs', 'is_hidden' => false, 'position' => 7], + ], + ]); + } + + #[Test] + public function admin_can_reorder_groups() + { + $response = $this->send( + $this->request('POST', '/api/groups/order', [ + 'authenticatedAs' => 1, + 'json' => [ + 'order' => [6, 5], + ], + ]) + ); + + $this->assertEquals(204, $response->getStatusCode(), (string) $response->getBody()); + + $this->assertSame(1, Group::findOrFail(5)->position, 'Group 5 should be moved to position 1'); + $this->assertSame(0, Group::findOrFail(6)->position, 'Group 6 should be moved to position 0'); + + $this->assertNull(Group::findOrFail(7)->position, 'Group 7 should have null position when not included'); + } + + #[Test] + public function non_admin_cannot_reorder_groups() + { + $response = $this->send( + $this->request('POST', '/api/groups/order', [ + 'authenticatedAs' => 2, + 'json' => [ + 'order' => [6, 5], + ], + ]) + ); + + $this->assertEquals(403, $response->getStatusCode(), (string) $response->getBody()); + + $this->assertSame(5, Group::findOrFail(5)->position); + $this->assertSame(6, Group::findOrFail(6)->position); + $this->assertSame(7, Group::findOrFail(7)->position); + } + + #[Test] + public function rejects_missing_order_payload() + { + $response = $this->send( + $this->request('POST', '/api/groups/order', [ + 'authenticatedAs' => 1, + 'json' => [ + // empty payload + ], + ]) + ); + + $this->assertEquals(422, $response->getStatusCode(), (string) $response->getBody()); + + $this->assertSame(5, Group::findOrFail(5)->position); + $this->assertSame(6, Group::findOrFail(6)->position); + $this->assertSame(7, Group::findOrFail(7)->position); + } + + #[Test] + public function rejects_null_order() + { + $response = $this->send( + $this->request('POST', '/api/groups/order', [ + 'authenticatedAs' => 1, + 'json' => [ + 'order' => null, + ], + ]) + ); + + $this->assertEquals(422, $response->getStatusCode(), (string) $response->getBody()); + + $this->assertSame(5, Group::findOrFail(5)->position); + $this->assertSame(6, Group::findOrFail(6)->position); + $this->assertSame(7, Group::findOrFail(7)->position); + } +} \ No newline at end of file From 1996a8ff1b56d3b719f34b2c1119772dfa0c32b7 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 24 Jan 2026 16:37:21 +0100 Subject: [PATCH 09/12] style(core): change formatting --- framework/core/tests/integration/api/groups/OrderTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/core/tests/integration/api/groups/OrderTest.php b/framework/core/tests/integration/api/groups/OrderTest.php index 2c311e4128..b5f53a6415 100644 --- a/framework/core/tests/integration/api/groups/OrderTest.php +++ b/framework/core/tests/integration/api/groups/OrderTest.php @@ -27,7 +27,7 @@ protected function setUp(): void parent::setUp(); $this->prepareDatabase([ - User::class => [ + User::class => [ $this->normalUser(), ], Group::class => [ @@ -58,7 +58,7 @@ public function admin_can_reorder_groups() $this->assertNull(Group::findOrFail(7)->position, 'Group 7 should have null position when not included'); } - #[Test] + #[Test] public function non_admin_cannot_reorder_groups() { $response = $this->send( @@ -114,4 +114,4 @@ public function rejects_null_order() $this->assertSame(6, Group::findOrFail(6)->position); $this->assertSame(7, Group::findOrFail(7)->position); } -} \ No newline at end of file +} From fc0ad2093488b233834fd514bcf8bd317a39d7f2 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Thu, 29 Jan 2026 08:38:14 +0100 Subject: [PATCH 10/12] chore(core): add missing sortable frontend dep --- framework/core/js/package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/framework/core/js/package.json b/framework/core/js/package.json index 21badd79fa..2762541c98 100644 --- a/framework/core/js/package.json +++ b/framework/core/js/package.json @@ -17,6 +17,7 @@ "mithril": "^2.2", "nanoid": "^3.1.30", "punycode": "^2.1.1", + "sortablejs": "^1.15.6", "textarea-caret": "^3.1.0", "throttle-debounce": "^3.0.1" }, diff --git a/yarn.lock b/yarn.lock index c4e34a2f6f..676d8ac422 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4969,6 +4969,11 @@ sortablejs@^1.14.0: resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.3.tgz#033668db5ebfb11167d1249ab88e748f27959e29" integrity sha512-zdK3/kwwAK1cJgy1rwl1YtNTbRmc8qW/+vgXf75A7NHag5of4pyI6uK86ktmQETyWRH7IGaE73uZOOBcGxgqZg== +sortablejs@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.6.tgz#ff93699493f5b8ab8d828f933227b4988df1d393" + integrity sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A== + source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" From 8f7c85701f5b84c9c49b205e75cc283a48a1905e Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Thu, 29 Jan 2026 09:24:27 +0100 Subject: [PATCH 11/12] chore: debugging --- framework/core/js/package.json | 2 +- yarn.lock | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/framework/core/js/package.json b/framework/core/js/package.json index 2762541c98..607a8f82f2 100644 --- a/framework/core/js/package.json +++ b/framework/core/js/package.json @@ -17,7 +17,7 @@ "mithril": "^2.2", "nanoid": "^3.1.30", "punycode": "^2.1.1", - "sortablejs": "^1.15.6", + "sortablejs": "^1.14.0", "textarea-caret": "^3.1.0", "throttle-debounce": "^3.0.1" }, diff --git a/yarn.lock b/yarn.lock index 676d8ac422..c4e34a2f6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4969,11 +4969,6 @@ sortablejs@^1.14.0: resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.3.tgz#033668db5ebfb11167d1249ab88e748f27959e29" integrity sha512-zdK3/kwwAK1cJgy1rwl1YtNTbRmc8qW/+vgXf75A7NHag5of4pyI6uK86ktmQETyWRH7IGaE73uZOOBcGxgqZg== -sortablejs@^1.15.6: - version "1.15.6" - resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.6.tgz#ff93699493f5b8ab8d828f933227b4988df1d393" - integrity sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A== - source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" From f01495919ed37aad3feabef1f9bc3cc3923224c3 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Thu, 29 Jan 2026 09:59:59 +0100 Subject: [PATCH 12/12] chore: debugging --- framework/core/js/webpack.config.cjs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/framework/core/js/webpack.config.cjs b/framework/core/js/webpack.config.cjs index 04df395ffc..c0f83b3bce 100755 --- a/framework/core/js/webpack.config.cjs +++ b/framework/core/js/webpack.config.cjs @@ -5,4 +5,9 @@ module.exports = merge(config(), { output: { library: 'flarum.core', }, + resolve: { + alias: { + sortablejs: require.resolve('sortablejs/Sortable.js'), + }, + }, });