From f861bec1a767eed7bfcc78f1a705b04031dcf0da Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Tue, 25 Mar 2025 17:32:18 -0400
Subject: update references from /scripts/ to /utility/

---
 packages/frontend/src/components/SkFollowingRecentNotes.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'packages/frontend/src/components/SkFollowingRecentNotes.vue')

diff --git a/packages/frontend/src/components/SkFollowingRecentNotes.vue b/packages/frontend/src/components/SkFollowingRecentNotes.vue
index 7e71065082..2e0d8f67e0 100644
--- a/packages/frontend/src/components/SkFollowingRecentNotes.vue
+++ b/packages/frontend/src/components/SkFollowingRecentNotes.vue
@@ -31,8 +31,8 @@ import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import SkFollowingFeedEntry from '@/components/SkFollowingFeedEntry.vue';
 import { $i } from '@/account.js';
-import { checkWordMute } from '@/scripts/check-word-mute.js';
-import { FollowingFeedTab } from '@/scripts/following-feed-utils.js';
+import { checkWordMute } from '@/utility/check-word-mute.js';
+import { FollowingFeedTab } from '@/utility/following-feed-utils.js';
 import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
 
 const props = defineProps<{
-- 
cgit v1.2.3-freya


From 906bbb4baa512aa7fcd1073adf310cddbbffa06b Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Tue, 1 Apr 2025 15:22:04 -0400
Subject: correct references to removed _ghost class

---
 packages/frontend/src/components/MkSchedulePostListDialog.vue | 2 +-
 packages/frontend/src/components/SkFollowingRecentNotes.vue   | 2 +-
 packages/frontend/src/pages/user/home.vue                     | 2 +-
 packages/frontend/src/pages/user/index.timeline.vue           | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

(limited to 'packages/frontend/src/components/SkFollowingRecentNotes.vue')

diff --git a/packages/frontend/src/components/MkSchedulePostListDialog.vue b/packages/frontend/src/components/MkSchedulePostListDialog.vue
index d0716ead79..0bcbb41192 100644
--- a/packages/frontend/src/components/MkSchedulePostListDialog.vue
+++ b/packages/frontend/src/components/MkSchedulePostListDialog.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkPagination ref="paginationEl" :pagination="pagination">
 			<template #empty>
 				<div class="_fullinfo">
-					<img :src="infoImageUrl" class="_ghost"/>
+					<img :src="infoImageUrl" draggable="false"/>
 					<div>{{ i18n.ts.nothing }}</div>
 				</div>
 			</template>
diff --git a/packages/frontend/src/components/SkFollowingRecentNotes.vue b/packages/frontend/src/components/SkFollowingRecentNotes.vue
index 2e0d8f67e0..fc72639095 100644
--- a/packages/frontend/src/components/SkFollowingRecentNotes.vue
+++ b/packages/frontend/src/components/SkFollowingRecentNotes.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<MkPagination ref="latestNotesPaging" :pagination="latestNotesPagination" @init="onListReady">
 		<template #empty>
 			<div class="_fullinfo">
-				<img :src="infoImageUrl" class="_ghost" :alt="i18n.ts.noNotes" aria-hidden="true"/>
+				<img :src="infoImageUrl" draggable="false" :alt="i18n.ts.noNotes" aria-hidden="true"/>
 				<div>{{ i18n.ts.noNotes }}</div>
 			</div>
 		</template>
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 9f2b3f3585..0f2338d16a 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -171,7 +171,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkLazy>
 						<div v-if="noteview === 'pinned'" class="_gaps">
 							<div v-if="user.pinnedNotes.length < 1" class="_fullinfo">
-								<img :src="infoImageUrl" class="_ghost" aria-hidden="true" :alt="i18n.ts.noNotes"/>
+								<img :src="infoImageUrl" draggable="false" aria-hidden="true" :alt="i18n.ts.noNotes"/>
 								<div>{{ i18n.ts.noNotes }}</div>
 							</div>
 							<div v-else class="_panel">
diff --git a/packages/frontend/src/pages/user/index.timeline.vue b/packages/frontend/src/pages/user/index.timeline.vue
index eebdebc8e4..4d7933dd76 100644
--- a/packages/frontend/src/pages/user/index.timeline.vue
+++ b/packages/frontend/src/pages/user/index.timeline.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</template>
 	<div v-if="tab === 'pinned'" class="_gaps">
 		<div v-if="user.pinnedNotes.length < 1" class="_fullinfo">
-			<img :src="infoImageUrl" class="_ghost" aria-hidden="true" :alt="i18n.ts.noNotes"/>
+			<img :src="infoImageUrl" draggable="false" aria-hidden="true" :alt="i18n.ts.noNotes"/>
 			<div>{{ i18n.ts.noNotes }}</div>
 		</div>
 		<div v-else class="_panel">
-- 
cgit v1.2.3-freya


From dbcfbad19640cdd8defdf29d06ffd92fd7e84497 Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Tue, 1 Apr 2025 21:03:45 -0400
Subject: fix $i imports

---
 packages/frontend/src/components/MkPoll.vue                 | 2 +-
 packages/frontend/src/components/SkFollowingRecentNotes.vue | 2 +-
 packages/frontend/src/pages/instance-info.vue               | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

(limited to 'packages/frontend/src/components/SkFollowingRecentNotes.vue')

diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue
index ff83520acd..07febacc36 100644
--- a/packages/frontend/src/components/MkPoll.vue
+++ b/packages/frontend/src/components/MkPoll.vue
@@ -41,7 +41,7 @@ import { pleaseLogin } from '@/utility/please-login.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
 
 const props = defineProps<{
 	noteId: string;
diff --git a/packages/frontend/src/components/SkFollowingRecentNotes.vue b/packages/frontend/src/components/SkFollowingRecentNotes.vue
index fc72639095..a817426dec 100644
--- a/packages/frontend/src/components/SkFollowingRecentNotes.vue
+++ b/packages/frontend/src/components/SkFollowingRecentNotes.vue
@@ -30,7 +30,7 @@ import { i18n } from '@/i18n.js';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import SkFollowingFeedEntry from '@/components/SkFollowingFeedEntry.vue';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
 import { checkWordMute } from '@/utility/check-word-mute.js';
 import { FollowingFeedTab } from '@/utility/following-feed-utils.js';
 import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index 963cc98c7c..853887d61e 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -197,7 +197,7 @@ import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
 import { dateString } from '@/filters/date.js';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkInfo from '@/components/MkInfo.vue';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
 
 const props = defineProps<{
 	host: string;
-- 
cgit v1.2.3-freya


From 35649b03c757dc80ec56cdb1c7c1b75f1661bb76 Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Tue, 1 Apr 2025 21:38:26 -0400
Subject: fix type errors in SkFollowingRecentNotes.vue and
 SkUserRecentNotes.vue

---
 packages/frontend/src/components/SkFollowingRecentNotes.vue | 7 ++++---
 packages/frontend/src/components/SkUserRecentNotes.vue      | 7 ++++---
 2 files changed, 8 insertions(+), 6 deletions(-)

(limited to 'packages/frontend/src/components/SkFollowingRecentNotes.vue')

diff --git a/packages/frontend/src/components/SkFollowingRecentNotes.vue b/packages/frontend/src/components/SkFollowingRecentNotes.vue
index a817426dec..df5e7da5c0 100644
--- a/packages/frontend/src/components/SkFollowingRecentNotes.vue
+++ b/packages/frontend/src/components/SkFollowingRecentNotes.vue
@@ -25,14 +25,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script setup lang="ts">
 import * as Misskey from 'misskey-js';
 import { computed, shallowRef } from 'vue';
+import type { FollowingFeedTab } from '@/utility/following-feed-utils.js';
+import type { Paging } from '@/components/MkPagination.vue';
 import { infoImageUrl } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
 import SkFollowingFeedEntry from '@/components/SkFollowingFeedEntry.vue';
 import { $i } from '@/i.js';
 import { checkWordMute } from '@/utility/check-word-mute.js';
-import { FollowingFeedTab } from '@/utility/following-feed-utils.js';
 import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
 
 const props = defineProps<{
@@ -112,7 +113,7 @@ function checkMute(note: Misskey.entities.Note | undefined | null, mutes: Mutes)
 		return false;
 	}
 
-	return checkWordMute(note, $i, mutes);
+	return !!checkWordMute(note, $i, mutes);
 }
 </script>
 
diff --git a/packages/frontend/src/components/SkUserRecentNotes.vue b/packages/frontend/src/components/SkUserRecentNotes.vue
index a6a808e748..fd67464234 100644
--- a/packages/frontend/src/components/SkUserRecentNotes.vue
+++ b/packages/frontend/src/components/SkUserRecentNotes.vue
@@ -15,13 +15,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { computed, onMounted, ref, Ref, watch } from 'vue';
+import { computed, onMounted, ref, watch } from 'vue';
 import * as Misskey from 'misskey-js';
+import type { Ref } from 'vue';
+import type { Paging } from '@/components/MkPagination.vue';
 import MkLoading from '@/components/global/MkLoading.vue';
 import MkNotes from '@/components/MkNotes.vue';
 import MkUserInfo from '@/components/MkUserInfo.vue';
 import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
-import { Paging } from '@/components/MkPagination.vue';
 import { misskeyApi } from '@/utility/misskey-api.js';
 
 const props = defineProps<{
@@ -68,7 +69,7 @@ async function reload(): Promise<void> {
 
 			// Wait for 1 second to match the animation effects in MkHorizontalSwipe, MkPullToRefresh, and MkPagination.
 			// Otherwise, the page appears to load "backwards".
-			new Promise(resolve => setTimeout(resolve, 1000)),
+			new Promise(resolve => window.setTimeout(resolve, 1000)),
 		])
 		.then(([u]) => user.value = u)
 		.catch(error => {
-- 
cgit v1.2.3-freya


From 42d4fc9d97b2df220216ae0635ea8a4b69948900 Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Sat, 10 May 2025 13:19:47 -0400
Subject: refactor following feed to avoid circular dependency

---
 .../src/components/SkFollowingRecentNotes.vue      |  2 +-
 .../src/components/SkRemoteFollowersWarning.vue    |  4 +--
 packages/frontend/src/pages/following-feed.vue     |  3 +-
 packages/frontend/src/preferences/def.ts           |  4 +--
 packages/frontend/src/store.ts                     |  4 +--
 packages/frontend/src/types/following-feed.ts      | 36 ++++++++++++++++++++++
 packages/frontend/src/ui/deck/following-column.vue |  7 +++--
 .../frontend/src/utility/following-feed-utils.ts   | 32 ++-----------------
 8 files changed, 51 insertions(+), 41 deletions(-)
 create mode 100644 packages/frontend/src/types/following-feed.ts

(limited to 'packages/frontend/src/components/SkFollowingRecentNotes.vue')

diff --git a/packages/frontend/src/components/SkFollowingRecentNotes.vue b/packages/frontend/src/components/SkFollowingRecentNotes.vue
index df5e7da5c0..bc8560bbb0 100644
--- a/packages/frontend/src/components/SkFollowingRecentNotes.vue
+++ b/packages/frontend/src/components/SkFollowingRecentNotes.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script setup lang="ts">
 import * as Misskey from 'misskey-js';
 import { computed, shallowRef } from 'vue';
-import type { FollowingFeedTab } from '@/utility/following-feed-utils.js';
+import type { FollowingFeedTab } from '@/types/following-feed.js';
 import type { Paging } from '@/components/MkPagination.vue';
 import { infoImageUrl } from '@/instance.js';
 import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/components/SkRemoteFollowersWarning.vue b/packages/frontend/src/components/SkRemoteFollowersWarning.vue
index bc15dd40ba..32d57477de 100644
--- a/packages/frontend/src/components/SkRemoteFollowersWarning.vue
+++ b/packages/frontend/src/components/SkRemoteFollowersWarning.vue
@@ -11,10 +11,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script setup lang="ts">
 import { computed } from 'vue';
-import type { FollowingFeedModel } from '@/utility/following-feed-utils.js';
+import type { FollowingFeedModel } from '@/types/following-feed.js';
 import { i18n } from '@/i18n.js';
 import MkInfo from '@/components/MkInfo.vue';
-import { followersTab } from '@/utility/following-feed-utils.js';
+import { followersTab } from '@/types/following-feed.js';
 
 const props = defineProps<{
 	model: FollowingFeedModel,
diff --git a/packages/frontend/src/pages/following-feed.vue b/packages/frontend/src/pages/following-feed.vue
index 05fe45ba47..2b8dcce6a6 100644
--- a/packages/frontend/src/pages/following-feed.vue
+++ b/packages/frontend/src/pages/following-feed.vue
@@ -33,7 +33,8 @@ import { i18n } from '@/i18n.js';
 import MkSwiper from '@/components/MkSwiper.vue';
 import MkPageHeader from '@/components/global/MkPageHeader.vue';
 import SkUserRecentNotes from '@/components/SkUserRecentNotes.vue';
-import { createModel, createHeaderItem, followingFeedTabs, followingTabIcon, followingTabName, followingTab } from '@/utility/following-feed-utils.js';
+import { createModel, createHeaderItem, followingTabIcon, followingTabName } from '@/utility/following-feed-utils.js';
+import { followingTab, followingFeedTabs } from '@/types/following-feed.js';
 import SkFollowingRecentNotes from '@/components/SkFollowingRecentNotes.vue';
 import SkRemoteFollowersWarning from '@/components/SkRemoteFollowersWarning.vue';
 import { useRouter } from '@/router.js';
diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts
index 58e26cb005..277508d79d 100644
--- a/packages/frontend/src/preferences/def.ts
+++ b/packages/frontend/src/preferences/def.ts
@@ -11,10 +11,10 @@ import type { Plugin } from '@/plugin.js';
 import type { DeviceKind } from '@/utility/device-kind.js';
 import type { DeckProfile } from '@/deck.js';
 import type { PreferencesDefinition } from './manager.js';
-import type { FollowingFeedState } from '@/utility/following-feed-utils.js';
+import type { FollowingFeedState } from '@/types/following-feed.js';
 import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
 import { searchEngineMap } from '@/utility/search-engine-map.js';
-import { defaultFollowingFeedState } from '@/utility/following-feed-utils.js';
+import { defaultFollowingFeedState } from '@/types/following-feed.js';
 
 /** サウンド設定 */
 export type SoundStore = {
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 0626779869..c5ee1883b8 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -10,11 +10,11 @@ import darkTheme from '@@/themes/d-green-lime.json5';
 import { hemisphere } from '@@/js/intl-const.js';
 import type { DeviceKind } from '@/utility/device-kind.js';
 import type { Plugin } from '@/plugin.js';
+import type { FollowingFeedState } from '@/types/following-feed.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { Pizzax } from '@/lib/pizzax.js';
 import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
-import { defaultFollowingFeedState } from '@/utility/following-feed-utils.js';
-import type { FollowingFeedState } from '@/utility/following-feed-utils.js';
+import { defaultFollowingFeedState } from '@/types/following-feed.js';
 import { searchEngineMap } from '@/utility/search-engine-map.js';
 
 /**
diff --git a/packages/frontend/src/types/following-feed.ts b/packages/frontend/src/types/following-feed.ts
new file mode 100644
index 0000000000..c38c19722a
--- /dev/null
+++ b/packages/frontend/src/types/following-feed.ts
@@ -0,0 +1,36 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import type { WritableComputedRef } from 'vue';
+
+export const followingTab = 'following' as const;
+export const mutualsTab = 'mutuals' as const;
+export const followersTab = 'followers' as const;
+export const followingFeedTabs = [followingTab, mutualsTab, followersTab] as const;
+export type FollowingFeedTab = typeof followingFeedTabs[number];
+
+export type FollowingFeedState = {
+	withNonPublic: boolean,
+	withQuotes: boolean,
+	withBots: boolean,
+	withReplies: boolean,
+	onlyFiles: boolean,
+	userList: FollowingFeedTab,
+	remoteWarningDismissed: boolean,
+};
+
+export type FollowingFeedModel = {
+	[Key in keyof FollowingFeedState]: WritableComputedRef<FollowingFeedState[Key]>;
+};
+
+export const defaultFollowingFeedState: FollowingFeedState = {
+	withNonPublic: false,
+	withQuotes: false,
+	withBots: true,
+	withReplies: false,
+	onlyFiles: false,
+	userList: followingTab,
+	remoteWarningDismissed: false,
+};
diff --git a/packages/frontend/src/ui/deck/following-column.vue b/packages/frontend/src/ui/deck/following-column.vue
index 8e3a6388f2..ed24d119d3 100644
--- a/packages/frontend/src/ui/deck/following-column.vue
+++ b/packages/frontend/src/ui/deck/following-column.vue
@@ -19,18 +19,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts">
 import { computed, shallowRef } from 'vue';
 import type { Column } from '@/deck.js';
-import type { FollowingFeedState } from '@/utility/following-feed-utils.js';
+import type { FollowingFeedState } from '@/types/following-feed.js';
 export type FollowingColumn = Column & Partial<FollowingFeedState>;
 </script>
 
 <script setup lang="ts">
-import type { FollowingFeedTab } from '@/utility/following-feed-utils.js';
+import type { FollowingFeedTab } from '@/types/following-feed.js';
 import type { MenuItem } from '@/types/menu.js';
 import { getColumn, updateColumn } from '@/deck.js';
 import XColumn from '@/ui/deck/column.vue';
 import SkFollowingRecentNotes from '@/components/SkFollowingRecentNotes.vue';
 import SkRemoteFollowersWarning from '@/components/SkRemoteFollowersWarning.vue';
-import { createModel, createOptionsMenu, followingTab, followingTabName, followingTabIcon, followingFeedTabs } from '@/utility/following-feed-utils.js';
+import { followingTab, followingFeedTabs } from '@/types/following-feed.js';
+import { createModel, createOptionsMenu, followingTabName, followingTabIcon } from '@/utility/following-feed-utils.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { useRouter } from '@/router.js';
diff --git a/packages/frontend/src/utility/following-feed-utils.ts b/packages/frontend/src/utility/following-feed-utils.ts
index ca528f5174..d821a80fb0 100644
--- a/packages/frontend/src/utility/following-feed-utils.ts
+++ b/packages/frontend/src/utility/following-feed-utils.ts
@@ -7,16 +7,12 @@ import { computed } from 'vue';
 import type { Ref, WritableComputedRef } from 'vue';
 import type { PageHeaderItem } from '@/types/page-header.js';
 import type { MenuItem } from '@/types/menu.js';
+import type { FollowingFeedTab, FollowingFeedState, FollowingFeedModel } from '@/types/following-feed.js';
 import { deepMerge } from '@/utility/merge.js';
 import { i18n } from '@/i18n.js';
 import { popupMenu } from '@/os.js';
 import { prefer } from '@/preferences.js';
-
-export const followingTab = 'following' as const;
-export const mutualsTab = 'mutuals' as const;
-export const followersTab = 'followers' as const;
-export const followingFeedTabs = [followingTab, mutualsTab, followersTab] as const;
-export type FollowingFeedTab = typeof followingFeedTabs[number];
+import { followingTab, followersTab, mutualsTab, defaultFollowingFeedState } from '@/types/following-feed.js';
 
 export function followingTabName(tab: FollowingFeedTab): string;
 export function followingTabName(tab: FollowingFeedTab | null | undefined): null;
@@ -33,30 +29,6 @@ export function followingTabIcon(tab: FollowingFeedTab | null | undefined): stri
 	return 'ph-user-check ph-bold ph-lg';
 }
 
-export type FollowingFeedModel = {
-	[Key in keyof FollowingFeedState]: WritableComputedRef<FollowingFeedState[Key]>;
-};
-
-export type FollowingFeedState = {
-	withNonPublic: boolean,
-	withQuotes: boolean,
-	withBots: boolean,
-	withReplies: boolean,
-	onlyFiles: boolean,
-	userList: FollowingFeedTab,
-	remoteWarningDismissed: boolean,
-};
-
-export const defaultFollowingFeedState: FollowingFeedState = {
-	withNonPublic: false,
-	withQuotes: false,
-	withBots: true,
-	withReplies: false,
-	onlyFiles: false,
-	userList: followingTab,
-	remoteWarningDismissed: false,
-};
-
 interface StorageInterface {
 	readonly state: Ref<Partial<FollowingFeedState>>;
 	save(updated: Partial<FollowingFeedState>): void;
-- 
cgit v1.2.3-freya


From 5cb0129c499e7bbf7fd34b34cdd4b51cc8086184 Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Sat, 10 May 2025 21:21:50 -0400
Subject: show muted words in following feed

---
 .../src/components/SkFollowingFeedEntry.vue        | 17 ++++++----
 .../src/components/SkFollowingRecentNotes.vue      | 39 ++--------------------
 .../frontend/src/utility/following-feed-utils.ts   | 35 +++++++++++++++++++
 3 files changed, 49 insertions(+), 42 deletions(-)

(limited to 'packages/frontend/src/components/SkFollowingRecentNotes.vue')

diff --git a/packages/frontend/src/components/SkFollowingFeedEntry.vue b/packages/frontend/src/components/SkFollowingFeedEntry.vue
index d1c522df33..80d15225f4 100644
--- a/packages/frontend/src/components/SkFollowingFeedEntry.vue
+++ b/packages/frontend/src/components/SkFollowingFeedEntry.vue
@@ -18,7 +18,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</MkA>
 		</header>
 		<div>
-			<div v-if="isMuted" :class="[$style.text, $style.muted]">({{ i18n.ts.postFiltered }})</div>
+			<div v-if="mutedWords" :class="[$style.text, $style.muted]">
+				<template v-if="prefer.s.showSoftWordMutedWord">{{ i18n.tsx.userSaysSomethingAbout({ name: i18n.ts.user, word: mutedWords}) }}</template>
+				<template v-else>{{ i18n.ts.postFiltered }}</template>
+			</div>
 			<Mfm v-else :class="$style.text" :text="getNoteSummary(note)" :isBlock="true" :plain="true" :nowrap="false" :isNote="true" nyaize="respect" :author="note.user"/>
 		</div>
 	</div>
@@ -27,17 +30,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
+import { computed } from 'vue';
 import { getNoteSummary } from '@/utility/get-note-summary.js';
 import { userPage } from '@/filters/user.js';
 import { notePage } from '@/filters/note.js';
 import { i18n } from '@/i18n.js';
+import { getSoftMutedWords } from '@/utility/following-feed-utils';
+import { prefer } from '@/preferences';
 
-withDefaults(defineProps<{
+const props = defineProps<{
 	note: Misskey.entities.Note,
-	isMuted: boolean
-}>(), {
-	isMuted: false,
-});
+}>();
+
+const mutedWords = computed(() => getSoftMutedWords(props.note));
 
 defineEmits<{
 	(event: 'select', user: Misskey.entities.UserLite): void
diff --git a/packages/frontend/src/components/SkFollowingRecentNotes.vue b/packages/frontend/src/components/SkFollowingRecentNotes.vue
index bc8560bbb0..b8a7b2213f 100644
--- a/packages/frontend/src/components/SkFollowingRecentNotes.vue
+++ b/packages/frontend/src/components/SkFollowingRecentNotes.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 		<template #default="{ items: notes }">
 			<MkDateSeparatedList v-slot="{ item: note }" :items="notes" :class="$style.panel" :noGap="true">
-				<SkFollowingFeedEntry v-if="!isHardMuted(note)" :isMuted="isSoftMuted(note)" :note="note" :class="props.selectedUserId == note.userId && $style.selected" @select="u => selectUser(u.id)"/>
+				<SkFollowingFeedEntry v-if="!getHardMutedWords(note)" :note="note" :class="props.selectedUserId == note.userId && $style.selected" @select="u => selectUser(u.id)"/>
 			</MkDateSeparatedList>
 		</template>
 	</MkPagination>
@@ -23,17 +23,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import * as Misskey from 'misskey-js';
 import { computed, shallowRef } from 'vue';
-import type { FollowingFeedTab } from '@/types/following-feed.js';
 import type { Paging } from '@/components/MkPagination.vue';
+import type { FollowingFeedTab } from '@/types/following-feed.js';
+import { getHardMutedWords } from '@/utility/following-feed-utils.js';
 import { infoImageUrl } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import SkFollowingFeedEntry from '@/components/SkFollowingFeedEntry.vue';
-import { $i } from '@/i.js';
-import { checkWordMute } from '@/utility/check-word-mute.js';
 import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
 
 const props = defineProps<{
@@ -84,37 +82,6 @@ const latestNotesPagination: Paging<'notes/following'> = {
 };
 
 const latestNotesPaging = shallowRef<InstanceType<typeof MkPagination>>();
-
-function isSoftMuted(note: Misskey.entities.Note): boolean {
-	return isMuted(note, $i?.mutedWords);
-}
-
-function isHardMuted(note: Misskey.entities.Note): boolean {
-	return isMuted(note, $i?.hardMutedWords);
-}
-
-// Match the typing used by Misskey
-type Mutes = (string | string[])[] | null | undefined;
-
-// Adapted from MkNote.ts
-function isMuted(note: Misskey.entities.Note, mutes: Mutes): boolean {
-	return checkMute(note, mutes)
-		|| checkMute(note.reply, mutes)
-		|| checkMute(note.renote, mutes);
-}
-
-// Adapted from check-word-mute.ts
-function checkMute(note: Misskey.entities.Note | undefined | null, mutes: Mutes): boolean {
-	if (!note) {
-		return false;
-	}
-
-	if (!mutes || mutes.length < 1) {
-		return false;
-	}
-
-	return !!checkWordMute(note, $i, mutes);
-}
 </script>
 
 <style module lang="scss">
diff --git a/packages/frontend/src/utility/following-feed-utils.ts b/packages/frontend/src/utility/following-feed-utils.ts
index d821a80fb0..4573f3dd7a 100644
--- a/packages/frontend/src/utility/following-feed-utils.ts
+++ b/packages/frontend/src/utility/following-feed-utils.ts
@@ -4,6 +4,7 @@
  */
 
 import { computed } from 'vue';
+import * as Misskey from 'misskey-js';
 import type { Ref, WritableComputedRef } from 'vue';
 import type { PageHeaderItem } from '@/types/page-header.js';
 import type { MenuItem } from '@/types/menu.js';
@@ -13,6 +14,8 @@ import { i18n } from '@/i18n.js';
 import { popupMenu } from '@/os.js';
 import { prefer } from '@/preferences.js';
 import { followingTab, followersTab, mutualsTab, defaultFollowingFeedState } from '@/types/following-feed.js';
+import { $i } from '@/i';
+import { checkWordMute } from '@/utility/check-word-mute';
 
 export function followingTabName(tab: FollowingFeedTab): string;
 export function followingTabName(tab: FollowingFeedTab | null | undefined): null;
@@ -149,3 +152,35 @@ function createDefaultStorage(): Ref<StorageInterface> {
 		},
 	}));
 }
+
+export function getSoftMutedWords(note: Misskey.entities.Note): string | null {
+	return getMutedWords(note, $i?.mutedWords);
+}
+
+export function getHardMutedWords(note: Misskey.entities.Note): string | null {
+	return getMutedWords(note, $i?.hardMutedWords);
+}
+
+// Match the typing used by Misskey
+type Mutes = (string | string[])[] | null | undefined;
+
+// Adapted from MkNote.ts
+function getMutedWords(note: Misskey.entities.Note, mutes: Mutes): string | null {
+	return checkMute(note, mutes)
+		?? checkMute(note.reply, mutes)
+		?? checkMute(note.renote, mutes);
+}
+
+// Adapted from check-word-mute.ts
+function checkMute(note: Misskey.entities.Note | undefined | null, mutes: Mutes): string | null {
+	if (!note) {
+		return null;
+	}
+
+	if (!mutes || mutes.length < 1) {
+		return null;
+	}
+
+	const mutedWords = checkWordMute(note, $i, mutes);
+	return mutedWords ? mutedWords.flat(2).join(', ') : null;
+}
-- 
cgit v1.2.3-freya


From 8348a36f24fcc9b01584a8fb2d895db95de82283 Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Tue, 13 May 2025 22:58:24 -0400
Subject: cleanup, simplify, and merge duplicate word mute implementations

---
 .../src/components/SkFollowingFeedEntry.vue        | 23 +++++-----
 .../src/components/SkFollowingRecentNotes.vue      |  3 +-
 packages/frontend/src/components/SkMutedNote.vue   |  5 +--
 packages/frontend/src/components/SkPatternTest.vue |  4 +-
 packages/frontend/src/utility/check-word-mute.ts   | 49 ++++++++++++----------
 .../frontend/src/utility/following-feed-utils.ts   | 34 ---------------
 6 files changed, 45 insertions(+), 73 deletions(-)

(limited to 'packages/frontend/src/components/SkFollowingRecentNotes.vue')

diff --git a/packages/frontend/src/components/SkFollowingFeedEntry.vue b/packages/frontend/src/components/SkFollowingFeedEntry.vue
index 80d15225f4..b71777e032 100644
--- a/packages/frontend/src/components/SkFollowingFeedEntry.vue
+++ b/packages/frontend/src/components/SkFollowingFeedEntry.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div :class="$style.root" @click="$emit('select', note.user)">
+<div v-if="!hardMuted" :class="$style.root" @click="$emit('select', note.user)">
 	<div :class="$style.avatar">
 		<MkAvatar :class="$style.icon" :user="note.user" indictor/>
 	</div>
@@ -18,36 +18,39 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</MkA>
 		</header>
 		<div>
-			<div v-if="mutedWords" :class="[$style.text, $style.muted]">
-				<template v-if="prefer.s.showSoftWordMutedWord">{{ i18n.tsx.userSaysSomethingAbout({ name: i18n.ts.user, word: mutedWords}) }}</template>
-				<template v-else>{{ i18n.ts.postFiltered }}</template>
+			<div v-if="muted" :class="[$style.text, $style.muted]">
+				<SkMutedNote :muted="muted" :note="note"></SkMutedNote>
 			</div>
 			<Mfm v-else :class="$style.text" :text="getNoteSummary(note)" :isBlock="true" :plain="true" :nowrap="false" :isNote="true" nyaize="respect" :author="note.user"/>
 		</div>
 	</div>
 </div>
+<div v-else>
+	<!--
+		MkDateSeparatedList uses TransitionGroup which requires single element in the child elements
+		so MkNote create empty div instead of no elements
+	-->
+</div>
 </template>
 
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
-import { computed } from 'vue';
 import { getNoteSummary } from '@/utility/get-note-summary.js';
 import { userPage } from '@/filters/user.js';
 import { notePage } from '@/filters/note.js';
-import { i18n } from '@/i18n.js';
-import { getSoftMutedWords } from '@/utility/following-feed-utils';
-import { prefer } from '@/preferences';
+import { checkMutes } from '@/utility/check-word-mute';
+import SkMutedNote from '@/components/SkMutedNote.vue';
 
 const props = defineProps<{
 	note: Misskey.entities.Note,
 }>();
 
-const mutedWords = computed(() => getSoftMutedWords(props.note));
-
 defineEmits<{
 	(event: 'select', user: Misskey.entities.UserLite): void
 }>();
 
+// eslint-disable-next-line vue/no-setup-props-reactivity-loss
+const { muted, hardMuted } = checkMutes(props.note);
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/components/SkFollowingRecentNotes.vue b/packages/frontend/src/components/SkFollowingRecentNotes.vue
index b8a7b2213f..3eb2ac8572 100644
--- a/packages/frontend/src/components/SkFollowingRecentNotes.vue
+++ b/packages/frontend/src/components/SkFollowingRecentNotes.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 		<template #default="{ items: notes }">
 			<MkDateSeparatedList v-slot="{ item: note }" :items="notes" :class="$style.panel" :noGap="true">
-				<SkFollowingFeedEntry v-if="!getHardMutedWords(note)" :note="note" :class="props.selectedUserId == note.userId && $style.selected" @select="u => selectUser(u.id)"/>
+				<SkFollowingFeedEntry :note="note" :class="props.selectedUserId == note.userId && $style.selected" @select="u => selectUser(u.id)"/>
 			</MkDateSeparatedList>
 		</template>
 	</MkPagination>
@@ -26,7 +26,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, shallowRef } from 'vue';
 import type { Paging } from '@/components/MkPagination.vue';
 import type { FollowingFeedTab } from '@/types/following-feed.js';
-import { getHardMutedWords } from '@/utility/following-feed-utils.js';
 import { infoImageUrl } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
diff --git a/packages/frontend/src/components/SkMutedNote.vue b/packages/frontend/src/components/SkMutedNote.vue
index 4336a3abdc..3c072fab3f 100644
--- a/packages/frontend/src/components/SkMutedNote.vue
+++ b/packages/frontend/src/components/SkMutedNote.vue
@@ -31,13 +31,12 @@ import { i18n } from '@/i18n.js';
 import { prefer } from '@/preferences.js';
 
 const props = defineProps<{
-	muted: false | 'sensitiveMute' | (string | string[])[];
+	muted: false | 'sensitiveMute' | string[];
 	note: Misskey.entities.Note;
-
 }>();
 
 const mutedWords = computed(() => Array.isArray(props.muted)
-	? props.muted.map(words => Array.isArray(words) ? words.join() : words).join(' ')
+	? props.muted.join(', ')
 	: props.muted);
 </script>
 
diff --git a/packages/frontend/src/components/SkPatternTest.vue b/packages/frontend/src/components/SkPatternTest.vue
index 2ed2b3fdc3..fe82c1df2f 100644
--- a/packages/frontend/src/components/SkPatternTest.vue
+++ b/packages/frontend/src/components/SkPatternTest.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<template #caption>{{ i18n.ts.wordMuteTestDescription }}</template>
 		</MkTextarea>
 		<div><MkButton :disabled="!testWords" @click="testWordMutes">{{ i18n.ts.wordMuteTestTest }}</MkButton></div>
-		<div v-if="testMatches == null">{{ i18n.ts.wordMuteTestNoResults}}</div>
+		<div v-if="testMatches == null">{{ i18n.ts.wordMuteTestNoResults }}</div>
 		<div v-else-if="testMatches === ''">{{ i18n.ts.wordMuteTestNoMatch }}</div>
 		<div v-else>{{ i18n.tsx.wordMuteTestMatch({ words: testMatches }) }}</div>
 	</div>
@@ -44,7 +44,7 @@ function testWordMutes() {
 	try {
 		const mutes = parseMutes(props.mutedWords);
 		const matches = checkWordMute(testWords.value, null, mutes);
-		testMatches.value = matches ? matches.flat(2).join(', ') : '';
+		testMatches.value = matches ? matches.join(', ') : '';
 	} catch {
 		// Error is displayed by above function
 		testMatches.value = null;
diff --git a/packages/frontend/src/utility/check-word-mute.ts b/packages/frontend/src/utility/check-word-mute.ts
index dc36ea1906..03fe95ae3a 100644
--- a/packages/frontend/src/utility/check-word-mute.ts
+++ b/packages/frontend/src/utility/check-word-mute.ts
@@ -13,34 +13,39 @@ export function checkMutes(noteToCheck: Misskey.entities.Note, withHardMute = fa
 	return { muted, hardMuted };
 }
 
-/* Overload FunctionにLintが対応していないのでコメントアウト
-function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
-function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): Array<string | string[]> | false | 'sensitiveMute';
-*/
-export function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): Array<string | string[]> | false | 'sensitiveMute' {
-	if (mutedWords != null) {
-		const result = checkWordMute(noteToCheck, $i, mutedWords);
-		if (Array.isArray(result)) return result;
-
-		const replyResult = noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords);
-		if (Array.isArray(replyResult)) return replyResult;
-
-		const renoteResult = noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords);
-		if (Array.isArray(renoteResult)) return renoteResult;
+export function checkMute(note: Misskey.entities.Note, mutes: undefined | null): false;
+export function checkMute(note: Misskey.entities.Note, mutes: undefined | null, checkOnly: false): false;
+export function checkMute(note: Misskey.entities.Note, mutes: undefined | null, checkOnly?: boolean): false | 'sensitiveMute';
+export function checkMute(note: Misskey.entities.Note, mutes: Array<string | string[]> | undefined | null): string[] | false;
+export function checkMute(note: Misskey.entities.Note, mutes: Array<string | string[]> | undefined | null, checkOnly: false): string[] | false;
+export function checkMute(note: Misskey.entities.Note, mutes: Array<string | string[]> | undefined | null, checkOnly?: boolean): string[] | false | 'sensitiveMute';
+export function checkMute(note: Misskey.entities.Note, mutes: Array<string | string[]> | undefined | null, checkOnly = false): string[] | false | 'sensitiveMute' {
+	if (mutes != null) {
+		const result =
+			checkWordMute(note, $i, mutes)
+			|| checkWordMute(note.reply, $i, mutes)
+			|| checkWordMute(note.renote, $i, mutes);
+
+		// Only continue to sensitiveMute if we don't match any *actual* mutes
+		if (result) {
+			return result;
+		}
 	}
 
-	if (checkOnly) return false;
-
-	const inTimeline = inject<boolean>('inTimeline', false);
-	const tl_withSensitive = inject<Ref<boolean> | null>('tl_withSensitive', null);
-	if (inTimeline && tl_withSensitive?.value === false && noteToCheck.files?.some((v) => v.isSensitive)) {
-		return 'sensitiveMute';
+	if (checkOnly) {
+		const inTimeline = inject<boolean>('inTimeline', false);
+		const tl_withSensitive = inject<Ref<boolean> | null>('tl_withSensitive', null);
+		if (inTimeline && tl_withSensitive?.value === false && note.files?.some((v) => v.isSensitive)) {
+			return 'sensitiveMute';
+		}
 	}
 
 	return false;
 }
 
-export function checkWordMute(note: string | Misskey.entities.Note, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): Array<string | string[]> | false {
+export function checkWordMute(note: string | Misskey.entities.Note | undefined | null, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): string[] | false {
+	if (note == null) return false;
+
 	// 自分自身
 	if (me && typeof(note) === 'object' && (note.userId === me.id)) return false;
 
@@ -79,7 +84,7 @@ export function checkWordMute(note: string | Misskey.entities.Note, me: Misskey.
 		}, new Set<string>());
 
 		// Nested arrays are intentional, otherwise the note components will join with space (" ") and it's confusing.
-		if (matched.size > 0) return [[Array.from(matched).join(', ')]];
+		if (matched.size > 0) return Array.from(matched);
 	}
 
 	return false;
diff --git a/packages/frontend/src/utility/following-feed-utils.ts b/packages/frontend/src/utility/following-feed-utils.ts
index 4573f3dd7a..99335f89bd 100644
--- a/packages/frontend/src/utility/following-feed-utils.ts
+++ b/packages/frontend/src/utility/following-feed-utils.ts
@@ -4,7 +4,6 @@
  */
 
 import { computed } from 'vue';
-import * as Misskey from 'misskey-js';
 import type { Ref, WritableComputedRef } from 'vue';
 import type { PageHeaderItem } from '@/types/page-header.js';
 import type { MenuItem } from '@/types/menu.js';
@@ -14,8 +13,6 @@ import { i18n } from '@/i18n.js';
 import { popupMenu } from '@/os.js';
 import { prefer } from '@/preferences.js';
 import { followingTab, followersTab, mutualsTab, defaultFollowingFeedState } from '@/types/following-feed.js';
-import { $i } from '@/i';
-import { checkWordMute } from '@/utility/check-word-mute';
 
 export function followingTabName(tab: FollowingFeedTab): string;
 export function followingTabName(tab: FollowingFeedTab | null | undefined): null;
@@ -153,34 +150,3 @@ function createDefaultStorage(): Ref<StorageInterface> {
 	}));
 }
 
-export function getSoftMutedWords(note: Misskey.entities.Note): string | null {
-	return getMutedWords(note, $i?.mutedWords);
-}
-
-export function getHardMutedWords(note: Misskey.entities.Note): string | null {
-	return getMutedWords(note, $i?.hardMutedWords);
-}
-
-// Match the typing used by Misskey
-type Mutes = (string | string[])[] | null | undefined;
-
-// Adapted from MkNote.ts
-function getMutedWords(note: Misskey.entities.Note, mutes: Mutes): string | null {
-	return checkMute(note, mutes)
-		?? checkMute(note.reply, mutes)
-		?? checkMute(note.renote, mutes);
-}
-
-// Adapted from check-word-mute.ts
-function checkMute(note: Misskey.entities.Note | undefined | null, mutes: Mutes): string | null {
-	if (!note) {
-		return null;
-	}
-
-	if (!mutes || mutes.length < 1) {
-		return null;
-	}
-
-	const mutedWords = checkWordMute(note, $i, mutes);
-	return mutedWords ? mutedWords.flat(2).join(', ') : null;
-}
-- 
cgit v1.2.3-freya

