enhance(frontend): ワードミュートで引っかかったワードを表示可能にする (#15195)
* feat(frontend): ソフトミュートで引っかかったものを表示できるように
* ソフトワードミュートのミュート文字列表示を切り替え可能に
* Chore(docs): Update CHANGELOG
* Fix: language file
* Fixed by review
* Fix by review
* Fix: reloadAskなおしきれていなかった
* perf: filter -> findに変更して最初の一個のみを表示するように変更
* Revert "perf: filter -> findに変更して最初の一個のみを表示するように変更"
This reverts commit 72ef92f0d6
.
This commit is contained in:
parent
87cdbaea4f
commit
9760f3d7c9
|
@ -13,6 +13,7 @@
|
||||||
- Enhance: PC画面でチャンネルが複数列で表示されるように
|
- Enhance: PC画面でチャンネルが複数列で表示されるように
|
||||||
(Cherry-picked from https://github.com/Otaku-Social/maniakey/pull/13)
|
(Cherry-picked from https://github.com/Otaku-Social/maniakey/pull/13)
|
||||||
- Enhance: 照会に失敗した場合、その理由を表示するように
|
- Enhance: 照会に失敗した場合、その理由を表示するように
|
||||||
|
- Enhance: ワードミュートで検知されたワードを表示できるように
|
||||||
- Enhance: リモートのノートのリンクをコピーできるように
|
- Enhance: リモートのノートのリンクをコピーできるように
|
||||||
- Enhance: 連合がホワイトリスト化・無効化されているサーバー向けのデザイン修正
|
- Enhance: 連合がホワイトリスト化・無効化されているサーバー向けのデザイン修正
|
||||||
- Enhance: AiScriptのセーブデータを明示的に削除する関数`Mk:remove`を追加
|
- Enhance: AiScriptのセーブデータを明示的に削除する関数`Mk:remove`を追加
|
||||||
|
|
|
@ -2762,6 +2762,10 @@ export interface Locale extends ILocale {
|
||||||
* ハードワードミュート
|
* ハードワードミュート
|
||||||
*/
|
*/
|
||||||
"hardWordMute": string;
|
"hardWordMute": string;
|
||||||
|
/**
|
||||||
|
* ミュートされたワードを表示
|
||||||
|
*/
|
||||||
|
"showMutedWord": string;
|
||||||
/**
|
/**
|
||||||
* 指定した語句を含むノートを隠します。ワードミュートとは異なり、ノートは完全に表示されなくなります。
|
* 指定した語句を含むノートを隠します。ワードミュートとは異なり、ノートは完全に表示されなくなります。
|
||||||
*/
|
*/
|
||||||
|
@ -2782,6 +2786,10 @@ export interface Locale extends ILocale {
|
||||||
* {name}が何かを言いました
|
* {name}が何かを言いました
|
||||||
*/
|
*/
|
||||||
"userSaysSomething": ParameterizedString<"name">;
|
"userSaysSomething": ParameterizedString<"name">;
|
||||||
|
/**
|
||||||
|
* {name}が「{word}」について何かを言いました
|
||||||
|
*/
|
||||||
|
"userSaysSomethingAbout": ParameterizedString<"name" | "word">;
|
||||||
/**
|
/**
|
||||||
* アクティブにする
|
* アクティブにする
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -687,11 +687,13 @@ testEmail: "配信テスト"
|
||||||
wordMute: "ワードミュート"
|
wordMute: "ワードミュート"
|
||||||
wordMuteDescription: "指定した語句を含むノートを最小化します。最小化されたノートをクリックすることで表示することができます。"
|
wordMuteDescription: "指定した語句を含むノートを最小化します。最小化されたノートをクリックすることで表示することができます。"
|
||||||
hardWordMute: "ハードワードミュート"
|
hardWordMute: "ハードワードミュート"
|
||||||
|
showMutedWord: "ミュートされたワードを表示"
|
||||||
hardWordMuteDescription: "指定した語句を含むノートを隠します。ワードミュートとは異なり、ノートは完全に表示されなくなります。"
|
hardWordMuteDescription: "指定した語句を含むノートを隠します。ワードミュートとは異なり、ノートは完全に表示されなくなります。"
|
||||||
regexpError: "正規表現エラー"
|
regexpError: "正規表現エラー"
|
||||||
regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:"
|
regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:"
|
||||||
instanceMute: "サーバーミュート"
|
instanceMute: "サーバーミュート"
|
||||||
userSaysSomething: "{name}が何かを言いました"
|
userSaysSomething: "{name}が何かを言いました"
|
||||||
|
userSaysSomethingAbout: "{name}が「{word}」について何かを言いました"
|
||||||
makeActive: "アクティブにする"
|
makeActive: "アクティブにする"
|
||||||
display: "表示"
|
display: "表示"
|
||||||
copy: "コピー"
|
copy: "コピー"
|
||||||
|
|
|
@ -150,13 +150,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkA>
|
</MkA>
|
||||||
</template>
|
</template>
|
||||||
</I18n>
|
</I18n>
|
||||||
<I18n v-else :src="i18n.ts.userSaysSomething" tag="small">
|
<I18n v-else-if="showSoftWordMutedWord !== true" :src="i18n.ts.userSaysSomething" tag="small">
|
||||||
<template #name>
|
<template #name>
|
||||||
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
|
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
|
||||||
<MkUserName :user="appearNote.user"/>
|
<MkUserName :user="appearNote.user"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
</template>
|
</template>
|
||||||
</I18n>
|
</I18n>
|
||||||
|
<I18n v-else :src="i18n.ts.userSaysSomethingAbout" tag="small">
|
||||||
|
<template #name>
|
||||||
|
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
|
||||||
|
<MkUserName :user="appearNote.user"/>
|
||||||
|
</MkA>
|
||||||
|
</template>
|
||||||
|
<template #word>
|
||||||
|
{{ Array.isArray(muted) ? muted.map(words => Array.isArray(words) ? words.join() : words).slice(0, 3).join(' ') : muted }}
|
||||||
|
</template>
|
||||||
|
</I18n>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<!--
|
<!--
|
||||||
|
@ -272,6 +282,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong);
|
||||||
const isDeleted = ref(false);
|
const isDeleted = ref(false);
|
||||||
const muted = ref(checkMute(appearNote.value, $i?.mutedWords));
|
const muted = ref(checkMute(appearNote.value, $i?.mutedWords));
|
||||||
const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords, true));
|
const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords, true));
|
||||||
|
const showSoftWordMutedWord = computed(() => defaultStore.state.showSoftWordMutedWord);
|
||||||
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
|
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
|
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
|
||||||
|
@ -290,14 +301,19 @@ const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||||
|
|
||||||
/* Overload FunctionにLintが対応していないのでコメントアウト
|
/* 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: true): boolean;
|
||||||
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
|
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): Array<string | string[]> | false | 'sensitiveMute';
|
||||||
*/
|
*/
|
||||||
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' {
|
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): Array<string | string[]> | false | 'sensitiveMute' {
|
||||||
if (mutedWords != null) {
|
if (mutedWords == null) return false;
|
||||||
if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
|
|
||||||
if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true;
|
const result = checkWordMute(noteToCheck, $i, mutedWords);
|
||||||
if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true;
|
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;
|
||||||
|
|
||||||
if (checkOnly) return false;
|
if (checkOnly) return false;
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<MkInfo>{{ i18n.ts.wordMuteDescription }}</MkInfo>
|
<MkInfo>{{ i18n.ts.wordMuteDescription }}</MkInfo>
|
||||||
|
<MkSwitch v-model="showSoftWordMutedWord">{{ i18n.ts.showMutedWord }}</MkSwitch>
|
||||||
<XWordMute :muted="$i.mutedWords" @save="saveMutedWords"/>
|
<XWordMute :muted="$i.mutedWords" @save="saveMutedWords"/>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
@ -132,7 +133,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
import XInstanceMute from './mute-block.instance-mute.vue';
|
import XInstanceMute from './mute-block.instance-mute.vue';
|
||||||
import XWordMute from './mute-block.word-mute.vue';
|
import XWordMute from './mute-block.word-mute.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
@ -146,6 +147,9 @@ import { instance, infoImageUrl } from '@/instance.js';
|
||||||
import { signinRequired } from '@/account.js';
|
import { signinRequired } from '@/account.js';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
|
import { defaultStore } from '@/store';
|
||||||
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
|
|
||||||
const $i = signinRequired();
|
const $i = signinRequired();
|
||||||
|
|
||||||
|
@ -168,6 +172,14 @@ const expandedRenoteMuteItems = ref([]);
|
||||||
const expandedMuteItems = ref([]);
|
const expandedMuteItems = ref([]);
|
||||||
const expandedBlockItems = ref([]);
|
const expandedBlockItems = ref([]);
|
||||||
|
|
||||||
|
const showSoftWordMutedWord = computed(defaultStore.makeGetterSetter('showSoftWordMutedWord'));
|
||||||
|
|
||||||
|
watch([
|
||||||
|
showSoftWordMutedWord,
|
||||||
|
], async () => {
|
||||||
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
|
});
|
||||||
|
|
||||||
async function unrenoteMute(user, ev) {
|
async function unrenoteMute(user, ev) {
|
||||||
os.popupMenu([{
|
os.popupMenu([{
|
||||||
text: i18n.ts.renoteUnmute,
|
text: i18n.ts.renoteUnmute,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
|
||||||
export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): boolean {
|
export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): Array<string | string[]> | false {
|
||||||
// 自分自身
|
// 自分自身
|
||||||
if (me && (note.userId === me.id)) return false;
|
if (me && (note.userId === me.id)) return false;
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.
|
||||||
|
|
||||||
if (text === '') return false;
|
if (text === '') return false;
|
||||||
|
|
||||||
const matched = mutedWords.some(filter => {
|
const matched = mutedWords.filter(filter => {
|
||||||
if (Array.isArray(filter)) {
|
if (Array.isArray(filter)) {
|
||||||
// Clean up
|
// Clean up
|
||||||
const filteredFilter = filter.filter(keyword => keyword !== '');
|
const filteredFilter = filter.filter(keyword => keyword !== '');
|
||||||
|
@ -36,7 +36,7 @@ export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (matched) return true;
|
if (matched.length > 0) return matched;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -9,10 +9,10 @@ import { hemisphere } from '@@/js/intl-const.js';
|
||||||
import lightTheme from '@@/themes/l-light.json5';
|
import lightTheme from '@@/themes/l-light.json5';
|
||||||
import darkTheme from '@@/themes/d-green-lime.json5';
|
import darkTheme from '@@/themes/d-green-lime.json5';
|
||||||
import type { SoundType } from '@/scripts/sound.js';
|
import type { SoundType } from '@/scripts/sound.js';
|
||||||
|
import type { Ast } from '@syuilo/aiscript';
|
||||||
import { DEFAULT_DEVICE_KIND, type DeviceKind } from '@/scripts/device-kind.js';
|
import { DEFAULT_DEVICE_KIND, type DeviceKind } from '@/scripts/device-kind.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { Storage } from '@/pizzax.js';
|
import { Storage } from '@/pizzax.js';
|
||||||
import type { Ast } from '@syuilo/aiscript';
|
|
||||||
|
|
||||||
interface PostFormAction {
|
interface PostFormAction {
|
||||||
title: string,
|
title: string,
|
||||||
|
@ -474,6 +474,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
showSoftWordMutedWord: {
|
||||||
|
where: 'device',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
sound_masterVolume: {
|
sound_masterVolume: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
|
|
Loading…
Reference in New Issue