diff --git a/packages/frontend/src/pages/following-feed.vue b/packages/frontend/src/pages/following-feed.vue
index fac2857d46..d45f572739 100644
--- a/packages/frontend/src/pages/following-feed.vue
+++ b/packages/frontend/src/pages/following-feed.vue
@@ -39,13 +39,6 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
-
-
diff --git a/packages/frontend/src/scripts/following-feed-utils.ts b/packages/frontend/src/scripts/following-feed-utils.ts
new file mode 100644
index 0000000000..064d6b72e3
--- /dev/null
+++ b/packages/frontend/src/scripts/following-feed-utils.ts
@@ -0,0 +1,118 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { computed } from 'vue';
+import { defaultStore } from '@/store.js';
+import { deepMerge } from '@/scripts/merge.js';
+import { PageHeaderItem } from '@/types/page-header.js';
+import { i18n } from '@/i18n.js';
+import { popupMenu } from '@/os.js';
+
+export const followingTab = 'following' as const;
+export const mutualsTab = 'mutuals' as const;
+export const followersTab = 'followers' as const;
+export type FollowingFeedTab = typeof followingTab | typeof mutualsTab | typeof followersTab;
+
+export function createOptions(): PageHeaderItem {
+ const {
+ userList,
+ withNonPublic,
+ withQuotes,
+ withBots,
+ withReplies,
+ onlyFiles,
+ } = createModel();
+
+ return {
+ icon: 'ti ti-dots',
+ text: i18n.ts.options,
+ handler: ev =>
+ popupMenu([
+ {
+ type: 'switch',
+ text: i18n.ts.showNonPublicNotes,
+ ref: withNonPublic,
+ disabled: userList.value === 'followers',
+ },
+ {
+ type: 'switch',
+ text: i18n.ts.showQuotes,
+ ref: withQuotes,
+ },
+ {
+ type: 'switch',
+ text: i18n.ts.showBots,
+ ref: withBots,
+ },
+ {
+ type: 'switch',
+ text: i18n.ts.showReplies,
+ ref: withReplies,
+ disabled: onlyFiles,
+ },
+ {
+ type: 'divider',
+ },
+ {
+ type: 'switch',
+ text: i18n.ts.fileAttachedOnly,
+ ref: onlyFiles,
+ disabled: withReplies,
+ },
+ ], ev.currentTarget ?? ev.target),
+ };
+}
+
+export function createModel() {
+ const userList = computed({
+ get: () => defaultStore.reactiveState.followingFeed.value.userList,
+ set: value => saveFollowingFilter('userList', value),
+ });
+
+ const withNonPublic = computed({
+ get: () => {
+ if (userList.value === 'followers') return false;
+ return defaultStore.reactiveState.followingFeed.value.withNonPublic;
+ },
+ set: value => saveFollowingFilter('withNonPublic', value),
+ });
+ const withQuotes = computed({
+ get: () => defaultStore.reactiveState.followingFeed.value.withQuotes,
+ set: value => saveFollowingFilter('withQuotes', value),
+ });
+ const withBots = computed({
+ get: () => defaultStore.reactiveState.followingFeed.value.withBots,
+ set: value => saveFollowingFilter('withBots', value),
+ });
+ const withReplies = computed({
+ get: () => defaultStore.reactiveState.followingFeed.value.withReplies,
+ set: value => saveFollowingFilter('withReplies', value),
+ });
+ const onlyFiles = computed({
+ get: () => defaultStore.reactiveState.followingFeed.value.onlyFiles,
+ set: value => saveFollowingFilter('onlyFiles', value),
+ });
+
+ const remoteWarningDismissed = computed({
+ get: () => defaultStore.reactiveState.followingFeed.value.remoteWarningDismissed,
+ set: value => saveFollowingFilter('remoteWarningDismissed', value),
+ });
+
+ return {
+ userList,
+ withNonPublic,
+ withQuotes,
+ withBots,
+ withReplies,
+ onlyFiles,
+ remoteWarningDismissed,
+ };
+}
+
+// Based on timeline.saveTlFilter()
+function saveFollowingFilter(key: Key, value: (typeof defaultStore.state.followingFeed)[Key]) {
+ const out = deepMerge({ [key]: value }, defaultStore.state.followingFeed);
+ return defaultStore.set('followingFeed', out);
+}
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 5d0395d774..fd84ad2e4d 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -11,7 +11,7 @@ import darkTheme from '@@/themes/d-ice.json5';
import { miLocalStorage } from './local-storage.js';
import { searchEngineMap } from './scripts/search-engine-map.js';
import type { SoundType } from '@/scripts/sound.js';
-import type { FollowingFeedTab } from '@/pages/following-feed.vue';
+import type { FollowingFeedTab } from '@/scripts/following-feed-utils.js';
import { Storage } from '@/pizzax.js';
interface PostFormAction {