From fcc413e659a4b698634b1a157adf708966b4ad15 Mon Sep 17 00:00:00 2001 From: Gianni Guida Date: Wed, 29 Apr 2026 14:40:55 +0200 Subject: [PATCH 1/2] feat: add setting to control sticky pinning on All Discussions page Adds an admin toggle (default: enabled) that gates unread-only sticky pinning on /all. When disabled, stickied discussions appear at their natural last_posted_at position. Tag pages are unaffected. --- extensions/sticky/extend.php | 3 +++ extensions/sticky/js/src/admin/index.js | 26 ++++++++++++------- extensions/sticky/locale/en.yml | 5 ++++ .../src/PinStickiedDiscussionsToTop.php | 18 +++++++++++++ 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/extensions/sticky/extend.php b/extensions/sticky/extend.php index 5e08a4a083..714eca13a0 100644 --- a/extensions/sticky/extend.php +++ b/extensions/sticky/extend.php @@ -49,6 +49,9 @@ new Extend\Locales(__DIR__.'/locale'), + (new Extend\Settings()) + ->default('flarum-sticky.pin_sticky_on_all_discussions', true), + (new Extend\Event()) ->listen(Saving::class, SaveStickyToDatabase::class) ->listen(DiscussionWasStickied::class, [Listener\CreatePostWhenDiscussionIsStickied::class, 'whenDiscussionWasStickied']) diff --git a/extensions/sticky/js/src/admin/index.js b/extensions/sticky/js/src/admin/index.js index 29b4448a83..0e11729a03 100644 --- a/extensions/sticky/js/src/admin/index.js +++ b/extensions/sticky/js/src/admin/index.js @@ -1,13 +1,21 @@ import app from 'flarum/admin/app'; app.initializers.add('flarum-sticky', () => { - app.extensionData.for('flarum-sticky').registerPermission( - { - icon: 'fas fa-thumbtack', - label: app.translator.trans('flarum-sticky.admin.permissions.sticky_discussions_label'), - permission: 'discussion.sticky', - }, - 'moderate', - 95 - ); + app.extensionData + .for('flarum-sticky') + .registerSetting({ + setting: 'flarum-sticky.pin_sticky_on_all_discussions', + label: app.translator.trans('flarum-sticky.admin.settings.pin_sticky_on_all_discussions_label'), + help: app.translator.trans('flarum-sticky.admin.settings.pin_sticky_on_all_discussions_help'), + type: 'boolean', + }) + .registerPermission( + { + icon: 'fas fa-thumbtack', + label: app.translator.trans('flarum-sticky.admin.permissions.sticky_discussions_label'), + permission: 'discussion.sticky', + }, + 'moderate', + 95 + ); }); diff --git a/extensions/sticky/locale/en.yml b/extensions/sticky/locale/en.yml index 414c04a888..1b52914124 100644 --- a/extensions/sticky/locale/en.yml +++ b/extensions/sticky/locale/en.yml @@ -11,6 +11,11 @@ flarum-sticky: permissions: sticky_discussions_label: Sticky discussions + # These translations are used in the Settings page of the admin interface. + settings: + pin_sticky_on_all_discussions_label: Pin stickied discussions on the All Discussions page + pin_sticky_on_all_discussions_help: When enabled (default), unread stickied discussions are pinned to the top of the All Discussions page. When disabled, stickied discussions appear at their natural position by latest activity. Tag pages always pin stickied discussions to the top regardless of this setting. + # Translations in this namespace are used by the forum user interface. forum: diff --git a/extensions/sticky/src/PinStickiedDiscussionsToTop.php b/extensions/sticky/src/PinStickiedDiscussionsToTop.php index 44e9562045..98ee22485e 100755 --- a/extensions/sticky/src/PinStickiedDiscussionsToTop.php +++ b/extensions/sticky/src/PinStickiedDiscussionsToTop.php @@ -11,10 +11,21 @@ use Flarum\Filter\FilterState; use Flarum\Query\QueryCriteria; +use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Tags\Query\TagFilterGambit; class PinStickiedDiscussionsToTop { + /** + * @var SettingsRepositoryInterface + */ + protected $settings; + + public function __construct(SettingsRepositoryInterface $settings) + { + $this->settings = $settings; + } + public function __invoke(FilterState $filterState, QueryCriteria $criteria) { if ($criteria->sortIsDefault) { @@ -36,6 +47,13 @@ public function __invoke(FilterState $filterState, QueryCriteria $criteria) return; } + // On "all discussions", admins can disable sticky pinning entirely. + // When disabled, stickied discussions appear at their natural + // last_posted_at position rather than being floated to the top. + if (! $this->settings->get('flarum-sticky.pin_sticky_on_all_discussions', true)) { + return; + } + // Otherwise, if we are viewing "all discussions", only pin stickied // discussions to the top if they are unread. To do this in a // performant way we create another query which will select all From 1e7a95492f4d6843deab0c94f6e448f686a10814 Mon Sep 17 00:00:00 2001 From: Gianni Guida Date: Wed, 29 Apr 2026 14:59:50 +0200 Subject: [PATCH 2/2] feat: add tests for sticky pinning behavior on all when setting is disabled --- .../integration/api/ListDiscussionsTest.php | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/extensions/sticky/tests/integration/api/ListDiscussionsTest.php b/extensions/sticky/tests/integration/api/ListDiscussionsTest.php index 3f595acbbe..89ef35fda7 100644 --- a/extensions/sticky/tests/integration/api/ListDiscussionsTest.php +++ b/extensions/sticky/tests/integration/api/ListDiscussionsTest.php @@ -117,4 +117,60 @@ public function list_discussions_shows_stick_first_on_a_tag() $this->assertEquals([3, 1, 2, 4], Arr::pluck($data['data'], 'id')); } + + /** @test */ + public function list_discussions_does_not_pin_sticky_on_all_when_setting_disabled_as_guest() + { + $this->setting('flarum-sticky.pin_sticky_on_all_discussions', '0'); + + $response = $this->send( + $this->request('GET', '/api/discussions') + ); + + $this->assertEquals(200, $response->getStatusCode()); + + $data = json_decode($response->getBody()->getContents(), true); + + $this->assertEquals([2, 4, 3, 1], Arr::pluck($data['data'], 'id')); + } + + /** @test */ + public function list_discussions_does_not_pin_unread_sticky_on_all_when_setting_disabled_as_user() + { + $this->setting('flarum-sticky.pin_sticky_on_all_discussions', '0'); + + $response = $this->send( + $this->request('GET', '/api/discussions', [ + 'authenticatedAs' => 2 + ]) + ); + + $this->assertEquals(200, $response->getStatusCode()); + + $data = json_decode($response->getBody()->getContents(), true); + + $this->assertEquals([2, 4, 3, 1], Arr::pluck($data['data'], 'id')); + } + + /** @test */ + public function list_discussions_pins_sticky_on_a_tag_when_setting_disabled() + { + $this->setting('flarum-sticky.pin_sticky_on_all_discussions', '0'); + + $response = $this->send( + $this->request('GET', '/api/discussions', [ + 'authenticatedAs' => 3 + ])->withQueryParams([ + 'filter' => [ + 'tag' => 'general' + ] + ]) + ); + + $this->assertEquals(200, $response->getStatusCode()); + + $data = json_decode($response->getBody()->getContents(), true); + + $this->assertEquals([3, 1, 2, 4], Arr::pluck($data['data'], 'id')); + } }