diff --git a/locales/en-US.yml b/locales/en-US.yml
index 9c5237b3b7..049c347111 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -696,6 +696,7 @@ regexpError: "Regular Expression error"
regexpErrorDescription: "An error occurred in the regular expression on line {line} of your {tab} word mutes:"
instanceMute: "Instance Mutes"
userSaysSomething: "{name} said something"
+postFiltered: "post is hidden by a filter"
makeActive: "Activate"
display: "Display"
copy: "Copy"
diff --git a/locales/index.d.ts b/locales/index.d.ts
index bfa29e6e44..10a846cd42 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -2800,6 +2800,10 @@ export interface Locale extends ILocale {
* {name}が何かを言いました
*/
"userSaysSomething": ParameterizedString<"name">;
+ /**
+ * post is hidden by a filter
+ */
+ "postFiltered": string;
/**
* アクティブにする
*/
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 75866f2596..25a2a7e825 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -696,6 +696,7 @@ regexpError: "正規表現エラー"
regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:"
instanceMute: "サーバーミュート"
userSaysSomething: "{name}が何かを言いました"
+postFiltered: "post is hidden by a filter"
makeActive: "アクティブにする"
display: "表示"
copy: "コピー"
diff --git a/packages/frontend/src/components/FollowingFeedEntry.vue b/packages/frontend/src/components/FollowingFeedEntry.vue
index 7f5abaa9cc..8fa5e014d8 100644
--- a/packages/frontend/src/components/FollowingFeedEntry.vue
+++ b/packages/frontend/src/components/FollowingFeedEntry.vue
@@ -18,7 +18,8 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
({{ i18n.ts.postFiltered }})
+
@@ -29,10 +30,14 @@ import * as Misskey from 'misskey-js';
import { getNoteSummary } from '@/scripts/get-note-summary.js';
import { userPage } from '@/filters/user.js';
import { notePage } from '@/filters/note.js';
+import { i18n } from '@/i18n.js';
-defineProps<{
- note: Misskey.entities.Note
-}>();
+withDefaults(defineProps<{
+ note: Misskey.entities.Note,
+ isMuted: boolean
+}>(), {
+ isMuted: false,
+});
defineEmits<{
(event: 'select', user: Misskey.entities.UserLite): void
@@ -98,6 +103,10 @@ defineEmits<{
height: 2.5em;
}
+.muted {
+ font-style: italic;
+}
+
@container (max-width: 600px) {
.root {
padding: 16px;
diff --git a/packages/frontend/src/pages/following-feed.vue b/packages/frontend/src/pages/following-feed.vue
index c47880bb58..5335191246 100644
--- a/packages/frontend/src/pages/following-feed.vue
+++ b/packages/frontend/src/pages/following-feed.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -68,7 +68,9 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
import { useRouter } from '@/router/supplier.js';
import * as os from '@/os.js';
import MkPageHeader from '@/components/global/MkPageHeader.vue';
+import { $i } from '@/account.js';
import MkLoading from '@/components/global/MkLoading.vue';
+import { getNoteText } from '@/scripts/check-word-mute.js';
const props = withDefaults(defineProps<{
initialTab?: FollowingFeedTab,
@@ -158,6 +160,81 @@ async function onChangeTab(): Promise {
await showUserNotes('');
}
+const softMutePatterns = ref(buildMutePatterns($i?.mutedWords));
+const hardMutePatterns = ref(buildMutePatterns($i?.hardMutedWords));
+
+function buildMutePatterns(mutedWords: (string | string[])[] | undefined): RegExp[] {
+ if (!mutedWords || mutedWords.length < 1) {
+ return [];
+ }
+
+ // flags -> pattern[]
+ const patternMap = new Map>();
+ for (const mute of mutedWords) {
+ let flags: string;
+ let patterns: string[];
+
+ if (!mute) {
+ continue;
+ } else if (Array.isArray(mute)) {
+ patterns = mute;
+ flags = 'i';
+ } else {
+ const match = mute.match(/^\/(.+)\/(.*)$/);
+ if (!match) {
+ continue;
+ } else {
+ patterns = [match[1]];
+ flags = match[2];
+ }
+ }
+
+ let flagPatterns = patternMap.get(flags);
+ if (!flagPatterns) {
+ flagPatterns = new Set();
+ patternMap.set(flags, flagPatterns);
+ }
+
+ for (const pattern of patterns) {
+ flagPatterns.add(pattern);
+ }
+ }
+
+ return Array
+ .from(patternMap)
+ .map(([flag, patterns]) => {
+ const pattern = Array.from(patterns).map(p => `(${p})`).join('|');
+ return new RegExp(pattern, flag);
+ });
+}
+
+// Adapted from MkNote.ts
+function isSoftMuted(note: Misskey.entities.Note): boolean {
+ return isMuted(note, softMutePatterns.value);
+}
+
+function isHardMuted(note: Misskey.entities.Note): boolean {
+ return isMuted(note, hardMutePatterns.value);
+}
+
+function isMuted(note: Misskey.entities.Note, mutes: RegExp[]): boolean {
+ if (mutes.length < 1) return false;
+
+ 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: RegExp[]): boolean {
+ if (!note) {
+ return false;
+ }
+
+ const noteText = getNoteText(note);
+ return mutes.some(p => p.test(noteText));
+}
+
const latestNotesPaging = shallowRef>();
const latestNotesPagination: Paging<'notes/following'> = {
diff --git a/packages/frontend/src/scripts/check-word-mute.ts b/packages/frontend/src/scripts/check-word-mute.ts
index e65c327ffe..45af7374e8 100644
--- a/packages/frontend/src/scripts/check-word-mute.ts
+++ b/packages/frontend/src/scripts/check-word-mute.ts
@@ -43,7 +43,7 @@ export function checkWordMute(note: Note, me: MeDetailed | null | undefined, mut
return false;
}
-function getNoteText(note: Note): string {
+export function getNoteText(note: Note): string {
const textParts: string[] = [];
if (note.cw) textParts.push(note.cw);