feat: 通報の即時解決機能を追加 (#113)
* feat: 通報の即時解決機能を追加 * fix: 条件変更時に有効期限を変更していないのに勝手に更新される問題を修正 * fix: 条件のパターンの削除ができない問題を修正 * fix: リソルバーの通報を解決する判定基準が間違っていたのを修正 * fix: 変更する変数が間違っていたのを修正 * fix: getUTCMonthはゼロ始まりかも * enhance: Storybookのストーリーを作成 * fix: 色々修正 * fix: 型エラーを修正 * [ci skip] Update CHANGELOG.md * [ci skip] Update CHANGELOG.md * Update CHANGELOG.md * リファクタリング * refactor: 型定義をよりよくした * refactor: beforeExpiresAtの初期値はundefinedの方がいい * refactor: 変数の名前を変更 * Fix: リモートサーバーから転送された通報も対象に追加 * Update CHANGELOG.md * take review --------- Co-authored-by: Chocolate Pie <106949016+chocolate-pie@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
import MkAbuseReportResolver from './MkAbuseReportResolver.vue';
|
||||
import type { StoryObj } from '@storybook/vue3';
|
||||
export const Default = {
|
||||
render(args) {
|
||||
return {
|
||||
components: {
|
||||
MkAbuseReportResolver,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
args,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
props() {
|
||||
return {
|
||||
...this.args,
|
||||
};
|
||||
},
|
||||
},
|
||||
template: '<MkAbuseReportResolver v-bind="props" />',
|
||||
};
|
||||
},
|
||||
args: {
|
||||
editable: true,
|
||||
data: {
|
||||
name: 'Sample',
|
||||
targetUserPattern: '^.*@.+$',
|
||||
reporterPattern: null,
|
||||
reportContentPattern: null,
|
||||
expiresAt: 'indefinitely',
|
||||
forward: false,
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies StoryObj<typeof MkAbuseReportResolver>;
|
155
packages/frontend/src/components/MkAbuseReportResolver.vue
Normal file
155
packages/frontend/src/components/MkAbuseReportResolver.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="_gaps dslkjkwejflew">
|
||||
<MkInput v-model="value.name" :readonly="!props.editable">
|
||||
<template #label>{{ i18n.ts.name }}</template>
|
||||
</MkInput>
|
||||
<div>
|
||||
<div :class="$style.label">{{ i18n.ts._abuse._resolver.targetUserPattern }}</div>
|
||||
<PrismEditor v-model="value.targetUserPattern" class="_code code" :class="$style.highlight" :highlight="highlighter" :lineNumbers="false" :readonly="!props.editable"/>
|
||||
</div>
|
||||
<div>
|
||||
<div :class="$style.label">{{ i18n.ts._abuse._resolver.reporterPattern }}</div>
|
||||
<PrismEditor v-model="value.reporterPattern" class="_code code" :class="$style.highlight" :highlight="highlighter" :lineNumbers="false" :readonly="!props.editable"/>
|
||||
</div>
|
||||
<div>
|
||||
<div :class="$style.label">{{ i18n.ts._abuse._resolver.reportContentPattern }}</div>
|
||||
<PrismEditor v-model="value.reportContentPattern" class="_code code" :class="$style.highlight" :highlight="highlighter" :lineNumbers="false" :readonly="!props.editable"/>
|
||||
</div>
|
||||
<MkSelect v-model="value.expiresAt" :disabled="!props.editable">
|
||||
<template #label>{{ i18n.ts._abuse._resolver.expiresAt }}<span v-if="expirationDate" style="float: right;">{{ expirationDate }}</span></template>
|
||||
<option value="1hour">{{ i18n.ts._abuse._resolver['1hour'] }}</option>
|
||||
<option value="12hours">{{ i18n.ts._abuse._resolver['12hours'] }}</option>
|
||||
<option value="1day">{{ i18n.ts._abuse._resolver['1day'] }}</option>
|
||||
<option value="1week">{{ i18n.ts._abuse._resolver['1week'] }}</option>
|
||||
<option value="1month">{{ i18n.ts._abuse._resolver['1month'] }}</option>
|
||||
<option value="3months">{{ i18n.ts._abuse._resolver['3months'] }}</option>
|
||||
<option value="6months">{{ i18n.ts._abuse._resolver['6months'] }}</option>
|
||||
<option value="1year">{{ i18n.ts._abuse._resolver['1year'] }}</option>
|
||||
<option value="indefinitely">{{ i18n.ts._abuse._resolver.indefinitely }}</option>
|
||||
</MkSelect>
|
||||
<MkSwitch v-model="value.forward" :disabled="!props.editable">
|
||||
{{ i18n.ts.forwardReport }}
|
||||
<template #caption>{{ i18n.ts.forwardReportIsAnonymous }}</template>
|
||||
</MkSwitch>
|
||||
<slot name="button"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch } from 'vue';
|
||||
import { PrismEditor } from 'vue-prism-editor';
|
||||
import { highlight, languages } from 'prismjs/components/prism-core';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import 'vue-prism-editor/dist/prismeditor.min.css';
|
||||
import 'prismjs/components/prism-clike';
|
||||
import 'prismjs/components/prism-regex';
|
||||
import 'prismjs/themes/prism-okaidia.css';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: {
|
||||
name: string;
|
||||
targetUserPattern: string | null;
|
||||
reporterPattern: string | null;
|
||||
reportContentPattern: string | null;
|
||||
expiresAt: string;
|
||||
forward: boolean;
|
||||
expirationDate: string;
|
||||
previousExpiresAt?: string;
|
||||
}
|
||||
editable: boolean;
|
||||
data?: {
|
||||
name: string;
|
||||
targetUserPattern: string | null;
|
||||
reporterPattern: string | null;
|
||||
reportContentPattern: string | null;
|
||||
expirationDate: string | null;
|
||||
expiresAt: string;
|
||||
forward: boolean;
|
||||
previousExpiresAt?: string;
|
||||
}
|
||||
}>();
|
||||
let expirationDate: string | null = $ref(null);
|
||||
|
||||
type NonNullType<T> = {
|
||||
[P in keyof T]: NonNullable<T[P]>
|
||||
}
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const value = computed({
|
||||
get() {
|
||||
const data = props.data ?? props.modelValue ?? {
|
||||
name: '',
|
||||
targetUserPattern: '',
|
||||
reporterPattern: '',
|
||||
reportContentPattern: '',
|
||||
expirationDate: null,
|
||||
expiresAt: 'indefinitely',
|
||||
forward: false,
|
||||
previousExpiresAt: undefined,
|
||||
};
|
||||
for (const [key, _value] of Object.entries(data)) {
|
||||
if (_value === null) {
|
||||
data[key] = '';
|
||||
}
|
||||
}
|
||||
if (props.modelValue && props.editable) {
|
||||
emit('update:modelValue', data);
|
||||
}
|
||||
return data as NonNullType<typeof data>;
|
||||
},
|
||||
set(updateValue) {
|
||||
if (props.modelValue && props.editable) {
|
||||
emit('update:modelValue', updateValue);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function highlighter(code) {
|
||||
return highlight(code, languages.regex);
|
||||
}
|
||||
|
||||
function renderExpirationDate(empty = false) {
|
||||
if (value.value.expirationDate && !empty) {
|
||||
expirationDate = new Date(value.value.expirationDate).toLocaleString();
|
||||
} else {
|
||||
expirationDate = null;
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => value.value.expirationDate, () => renderExpirationDate(), { immediate: true });
|
||||
watch(() => value.value.expiresAt, () => renderExpirationDate(true));
|
||||
watch(() => props.editable, () => {
|
||||
if (props.editable) {
|
||||
value.value.previousExpiresAt = value.value.expiresAt;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.dslkjkwejflew .prism-editor__textarea {
|
||||
padding-left: 10px !important;
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
|
||||
.dslkjkwejflew .prism-editor__editor {
|
||||
padding-left: 10px !important;
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" module>
|
||||
.label {
|
||||
font-size: 0.85em;
|
||||
padding: 0 0 8px 0;
|
||||
user-select: none;
|
||||
}
|
||||
.highlight {
|
||||
padding: 0;
|
||||
position: relative;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
@@ -108,6 +108,10 @@ onMounted(() => {
|
||||
const myBg = computedStyle.getPropertyValue('--panel');
|
||||
bgSame = parentBg === myBg;
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
toggle,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
@@ -18,7 +18,7 @@
|
||||
:spellcheck="spellcheck"
|
||||
:step="step"
|
||||
:list="id"
|
||||
@focus="focused = true"
|
||||
@focus="onFocus"
|
||||
@blur="focused = false"
|
||||
@keydown="onKeydown($event)"
|
||||
@input="onInput"
|
||||
@@ -98,6 +98,12 @@ const onKeydown = (ev: KeyboardEvent) => {
|
||||
}
|
||||
};
|
||||
|
||||
const onFocus = () => {
|
||||
if (!(props.readonly || props.disabled)) {
|
||||
focused.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const updated = () => {
|
||||
changed.value = false;
|
||||
if (type.value === 'number') {
|
||||
|
@@ -10,7 +10,6 @@
|
||||
:class="$style.inputCore"
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
:readonly="readonly"
|
||||
:placeholder="placeholder"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
@@ -60,7 +59,7 @@ const opening = ref(false);
|
||||
const changed = ref(false);
|
||||
const invalid = ref(false);
|
||||
const filled = computed(() => v.value !== '' && v.value != null);
|
||||
const inputEl = ref(null);
|
||||
const inputEl = ref<HTMLSelectElement | null>(null);
|
||||
const prefixEl = ref(null);
|
||||
const suffixEl = ref(null);
|
||||
const container = ref(null);
|
||||
@@ -119,6 +118,9 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
function show(ev: MouseEvent) {
|
||||
if (inputEl.value && inputEl.value.hasAttribute('disabled')) {
|
||||
return;
|
||||
}
|
||||
focused.value = true;
|
||||
opening.value = true;
|
||||
|
||||
|
Reference in New Issue
Block a user