@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="note _panel"
|
||||
v-if="!muted"
|
||||
v-show="!isDeleted"
|
||||
:tabindex="!isDeleted ? '-1' : null"
|
||||
:class="{ renote: isRenote }"
|
||||
@@ -84,6 +85,13 @@
|
||||
</article>
|
||||
<x-sub v-for="note in replies" :key="note.id" :note="note" class="reply" :detail="true"/>
|
||||
</div>
|
||||
<div v-else class="_panel muted" @click="muted = false">
|
||||
<i18n path="userSaysSomething" tag="small">
|
||||
<router-link class="name" :to="appearNote.user | userPage" v-user-preview="appearNote.userId" place="name">
|
||||
<mk-user-name :user="appearNote.user"/>
|
||||
</router-link>
|
||||
</i18n>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -105,6 +113,7 @@ import pleaseLogin from '../scripts/please-login';
|
||||
import { focusPrev, focusNext } from '../scripts/focus';
|
||||
import { url } from '../config';
|
||||
import copyToClipboard from '../scripts/copy-to-clipboard';
|
||||
import { checkWordMute } from '../scripts/check-word-mute';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@@ -142,6 +151,7 @@ export default Vue.extend({
|
||||
replies: [],
|
||||
showContent: false,
|
||||
isDeleted: false,
|
||||
muted: false,
|
||||
myReaction: null,
|
||||
reactions: {},
|
||||
emojis: [],
|
||||
@@ -227,15 +237,16 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.emojis = [...this.appearNote.emojis];
|
||||
this.reactions = { ...this.appearNote.reactions };
|
||||
this.myReaction = this.appearNote.myReaction;
|
||||
|
||||
async created() {
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
this.connection = this.$root.stream;
|
||||
}
|
||||
|
||||
this.emojis = [...this.appearNote.emojis];
|
||||
this.reactions = { ...this.appearNote.reactions };
|
||||
this.myReaction = this.appearNote.myReaction;
|
||||
this.muted = await checkWordMute(this.appearNote, this.$store.state.i, this.$store.state.settings.mutedWords);
|
||||
|
||||
if (this.detail) {
|
||||
this.$root.api('notes/children', {
|
||||
noteId: this.appearNote.id,
|
||||
@@ -976,4 +987,10 @@ export default Vue.extend({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.muted {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
|
42
src/client/components/tab.vue
Normal file
42
src/client/components/tab.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="pxhvhrfw" v-size="[{ max: 500 }]">
|
||||
<button v-for="item in items" class="_button" @click="$emit('input', item.value)" :class="{ active: value === item.value }" :key="item.value">{{ item.label }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pxhvhrfw {
|
||||
display: flex;
|
||||
|
||||
> button {
|
||||
flex: 1;
|
||||
padding: 11px 8px 8px 8px;
|
||||
border-bottom: solid 3px transparent;
|
||||
|
||||
&.active {
|
||||
color: var(--accent);
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
&.max-width_500px {
|
||||
font-size: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -27,6 +27,7 @@
|
||||
<x-import-export/>
|
||||
<x-drive/>
|
||||
<x-mute-block/>
|
||||
<x-word-mute/>
|
||||
<x-security/>
|
||||
<x-2fa/>
|
||||
<x-integration/>
|
||||
@@ -47,6 +48,7 @@ import XImportExport from './import-export.vue';
|
||||
import XDrive from './drive.vue';
|
||||
import XReactionSetting from './reaction.vue';
|
||||
import XMuteBlock from './mute-block.vue';
|
||||
import XWordMute from './word-mute.vue';
|
||||
import XSecurity from './security.vue';
|
||||
import X2fa from './2fa.vue';
|
||||
import XIntegration from './integration.vue';
|
||||
@@ -68,6 +70,7 @@ export default Vue.extend({
|
||||
XDrive,
|
||||
XReactionSetting,
|
||||
XMuteBlock,
|
||||
XWordMute,
|
||||
XSecurity,
|
||||
X2fa,
|
||||
XIntegration,
|
||||
|
77
src/client/pages/my-settings/word-mute.vue
Normal file
77
src/client/pages/my-settings/word-mute.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<section class="_card">
|
||||
<div class="_title"><fa :icon="faCommentSlash"/> {{ $t('wordMute') }}</div>
|
||||
<div class="_content _noPad">
|
||||
<mk-tab v-model="tab" :items="[{ label: $t('_wordMute.soft'), value: 'soft' }, { label: $t('_wordMute.hard'), value: 'hard' }]"/>
|
||||
</div>
|
||||
<div class="_content" v-show="tab === 'soft'">
|
||||
<mk-info>{{ $t('_wordMute.softDescription') }}</mk-info>
|
||||
<mk-textarea v-model="softMutedWords">
|
||||
<span>{{ $t('_wordMute.muteWords') }}</span>
|
||||
<template #desc>{{ $t('_wordMute.muteWordsDescription') }}<br>{{ $t('_wordMute.muteWordsDescription2') }}</template>
|
||||
</mk-textarea>
|
||||
</div>
|
||||
<div class="_content" v-show="tab === 'hard'">
|
||||
<mk-info>{{ $t('_wordMute.hardDescription') }}</mk-info>
|
||||
<mk-textarea v-model="hardMutedWords">
|
||||
<span>{{ $t('_wordMute.muteWords') }}</span>
|
||||
<template #desc>{{ $t('_wordMute.muteWordsDescription') }}<br>{{ $t('_wordMute.muteWordsDescription2') }}</template>
|
||||
</mk-textarea>
|
||||
</div>
|
||||
<div class="_footer">
|
||||
<mk-button @click="save()" primary inline :disabled="!changed"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faCommentSlash, faSave } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkButton from '../../components/ui/button.vue';
|
||||
import MkTextarea from '../../components/ui/textarea.vue';
|
||||
import MkTab from '../../components/tab.vue';
|
||||
import MkInfo from '../../components/ui/info.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
MkButton,
|
||||
MkTextarea,
|
||||
MkTab,
|
||||
MkInfo,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
tab: 'soft',
|
||||
softMutedWords: '',
|
||||
hardMutedWords: '',
|
||||
changed: false,
|
||||
faCommentSlash, faSave,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
softMutedWords() {
|
||||
this.changed = true;
|
||||
},
|
||||
hardMutedWords() {
|
||||
this.changed = true;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.softMutedWords = this.$store.state.settings.mutedWords.map(x => x.join(' ')).join('\n');
|
||||
this.hardMutedWords = this.$store.state.i.mutedWords.map(x => x.join(' ')).join('\n');
|
||||
},
|
||||
|
||||
methods: {
|
||||
async save() {
|
||||
this.$store.dispatch('settings/set', { key: 'mutedWords', value: this.softMutedWords.trim().split('\n').map(x => x.trim().split(' ')) });
|
||||
await this.$root.api('i/update', {
|
||||
mutedWords: this.hardMutedWords.trim().split('\n').map(x => x.trim().split(' ')),
|
||||
});
|
||||
this.changed = false;
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
26
src/client/scripts/check-word-mute.ts
Normal file
26
src/client/scripts/check-word-mute.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export async function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: string[][]): Promise<boolean> {
|
||||
// 自分自身
|
||||
if (me && (note.userId === me.id)) return false;
|
||||
|
||||
const words = mutedWords
|
||||
// Clean up
|
||||
.map(xs => xs.filter(x => x !== ''))
|
||||
.filter(xs => xs.length > 0);
|
||||
|
||||
if (words.length > 0) {
|
||||
if (note.text == null) return false;
|
||||
|
||||
const matched = words.some(and =>
|
||||
and.every(keyword => {
|
||||
const regexp = keyword.match(/^\/(.+)\/(.*)$/);
|
||||
if (regexp) {
|
||||
return new RegExp(regexp[1], regexp[2]).test(note.text!);
|
||||
}
|
||||
return note.text!.includes(keyword);
|
||||
}));
|
||||
|
||||
if (matched) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@@ -18,6 +18,7 @@ export const defaultSettings = {
|
||||
pastedFileName: 'yyyy-MM-dd HH-mm-ss [{{number}}]',
|
||||
memo: null,
|
||||
reactions: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
|
||||
mutedWords: [],
|
||||
};
|
||||
|
||||
export const defaultDeviceUserSettings = {
|
||||
|
@@ -355,6 +355,10 @@ hr {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
&._noPad {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
& + ._content {
|
||||
border-top: solid 1px var(--divider);
|
||||
}
|
||||
|
Reference in New Issue
Block a user