ワードミュート (#6594)

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip
This commit is contained in:
syuilo
2020-07-27 13:34:20 +09:00
committed by GitHub
parent b5a1fdd4c7
commit cf43dd6ec5
32 changed files with 485 additions and 12 deletions

View File

@@ -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>

View 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>

View File

@@ -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,

View 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>

View 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;
}

View File

@@ -18,6 +18,7 @@ export const defaultSettings = {
pastedFileName: 'yyyy-MM-dd HH-mm-ss [{{number}}]',
memo: null,
reactions: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
mutedWords: [],
};
export const defaultDeviceUserSettings = {

View File

@@ -355,6 +355,10 @@ hr {
padding: 16px;
}
&._noPad {
padding: 0 !important;
}
& + ._content {
border-top: solid 1px var(--divider);
}