From 0bbde336b3636f4135de54c0ed75c7aa208534fe Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sat, 8 Jan 2022 20:30:01 +0900 Subject: [PATCH 01/81] =?UTF-8?q?refactor:=20Widget=E3=81=AEcomposition=20?= =?UTF-8?q?api=E7=A7=BB=E8=A1=8C=20(#8125)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * wip * wip * wip * wip * wip * fix --- packages/client/src/components/widgets.vue | 79 ++++--- packages/client/src/scripts/form.ts | 30 ++- packages/client/src/widgets/activity.vue | 131 +++++------ packages/client/src/widgets/aichan.vue | 94 ++++---- packages/client/src/widgets/aiscript.vue | 176 ++++++++------- packages/client/src/widgets/button.vue | 151 +++++++------ packages/client/src/widgets/calendar.vue | 148 ++++++------ packages/client/src/widgets/clock.vue | 73 +++--- packages/client/src/widgets/define.ts | 75 ------- packages/client/src/widgets/digital-clock.vue | 123 +++++----- packages/client/src/widgets/federation.vue | 108 +++++---- packages/client/src/widgets/job-queue.vue | 188 ++++++++-------- packages/client/src/widgets/memo.vue | 98 ++++---- packages/client/src/widgets/notifications.vue | 95 ++++---- packages/client/src/widgets/online-users.vue | 80 ++++--- packages/client/src/widgets/photos.vue | 125 ++++++----- packages/client/src/widgets/post-form.vue | 38 ++-- packages/client/src/widgets/rss.vue | 107 +++++---- .../src/widgets/server-metric/index.vue | 123 +++++----- packages/client/src/widgets/slideshow.vue | 210 +++++++++--------- packages/client/src/widgets/timeline.vue | 201 +++++++++-------- packages/client/src/widgets/trends.vue | 86 +++---- packages/client/src/widgets/widget.ts | 71 ++++++ 23 files changed, 1389 insertions(+), 1221 deletions(-) delete mode 100644 packages/client/src/widgets/define.ts create mode 100644 packages/client/src/widgets/widget.ts diff --git a/packages/client/src/components/widgets.vue b/packages/client/src/components/widgets.vue index 12f7129253..ccde5fbe55 100644 --- a/packages/client/src/components/widgets.vue +++ b/packages/client/src/components/widgets.vue @@ -10,7 +10,7 @@ <MkButton inline @click="$emit('exit')">{{ $ts.close }}</MkButton> </header> <XDraggable - v-model="_widgets" + v-model="widgets_" item-key="id" animation="150" > @@ -18,7 +18,7 @@ <div class="customize-container"> <button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="fas fa-cog"></i></button> <button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="fas fa-times"></i></button> - <component :is="`mkw-${element.name}`" :widget="element" :setting-callback="setting => settings[element.id] = setting" @updateProps="updateWidget(element.id, $event)"/> + <component :ref="el => widgetRefs[element.id] = el" :is="`mkw-${element.name}`" :widget="element" @updateProps="updateWidget(element.id, $event)"/> </div> </template> </XDraggable> @@ -28,7 +28,7 @@ </template> <script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +import { defineComponent, defineAsyncComponent, reactive, ref, computed } from 'vue'; import { v4 as uuid } from 'uuid'; import MkSelect from '@/components/form/select.vue'; import MkButton from '@/components/ui/button.vue'; @@ -54,50 +54,47 @@ export default defineComponent({ emits: ['updateWidgets', 'addWidget', 'removeWidget', 'updateWidget', 'exit'], - data() { - return { - widgetAdderSelected: null, - widgetDefs, - settings: {}, + setup(props, context) { + const widgetRefs = reactive({}); + const configWidget = (id: string) => { + widgetRefs[id].configure(); }; - }, + const widgetAdderSelected = ref(null); + const addWidget = () => { + if (widgetAdderSelected.value == null) return; - computed: { - _widgets: { - get() { - return this.widgets; - }, - set(value) { - this.$emit('updateWidgets', value); - } - } - }, - - methods: { - configWidget(id) { - this.settings[id](); - }, - - addWidget() { - if (this.widgetAdderSelected == null) return; - - this.$emit('addWidget', { - name: this.widgetAdderSelected, + context.emit('addWidget', { + name: widgetAdderSelected.value, id: uuid(), - data: {} + data: {}, }); - this.widgetAdderSelected = null; - }, + widgetAdderSelected.value = null; + }; + const removeWidget = (widget) => { + context.emit('removeWidget', widget); + }; + const updateWidget = (id, data) => { + context.emit('updateWidget', { id, data }); + }; + const widgets_ = computed({ + get: () => props.widgets, + set: (value) => { + context.emit('updateWidgets', value); + }, + }); - removeWidget(widget) { - this.$emit('removeWidget', widget); - }, - - updateWidget(id, data) { - this.$emit('updateWidget', { id, data }); - }, - } + return { + widgetRefs, + configWidget, + widgetAdderSelected, + widgetDefs, + addWidget, + removeWidget, + updateWidget, + widgets_, + }; + }, }); </script> diff --git a/packages/client/src/scripts/form.ts b/packages/client/src/scripts/form.ts index 7bf6cec452..7f321cc0ae 100644 --- a/packages/client/src/scripts/form.ts +++ b/packages/client/src/scripts/form.ts @@ -21,11 +21,39 @@ export type FormItem = { default: string | null; hidden?: boolean; enum: string[]; +} | { + label?: string; + type: 'radio'; + default: unknown | null; + hidden?: boolean; + options: { + label: string; + value: unknown; + }[]; +} | { + label?: string; + type: 'object'; + default: Record<string, unknown> | null; + hidden: true; } | { label?: string; type: 'array'; default: unknown[] | null; - hidden?: boolean; + hidden: true; }; export type Form = Record<string, FormItem>; + +type GetItemType<Item extends FormItem> = + Item['type'] extends 'string' ? string : + Item['type'] extends 'number' ? number : + Item['type'] extends 'boolean' ? boolean : + Item['type'] extends 'radio' ? unknown : + Item['type'] extends 'enum' ? string : + Item['type'] extends 'array' ? unknown[] : + Item['type'] extends 'object' ? Record<string, unknown> + : never; + +export type GetFormResultType<F extends Form> = { + [P in keyof F]: GetItemType<F[P]>; +}; diff --git a/packages/client/src/widgets/activity.vue b/packages/client/src/widgets/activity.vue index d322f4758a..acbbb7a97a 100644 --- a/packages/client/src/widgets/activity.vue +++ b/packages/client/src/widgets/activity.vue @@ -1,82 +1,89 @@ <template> -<MkContainer :show-header="props.showHeader" :naked="props.transparent"> +<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent"> <template #header><i class="fas fa-chart-bar"></i>{{ $ts._widgets.activity }}</template> <template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template> <div> <MkLoading v-if="fetching"/> <template v-else> - <XCalendar v-show="props.view === 0" :data="[].concat(activity)"/> - <XChart v-show="props.view === 1" :data="[].concat(activity)"/> + <XCalendar v-show="widgetProps.view === 0" :data="[].concat(activity)"/> + <XChart v-show="widgetProps.view === 1" :data="[].concat(activity)"/> </template> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import * as os from '@/os'; import MkContainer from '@/components/ui/container.vue'; -import define from './define'; import XCalendar from './activity.calendar.vue'; import XChart from './activity.chart.vue'; -import * as os from '@/os'; +import { $i } from '@/account'; -const widget = define({ - name: 'activity', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - transparent: { - type: 'boolean', - default: false, - }, - view: { - type: 'number', - default: 0, - hidden: true, - }, - }) +const name = 'activity'; + +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, + }, + transparent: { + type: 'boolean' as const, + default: false, + }, + view: { + type: 'number' as const, + default: 0, + hidden: true, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const activity = ref(null); +const fetching = ref(true); + +const toggleView = () => { + if (widgetProps.view === 1) { + widgetProps.view = 0; + } else { + widgetProps.view++; + } + save(); +}; + +os.api('charts/user/notes', { + userId: $i.id, + span: 'day', + limit: 7 * 21, +}).then(res => { + activity.value = res.diffs.normal.map((_, i) => ({ + total: res.diffs.normal[i] + res.diffs.reply[i] + res.diffs.renote[i], + notes: res.diffs.normal[i], + replies: res.diffs.reply[i], + renotes: res.diffs.renote[i] + })); + fetching.value = false; }); -export default defineComponent({ - components: { - MkContainer, - XCalendar, - XChart, - }, - extends: widget, - data() { - return { - fetching: true, - activity: null, - }; - }, - mounted() { - os.api('charts/user/notes', { - userId: this.$i.id, - span: 'day', - limit: 7 * 21 - }).then(activity => { - this.activity = activity.diffs.normal.map((_, i) => ({ - total: activity.diffs.normal[i] + activity.diffs.reply[i] + activity.diffs.renote[i], - notes: activity.diffs.normal[i], - replies: activity.diffs.reply[i], - renotes: activity.diffs.renote[i] - })); - this.fetching = false; - }); - }, - methods: { - toggleView() { - if (this.props.view === 1) { - this.props.view = 0; - } else { - this.props.view++; - } - this.save(); - } - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/aichan.vue b/packages/client/src/widgets/aichan.vue index 891b7454d1..03e394b976 100644 --- a/packages/client/src/widgets/aichan.vue +++ b/packages/client/src/widgets/aichan.vue @@ -1,51 +1,65 @@ <template> -<MkContainer :naked="props.transparent" :show-header="false"> +<MkContainer :naked="widgetProps.transparent" :show-header="false"> <iframe ref="live2d" class="dedjhjmo" src="https://misskey-dev.github.io/mascot-web/?scale=1.5&y=1.1&eyeY=100" @click="touched"></iframe> </MkContainer> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import define from './define'; -import MkContainer from '@/components/ui/container.vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; -const widget = define({ - name: 'ai', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - }) +const name = 'ai'; + +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const live2d = ref<HTMLIFrameElement>(); + +const touched = () => { + //if (this.live2d) this.live2d.changeExpression('gurugurume'); +}; + +onMounted(() => { + const onMousemove = (ev: MouseEvent) => { + const iframeRect = live2d.value.getBoundingClientRect(); + live2d.value.contentWindow.postMessage({ + type: 'moveCursor', + body: { + x: ev.clientX - iframeRect.left, + y: ev.clientY - iframeRect.top, + } + }, '*'); + }; + + window.addEventListener('mousemove', onMousemove, { passive: true }); + onUnmounted(() => { + window.removeEventListener('mousemove', onMousemove); + }); }); -export default defineComponent({ - components: { - MkContainer, - }, - extends: widget, - data() { - return { - }; - }, - mounted() { - window.addEventListener('mousemove', ev => { - const iframeRect = this.$refs.live2d.getBoundingClientRect(); - this.$refs.live2d.contentWindow.postMessage({ - type: 'moveCursor', - body: { - x: ev.clientX - iframeRect.left, - y: ev.clientY - iframeRect.top, - } - }, '*'); - }, { passive: true }); - }, - methods: { - touched() { - //if (this.live2d) this.live2d.changeExpression('gurugurume'); - } - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/aiscript.vue b/packages/client/src/widgets/aiscript.vue index 46c5094ee9..0a5c0d614d 100644 --- a/packages/client/src/widgets/aiscript.vue +++ b/packages/client/src/widgets/aiscript.vue @@ -1,9 +1,9 @@ <template> -<MkContainer :show-header="props.showHeader"> +<MkContainer :show-header="widgetProps.showHeader"> <template #header><i class="fas fa-terminal"></i>{{ $ts._widgets.aiscript }}</template> <div class="uylguesu _monospace"> - <textarea v-model="props.script" placeholder="(1 + 1)"></textarea> + <textarea v-model="widgetProps.script" placeholder="(1 + 1)"></textarea> <button class="_buttonPrimary" @click="run">RUN</button> <div class="logs"> <div v-for="log in logs" :key="log.id" class="log" :class="{ print: log.print }">{{ log.text }}</div> @@ -12,97 +12,109 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkContainer from '@/components/ui/container.vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; +import MkContainer from '@/components/ui/container.vue'; import { AiScript, parse, utils } from '@syuilo/aiscript'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; +import { $i } from '@/account'; -const widget = define({ - name: 'aiscript', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - script: { - type: 'string', - multiline: true, - default: '(1 + 1)', - hidden: true, - }, - }) -}); +const name = 'aiscript'; -export default defineComponent({ - components: { - MkContainer +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, - - data() { - return { - logs: [], - }; + script: { + type: 'string' as const, + multiline: true, + default: '(1 + 1)', + hidden: true, }, +}; - methods: { - async run() { - this.logs = []; - const aiscript = new AiScript(createAiScriptEnv({ - storageKey: 'widget', - token: this.$i?.token, - }), { - in: (q) => { - return new Promise(ok => { - os.inputText({ - title: q, - }).then(({ canceled, result: a }) => { - ok(a); - }); - }); - }, - out: (value) => { - this.logs.push({ - id: Math.random(), - text: value.type === 'str' ? value.value : utils.valToString(value), - print: true - }); - }, - log: (type, params) => { - switch (type) { - case 'end': this.logs.push({ - id: Math.random(), - text: utils.valToString(params.val, true), - print: false - }); break; - default: break; - } - } +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const logs = ref<{ + id: string; + text: string; + print: boolean; +}[]>([]); + +const run = async () => { + logs.value = []; + const aiscript = new AiScript(createAiScriptEnv({ + storageKey: 'widget', + token: $i?.token, + }), { + in: (q) => { + return new Promise(ok => { + os.inputText({ + title: q, + }).then(({ canceled, result: a }) => { + ok(a); + }); }); - - let ast; - try { - ast = parse(this.props.script); - } catch (e) { - os.alert({ - type: 'error', - text: 'Syntax error :(' - }); - return; - } - try { - await aiscript.exec(ast); - } catch (e) { - os.alert({ - type: 'error', - text: e - }); - } }, + out: (value) => { + logs.value.push({ + id: Math.random().toString(), + text: value.type === 'str' ? value.value : utils.valToString(value), + print: true, + }); + }, + log: (type, params) => { + switch (type) { + case 'end': logs.value.push({ + id: Math.random().toString(), + text: utils.valToString(params.val, true), + print: false, + }); break; + default: break; + } + } + }); + + let ast; + try { + ast = parse(widgetProps.script); + } catch (e) { + os.alert({ + type: 'error', + text: 'Syntax error :(', + }); + return; } + try { + await aiscript.exec(ast); + } catch (e) { + os.alert({ + type: 'error', + text: e, + }); + } +}; + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/button.vue b/packages/client/src/widgets/button.vue index e98570862e..a33afd6e7a 100644 --- a/packages/client/src/widgets/button.vue +++ b/packages/client/src/widgets/button.vue @@ -1,90 +1,99 @@ <template> <div class="mkw-button"> - <MkButton :primary="props.colored" full @click="run"> - {{ props.label }} + <MkButton :primary="widgetProps.colored" full @click="run"> + {{ widgetProps.label }} </MkButton> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; -import MkButton from '@/components/ui/button.vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; import { AiScript, parse, utils } from '@syuilo/aiscript'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; +import { $i } from '@/account'; +import MkButton from '@/components/ui/button.vue'; -const widget = define({ - name: 'button', - props: () => ({ - label: { - type: 'string', - default: 'BUTTON', - }, - colored: { - type: 'boolean', - default: true, - }, - script: { - type: 'string', - multiline: true, - default: 'Mk:dialog("hello" "world")', - }, - }) -}); +const name = 'button'; -export default defineComponent({ - components: { - MkButton +const widgetPropsDef = { + label: { + type: 'string' as const, + default: 'BUTTON', }, - extends: widget, - data() { - return { - }; + colored: { + type: 'boolean' as const, + default: true, }, - methods: { - async run() { - const aiscript = new AiScript(createAiScriptEnv({ - storageKey: 'widget', - token: this.$i?.token, - }), { - in: (q) => { - return new Promise(ok => { - os.inputText({ - title: q, - }).then(({ canceled, result: a }) => { - ok(a); - }); - }); - }, - out: (value) => { - // nop - }, - log: (type, params) => { - // nop - } + script: { + type: 'string' as const, + multiline: true, + default: 'Mk:dialog("hello" "world")', + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const run = async () => { + const aiscript = new AiScript(createAiScriptEnv({ + storageKey: 'widget', + token: $i?.token, + }), { + in: (q) => { + return new Promise(ok => { + os.inputText({ + title: q, + }).then(({ canceled, result: a }) => { + ok(a); + }); }); - - let ast; - try { - ast = parse(this.props.script); - } catch (e) { - os.alert({ - type: 'error', - text: 'Syntax error :(' - }); - return; - } - try { - await aiscript.exec(ast); - } catch (e) { - os.alert({ - type: 'error', - text: e - }); - } }, + out: (value) => { + // nop + }, + log: (type, params) => { + // nop + } + }); + + let ast; + try { + ast = parse(widgetProps.script); + } catch (e) { + os.alert({ + type: 'error', + text: 'Syntax error :(', + }); + return; } + try { + await aiscript.exec(ast); + } catch (e) { + os.alert({ + type: 'error', + text: e, + }); + } +}; + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/calendar.vue b/packages/client/src/widgets/calendar.vue index c8b52d7afc..d16d3424b6 100644 --- a/packages/client/src/widgets/calendar.vue +++ b/packages/client/src/widgets/calendar.vue @@ -1,5 +1,5 @@ <template> -<div class="mkw-calendar" :class="{ _panel: !props.transparent }"> +<div class="mkw-calendar" :class="{ _panel: !widgetProps.transparent }"> <div class="calendar" :class="{ isHoliday }"> <p class="month-and-year"> <span class="year">{{ $t('yearX', { year }) }}</span> @@ -32,77 +32,87 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; +<script lang="ts" setup> +import { onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { i18n } from '@/i18n'; -const widget = define({ - name: 'calendar', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - }) +const name = 'calendar'; + +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const year = ref(0); +const month = ref(0); +const day = ref(0); +const weekDay = ref(''); +const yearP = ref(0); +const monthP = ref(0); +const dayP = ref(0); +const isHoliday = ref(false); +const tick = () => { + const now = new Date(); + const nd = now.getDate(); + const nm = now.getMonth(); + const ny = now.getFullYear(); + + year.value = ny; + month.value = nm + 1; + day.value = nd; + weekDay.value = [ + i18n.locale._weekday.sunday, + i18n.locale._weekday.monday, + i18n.locale._weekday.tuesday, + i18n.locale._weekday.wednesday, + i18n.locale._weekday.thursday, + i18n.locale._weekday.friday, + i18n.locale._weekday.saturday + ][now.getDay()]; + + const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime(); + const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/; + const monthNumer = now.getTime() - new Date(ny, nm, 1).getTime(); + const monthDenom = new Date(ny, nm + 1, 1).getTime() - new Date(ny, nm, 1).getTime(); + const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime(); + const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime(); + + dayP.value = dayNumer / dayDenom * 100; + monthP.value = monthNumer / monthDenom * 100; + yearP.value = yearNumer / yearDenom * 100; + + isHoliday.value = now.getDay() === 0 || now.getDay() === 6; +}; + +tick(); + +const intervalId = setInterval(tick, 1000); +onUnmounted(() => { + clearInterval(intervalId); }); -export default defineComponent({ - extends: widget, - data() { - return { - now: new Date(), - year: null, - month: null, - day: null, - weekDay: null, - yearP: null, - dayP: null, - monthP: null, - isHoliday: null, - clock: null - }; - }, - created() { - this.tick(); - this.clock = setInterval(this.tick, 1000); - }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - tick() { - const now = new Date(); - const nd = now.getDate(); - const nm = now.getMonth(); - const ny = now.getFullYear(); - - this.year = ny; - this.month = nm + 1; - this.day = nd; - this.weekDay = [ - this.$ts._weekday.sunday, - this.$ts._weekday.monday, - this.$ts._weekday.tuesday, - this.$ts._weekday.wednesday, - this.$ts._weekday.thursday, - this.$ts._weekday.friday, - this.$ts._weekday.saturday - ][now.getDay()]; - - const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime(); - const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/; - const monthNumer = now.getTime() - new Date(ny, nm, 1).getTime(); - const monthDenom = new Date(ny, nm + 1, 1).getTime() - new Date(ny, nm, 1).getTime(); - const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime(); - const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime(); - - this.dayP = dayNumer / dayDenom * 100; - this.monthP = monthNumer / monthDenom * 100; - this.yearP = yearNumer / yearDenom * 100; - - this.isHoliday = now.getDay() === 0 || now.getDay() === 6; - } - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/clock.vue b/packages/client/src/widgets/clock.vue index 6ca7ecd430..6acb10d74d 100644 --- a/packages/client/src/widgets/clock.vue +++ b/packages/client/src/widgets/clock.vue @@ -1,45 +1,56 @@ <template> -<MkContainer :naked="props.transparent" :show-header="false"> +<MkContainer :naked="widgetProps.transparent" :show-header="false"> <div class="vubelbmv"> - <MkAnalogClock class="clock" :thickness="props.thickness"/> + <MkAnalogClock class="clock" :thickness="widgetProps.thickness"/> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; +<script lang="ts" setup> +import { } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import MkContainer from '@/components/ui/container.vue'; import MkAnalogClock from '@/components/analog-clock.vue'; -import * as os from '@/os'; -const widget = define({ - name: 'clock', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - thickness: { - type: 'radio', - default: 0.1, - options: [{ - value: 0.1, label: 'thin' - }, { - value: 0.2, label: 'medium' - }, { - value: 0.3, label: 'thick' - }] - } - }) -}); +const name = 'clock'; -export default defineComponent({ - components: { - MkContainer, - MkAnalogClock +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, }, - extends: widget, + thickness: { + type: 'radio' as const, + default: 0.1, + options: [{ + value: 0.1, label: 'thin' + }, { + value: 0.2, label: 'medium' + }, { + value: 0.3, label: 'thick' + }], + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/define.ts b/packages/client/src/widgets/define.ts deleted file mode 100644 index 08a346d97c..0000000000 --- a/packages/client/src/widgets/define.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { defineComponent } from 'vue'; -import { throttle } from 'throttle-debounce'; -import { Form } from '@/scripts/form'; -import * as os from '@/os'; - -export default function <T extends Form>(data: { - name: string; - props?: () => T; -}) { - return defineComponent({ - props: { - widget: { - type: Object, - required: false - }, - settingCallback: { - required: false - } - }, - - emits: ['updateProps'], - - data() { - return { - props: this.widget ? JSON.parse(JSON.stringify(this.widget.data)) : {}, - save: throttle(3000, () => { - this.$emit('updateProps', this.props); - }), - }; - }, - - computed: { - id(): string { - return this.widget ? this.widget.id : null; - }, - }, - - created() { - this.mergeProps(); - - this.$watch('props', () => { - this.mergeProps(); - }, { deep: true }); - - if (this.settingCallback) this.settingCallback(this.setting); - }, - - methods: { - mergeProps() { - if (data.props) { - const defaultProps = data.props(); - for (const prop of Object.keys(defaultProps)) { - if (this.props.hasOwnProperty(prop)) continue; - this.props[prop] = defaultProps[prop].default; - } - } - }, - - async setting() { - const form = data.props(); - for (const item of Object.keys(form)) { - form[item].default = this.props[item]; - } - const { canceled, result } = await os.form(data.name, form); - if (canceled) return; - - for (const key of Object.keys(result)) { - this.props[key] = result[key]; - } - - this.save(); - }, - } - }); -} diff --git a/packages/client/src/widgets/digital-clock.vue b/packages/client/src/widgets/digital-clock.vue index fbf632d2de..637b0368be 100644 --- a/packages/client/src/widgets/digital-clock.vue +++ b/packages/client/src/widgets/digital-clock.vue @@ -1,73 +1,84 @@ <template> -<div class="mkw-digitalClock _monospace" :class="{ _panel: !props.transparent }" :style="{ fontSize: `${props.fontSize}em` }"> +<div class="mkw-digitalClock _monospace" :class="{ _panel: !widgetProps.transparent }" :style="{ fontSize: `${widgetProps.fontSize}em` }"> <span> <span v-text="hh"></span> <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> <span v-text="mm"></span> <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> <span v-text="ss"></span> - <span v-if="props.showMs" :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> - <span v-if="props.showMs" v-text="ms"></span> + <span v-if="widgetProps.showMs" :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> + <span v-if="widgetProps.showMs" v-text="ms"></span> </span> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onUnmounted, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; -const widget = define({ - name: 'digitalClock', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - fontSize: { - type: 'number', - default: 1.5, - step: 0.1, - }, - showMs: { - type: 'boolean', - default: true, - }, - }) +const name = 'digitalClock'; + +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, + }, + fontSize: { + type: 'number' as const, + default: 1.5, + step: 0.1, + }, + showMs: { + type: 'boolean' as const, + default: true, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +let intervalId; +const hh = ref(''); +const mm = ref(''); +const ss = ref(''); +const ms = ref(''); +const showColon = ref(true); +const tick = () => { + const now = new Date(); + hh.value = now.getHours().toString().padStart(2, '0'); + mm.value = now.getMinutes().toString().padStart(2, '0'); + ss.value = now.getSeconds().toString().padStart(2, '0'); + ms.value = Math.floor(now.getMilliseconds() / 10).toString().padStart(2, '0'); + showColon.value = now.getSeconds() % 2 === 0; +}; + +tick(); + +watch(() => widgetProps.showMs, () => { + if (intervalId) clearInterval(intervalId); + intervalId = setInterval(tick, widgetProps.showMs ? 10 : 1000); +}, { immediate: true }); + +onUnmounted(() => { + clearInterval(intervalId); }); -export default defineComponent({ - extends: widget, - data() { - return { - clock: null, - hh: null, - mm: null, - ss: null, - ms: null, - showColon: true, - }; - }, - created() { - this.tick(); - this.$watch(() => this.props.showMs, () => { - if (this.clock) clearInterval(this.clock); - this.clock = setInterval(this.tick, this.props.showMs ? 10 : 1000); - }, { immediate: true }); - }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - tick() { - const now = new Date(); - this.hh = now.getHours().toString().padStart(2, '0'); - this.mm = now.getMinutes().toString().padStart(2, '0'); - this.ss = now.getSeconds().toString().padStart(2, '0'); - this.ms = Math.floor(now.getMilliseconds() / 10).toString().padStart(2, '0'); - this.showColon = now.getSeconds() % 2 === 0; - } - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue index 736a91c52e..5d53b683b4 100644 --- a/packages/client/src/widgets/federation.vue +++ b/packages/client/src/widgets/federation.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="props.showHeader" :foldable="foldable" :scrollable="scrollable"> +<MkContainer :show-header="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable"> <template #header><i class="fas fa-globe"></i>{{ $ts._widgets.federation }}</template> <div class="wbrkwalb"> @@ -18,66 +18,64 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import MkContainer from '@/components/ui/container.vue'; -import define from './define'; import MkMiniChart from '@/components/mini-chart.vue'; import * as os from '@/os'; -const widget = define({ - name: 'federation', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - }) +const name = 'federation'; + +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps> & { foldable?: boolean; scrollable?: boolean; }>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; foldable?: boolean; scrollable?: boolean; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const instances = ref([]); +const charts = ref([]); +const fetching = ref(true); + +const fetch = async () => { + const instances = await os.api('federation/instances', { + sort: '+lastCommunicatedAt', + limit: 5 + }); + const charts = await Promise.all(instances.map(i => os.api('charts/instance', { host: i.host, limit: 16, span: 'hour' }))); + instances.value = instances; + charts.value = charts; + fetching.value = false; +}; + +onMounted(() => { + fetch(); + const intervalId = setInterval(fetch, 1000 * 60); + onUnmounted(() => { + clearInterval(intervalId); + }); }); -export default defineComponent({ - components: { - MkContainer, MkMiniChart - }, - extends: widget, - props: { - foldable: { - type: Boolean, - required: false, - default: false - }, - scrollable: { - type: Boolean, - required: false, - default: false - }, - }, - data() { - return { - instances: [], - charts: [], - fetching: true, - }; - }, - mounted() { - this.fetch(); - this.clock = setInterval(this.fetch, 1000 * 60); - }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - async fetch() { - const instances = await os.api('federation/instances', { - sort: '+lastCommunicatedAt', - limit: 5 - }); - const charts = await Promise.all(instances.map(i => os.api('charts/instance', { host: i.host, limit: 16, span: 'hour' }))); - this.instances = instances; - this.charts = charts; - this.fetching = false; - } - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/job-queue.vue b/packages/client/src/widgets/job-queue.vue index 1b7c71de67..4a2a3cf233 100644 --- a/packages/client/src/widgets/job-queue.vue +++ b/packages/client/src/widgets/job-queue.vue @@ -1,134 +1,146 @@ <template> -<div class="mkw-jobQueue _monospace" :class="{ _panel: !props.transparent }"> +<div class="mkw-jobQueue _monospace" :class="{ _panel: !widgetProps.transparent }"> <div class="inbox"> - <div class="label">Inbox queue<i v-if="inbox.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div> + <div class="label">Inbox queue<i v-if="current.inbox.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div> <div class="values"> <div> <div>Process</div> - <div :class="{ inc: inbox.activeSincePrevTick > prev.inbox.activeSincePrevTick, dec: inbox.activeSincePrevTick < prev.inbox.activeSincePrevTick }">{{ number(inbox.activeSincePrevTick) }}</div> + <div :class="{ inc: current.inbox.activeSincePrevTick > prev.inbox.activeSincePrevTick, dec: current.inbox.activeSincePrevTick < prev.inbox.activeSincePrevTick }">{{ number(current.inbox.activeSincePrevTick) }}</div> </div> <div> <div>Active</div> - <div :class="{ inc: inbox.active > prev.inbox.active, dec: inbox.active < prev.inbox.active }">{{ number(inbox.active) }}</div> + <div :class="{ inc: current.inbox.active > prev.inbox.active, dec: current.inbox.active < prev.inbox.active }">{{ number(current.inbox.active) }}</div> </div> <div> <div>Delayed</div> - <div :class="{ inc: inbox.delayed > prev.inbox.delayed, dec: inbox.delayed < prev.inbox.delayed }">{{ number(inbox.delayed) }}</div> + <div :class="{ inc: current.inbox.delayed > prev.inbox.delayed, dec: current.inbox.delayed < prev.inbox.delayed }">{{ number(current.inbox.delayed) }}</div> </div> <div> <div>Waiting</div> - <div :class="{ inc: inbox.waiting > prev.inbox.waiting, dec: inbox.waiting < prev.inbox.waiting }">{{ number(inbox.waiting) }}</div> + <div :class="{ inc: current.inbox.waiting > prev.inbox.waiting, dec: current.inbox.waiting < prev.inbox.waiting }">{{ number(current.inbox.waiting) }}</div> </div> </div> </div> <div class="deliver"> - <div class="label">Deliver queue<i v-if="deliver.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div> + <div class="label">Deliver queue<i v-if="current.deliver.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div> <div class="values"> <div> <div>Process</div> - <div :class="{ inc: deliver.activeSincePrevTick > prev.deliver.activeSincePrevTick, dec: deliver.activeSincePrevTick < prev.deliver.activeSincePrevTick }">{{ number(deliver.activeSincePrevTick) }}</div> + <div :class="{ inc: current.deliver.activeSincePrevTick > prev.deliver.activeSincePrevTick, dec: current.deliver.activeSincePrevTick < prev.deliver.activeSincePrevTick }">{{ number(current.deliver.activeSincePrevTick) }}</div> </div> <div> <div>Active</div> - <div :class="{ inc: deliver.active > prev.deliver.active, dec: deliver.active < prev.deliver.active }">{{ number(deliver.active) }}</div> + <div :class="{ inc: current.deliver.active > prev.deliver.active, dec: current.deliver.active < prev.deliver.active }">{{ number(current.deliver.active) }}</div> </div> <div> <div>Delayed</div> - <div :class="{ inc: deliver.delayed > prev.deliver.delayed, dec: deliver.delayed < prev.deliver.delayed }">{{ number(deliver.delayed) }}</div> + <div :class="{ inc: current.deliver.delayed > prev.deliver.delayed, dec: current.deliver.delayed < prev.deliver.delayed }">{{ number(current.deliver.delayed) }}</div> </div> <div> <div>Waiting</div> - <div :class="{ inc: deliver.waiting > prev.deliver.waiting, dec: deliver.waiting < prev.deliver.waiting }">{{ number(deliver.waiting) }}</div> + <div :class="{ inc: current.deliver.waiting > prev.deliver.waiting, dec: current.deliver.waiting < prev.deliver.waiting }">{{ number(current.deliver.waiting) }}</div> </div> </div> </div> </div> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import define from './define'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { stream } from '@/stream'; import number from '@/filters/number'; import * as sound from '@/scripts/sound'; +import * as os from '@/os'; -const widget = define({ - name: 'jobQueue', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - sound: { - type: 'boolean', - default: false, - }, - }) +const name = 'jobQueue'; + +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, + }, + sound: { + type: 'boolean' as const, + default: false, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const connection = stream.useChannel('queueStats'); +const current = reactive({ + inbox: { + activeSincePrevTick: 0, + active: 0, + waiting: 0, + delayed: 0, + }, + deliver: { + activeSincePrevTick: 0, + active: 0, + waiting: 0, + delayed: 0, + }, +}); +const prev = reactive({} as typeof current); +const jammedSound = sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1); + +for (const domain of ['inbox', 'deliver']) { + prev[domain] = JSON.parse(JSON.stringify(current[domain])); +} + +const onStats = (stats) => { + for (const domain of ['inbox', 'deliver']) { + prev[domain] = JSON.parse(JSON.stringify(current[domain])); + current[domain].activeSincePrevTick = stats[domain].activeSincePrevTick; + current[domain].active = stats[domain].active; + current[domain].waiting = stats[domain].waiting; + current[domain].delayed = stats[domain].delayed; + + if (current[domain].waiting > 0 && widgetProps.sound && jammedSound.paused) { + jammedSound.play(); + } + } +}; + +const onStatsLog = (statsLog) => { + for (const stats of [...statsLog].reverse()) { + onStats(stats); + } +}; + +connection.on('stats', onStats); +connection.on('statsLog', onStatsLog); + +connection.send('requestLog', { + id: Math.random().toString().substr(2, 8), + length: 1, }); -export default defineComponent({ - extends: widget, - data() { - return { - connection: markRaw(stream.useChannel('queueStats')), - inbox: { - activeSincePrevTick: 0, - active: 0, - waiting: 0, - delayed: 0, - }, - deliver: { - activeSincePrevTick: 0, - active: 0, - waiting: 0, - delayed: 0, - }, - prev: {}, - sound: sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1) - }; - }, - created() { - for (const domain of ['inbox', 'deliver']) { - this.prev[domain] = JSON.parse(JSON.stringify(this[domain])); - } - - this.connection.on('stats', this.onStats); - this.connection.on('statsLog', this.onStatsLog); +onUnmounted(() => { + connection.off('stats', onStats); + connection.off('statsLog', onStatsLog); + connection.dispose(); +}); - this.connection.send('requestLog', { - id: Math.random().toString().substr(2, 8), - length: 1 - }); - }, - beforeUnmount() { - this.connection.off('stats', this.onStats); - this.connection.off('statsLog', this.onStatsLog); - this.connection.dispose(); - }, - methods: { - onStats(stats) { - for (const domain of ['inbox', 'deliver']) { - this.prev[domain] = JSON.parse(JSON.stringify(this[domain])); - this[domain].activeSincePrevTick = stats[domain].activeSincePrevTick; - this[domain].active = stats[domain].active; - this[domain].waiting = stats[domain].waiting; - this[domain].delayed = stats[domain].delayed; - - if (this[domain].waiting > 0 && this.props.sound && this.sound.paused) { - this.sound.play(); - } - } - }, - - onStatsLog(statsLog) { - for (const stats of [...statsLog].reverse()) { - this.onStats(stats); - } - }, - - number - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/memo.vue b/packages/client/src/widgets/memo.vue index 9b51ada220..3dfc6eb5fa 100644 --- a/packages/client/src/widgets/memo.vue +++ b/packages/client/src/widgets/memo.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="props.showHeader"> +<MkContainer :show-header="widgetProps.showHeader"> <template #header><i class="fas fa-sticky-note"></i>{{ $ts._widgets.memo }}</template> <div class="otgbylcu"> @@ -9,56 +9,60 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkContainer from '@/components/ui/container.vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; +import MkContainer from '@/components/ui/container.vue'; +import { defaultStore } from '@/store'; -const widget = define({ - name: 'memo', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - }) +const name = 'memo'; + +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const text = ref<string | null>(defaultStore.state.memo); +const changed = ref(false); +let timeoutId; + +const saveMemo = () => { + defaultStore.set('memo', text.value); + changed.value = false; +}; + +const onChange = () => { + changed.value = true; + clearTimeout(timeoutId); + timeoutId = setTimeout(saveMemo, 1000); +}; + +watch(() => defaultStore.reactiveState.memo, newText => { + text.value = newText.value; }); -export default defineComponent({ - components: { - MkContainer - }, - extends: widget, - - data() { - return { - text: null, - changed: false, - timeoutId: null, - }; - }, - - created() { - this.text = this.$store.state.memo; - - this.$watch(() => this.$store.reactiveState.memo, text => { - this.text = text; - }); - }, - - methods: { - onChange() { - this.changed = true; - clearTimeout(this.timeoutId); - this.timeoutId = setTimeout(this.saveMemo, 1000); - }, - - saveMemo() { - this.$store.set('memo', this.text); - this.changed = false; - } - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/notifications.vue b/packages/client/src/widgets/notifications.vue index 568705b661..8cf29c9271 100644 --- a/packages/client/src/widgets/notifications.vue +++ b/packages/client/src/widgets/notifications.vue @@ -1,65 +1,68 @@ <template> -<MkContainer :style="`height: ${props.height}px;`" :show-header="props.showHeader" :scrollable="true"> +<MkContainer :style="`height: ${widgetProps.height}px;`" :show-header="widgetProps.showHeader" :scrollable="true"> <template #header><i class="fas fa-bell"></i>{{ $ts.notifications }}</template> - <template #func><button class="_button" @click="configure()"><i class="fas fa-cog"></i></button></template> + <template #func><button class="_button" @click="configureNotification()"><i class="fas fa-cog"></i></button></template> <div> - <XNotifications :include-types="props.includingTypes"/> + <XNotifications :include-types="widgetProps.includingTypes"/> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import MkContainer from '@/components/ui/container.vue'; import XNotifications from '@/components/notifications.vue'; -import define from './define'; import * as os from '@/os'; -const widget = define({ - name: 'notifications', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - height: { - type: 'number', - default: 300, - }, - includingTypes: { - type: 'array', - hidden: true, - default: null, - }, - }) -}); +const name = 'notifications'; -export default defineComponent({ - - components: { - MkContainer, - XNotifications, +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, - - data() { - return { - }; + height: { + type: 'number' as const, + default: 300, }, + includingTypes: { + type: 'array' as const, + hidden: true, + default: null, + }, +}; - methods: { - configure() { - os.popup(import('@/components/notification-setting-window.vue'), { - includingTypes: this.props.includingTypes, - }, { - done: async (res) => { - const { includingTypes } = res; - this.props.includingTypes = includingTypes; - this.save(); - } - }, 'closed'); +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const configureNotification = () => { + os.popup(import('@/components/notification-setting-window.vue'), { + includingTypes: widgetProps.includingTypes, + }, { + done: async (res) => { + const { includingTypes } = res; + widgetProps.includingTypes = includingTypes; + save(); } - } + }, 'closed'); +}; + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/online-users.vue b/packages/client/src/widgets/online-users.vue index 5b889f4816..2d47688697 100644 --- a/packages/client/src/widgets/online-users.vue +++ b/packages/client/src/widgets/online-users.vue @@ -1,48 +1,60 @@ <template> -<div class="mkw-onlineUsers" :class="{ _panel: !props.transparent, pad: !props.transparent }"> +<div class="mkw-onlineUsers" :class="{ _panel: !widgetProps.transparent, pad: !widgetProps.transparent }"> <I18n v-if="onlineUsersCount" :src="$ts.onlineUsersCount" text-tag="span" class="text"> <template #n><b>{{ onlineUsersCount }}</b></template> </I18n> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; -const widget = define({ - name: 'onlineUsers', - props: () => ({ - transparent: { - type: 'boolean', - default: true, - }, - }) +const name = 'onlineUsers'; + +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: true, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const onlineUsersCount = ref(0); + +const tick = () => { + os.api('get-online-users-count').then(res => { + onlineUsersCount.value = res.count; + }); +}; + +onMounted(() => { + tick(); + const intervalId = setInterval(tick, 1000 * 15); + onUnmounted(() => { + clearInterval(intervalId); + }); }); -export default defineComponent({ - extends: widget, - data() { - return { - onlineUsersCount: null, - clock: null, - }; - }, - created() { - this.tick(); - this.clock = setInterval(this.tick, 1000 * 15); - }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - tick() { - os.api('get-online-users-count').then(res => { - this.onlineUsersCount = res.count; - }); - } - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/photos.vue b/packages/client/src/widgets/photos.vue index 7a0b54027b..8f948dc643 100644 --- a/packages/client/src/widgets/photos.vue +++ b/packages/client/src/widgets/photos.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="props.showHeader" :naked="props.transparent" :class="$style.root" :data-transparent="props.transparent ? true : null"> +<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null"> <template #header><i class="fas fa-camera"></i>{{ $ts._widgets.photos }}</template> <div class=""> @@ -14,70 +14,77 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import MkContainer from '@/components/ui/container.vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { stream } from '@/stream'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import * as os from '@/os'; -import { stream } from '@/stream'; +import MkContainer from '@/components/ui/container.vue'; +import { defaultStore } from '@/store'; -const widget = define({ - name: 'photos', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - transparent: { - type: 'boolean', - default: false, - }, - }) +const name = 'photos'; + +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, + }, + transparent: { + type: 'boolean' as const, + default: false, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const connection = stream.useChannel('main'); +const images = ref([]); +const fetching = ref(true); + +const onDriveFileCreated = (file) => { + if (/^image\/.+$/.test(file.type)) { + images.value.unshift(file); + if (images.value.length > 9) images.value.pop(); + } +}; + +const thumbnail = (image: any): string => { + return defaultStore.state.disableShowingAnimatedImages + ? getStaticImageUrl(image.thumbnailUrl) + : image.thumbnailUrl; +}; + +os.api('drive/stream', { + type: 'image/*', + limit: 9 +}).then(res => { + images.value = res; + fetching.value = false; }); -export default defineComponent({ - components: { - MkContainer, - }, - extends: widget, - data() { - return { - images: [], - fetching: true, - connection: null, - }; - }, - mounted() { - this.connection = markRaw(stream.useChannel('main')); +connection.on('driveFileCreated', onDriveFileCreated); +onUnmounted(() => { + connection.dispose(); +}); - this.connection.on('driveFileCreated', this.onDriveFileCreated); - - os.api('drive/stream', { - type: 'image/*', - limit: 9 - }).then(images => { - this.images = images; - this.fetching = false; - }); - }, - beforeUnmount() { - this.connection.dispose(); - }, - methods: { - onDriveFileCreated(file) { - if (/^image\/.+$/.test(file.type)) { - this.images.unshift(file); - if (this.images.length > 9) this.images.pop(); - } - }, - - thumbnail(image: any): string { - return this.$store.state.disableShowingAnimatedImages - ? getStaticImageUrl(image.thumbnailUrl) - : image.thumbnailUrl; - }, - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/post-form.vue b/packages/client/src/widgets/post-form.vue index 6de0574cc1..51aa8fcf6b 100644 --- a/packages/client/src/widgets/post-form.vue +++ b/packages/client/src/widgets/post-form.vue @@ -2,22 +2,34 @@ <XPostForm class="_panel" :fixed="true" :autofocus="false"/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import XPostForm from '@/components/post-form.vue'; -import define from './define'; -const widget = define({ - name: 'postForm', - props: () => ({ - }) -}); +const name = 'postForm'; -export default defineComponent({ +const widgetPropsDef = { +}; - components: { - XPostForm, - }, - extends: widget, +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue index b2dc77854e..aa82054580 100644 --- a/packages/client/src/widgets/rss.vue +++ b/packages/client/src/widgets/rss.vue @@ -1,7 +1,7 @@ <template> -<MkContainer :show-header="props.showHeader"> +<MkContainer :show-header="widgetProps.showHeader"> <template #header><i class="fas fa-rss-square"></i>RSS</template> - <template #func><button class="_button" @click="setting"><i class="fas fa-cog"></i></button></template> + <template #func><button class="_button" @click="configure"><i class="fas fa-cog"></i></button></template> <div class="ekmkgxbj"> <MkLoading v-if="fetching"/> @@ -12,57 +12,66 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkContainer from '@/components/ui/container.vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; +import MkContainer from '@/components/ui/container.vue'; -const widget = define({ - name: 'rss', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - url: { - type: 'string', - default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', - }, - }) +const name = 'rss'; + +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, + }, + url: { + type: 'string' as const, + default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const items = ref([]); +const fetching = ref(true); + +const tick = () => { + fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.props.url}`, {}).then(res => { + res.json().then(feed => { + items.value = feed.items; + fetching.value = false; + }); + }); +}; + +watch(() => widgetProps.url, tick); + +onMounted(() => { + tick(); + const intervalId = setInterval(tick, 60000); + onUnmounted(() => { + clearInterval(intervalId); + }); }); -export default defineComponent({ - components: { - MkContainer - }, - extends: widget, - data() { - return { - items: [], - fetching: true, - clock: null, - }; - }, - mounted() { - this.fetch(); - this.clock = setInterval(this.fetch, 60000); - this.$watch(() => this.props.url, this.fetch); - }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - fetch() { - fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.props.url}`, { - }).then(res => { - res.json().then(feed => { - this.items = feed.items; - this.fetching = false; - }); - }); - }, - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/server-metric/index.vue b/packages/client/src/widgets/server-metric/index.vue index 107b750906..2caa73fa74 100644 --- a/packages/client/src/widgets/server-metric/index.vue +++ b/packages/client/src/widgets/server-metric/index.vue @@ -1,21 +1,22 @@ <template> -<MkContainer :show-header="props.showHeader" :naked="props.transparent"> +<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent"> <template #header><i class="fas fa-server"></i>{{ $ts._widgets.serverMetric }}</template> <template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template> <div v-if="meta" class="mkw-serverMetric"> - <XCpuMemory v-if="props.view === 0" :connection="connection" :meta="meta"/> - <XNet v-if="props.view === 1" :connection="connection" :meta="meta"/> - <XCpu v-if="props.view === 2" :connection="connection" :meta="meta"/> - <XMemory v-if="props.view === 3" :connection="connection" :meta="meta"/> - <XDisk v-if="props.view === 4" :connection="connection" :meta="meta"/> + <XCpuMemory v-if="widgetProps.view === 0" :connection="connection" :meta="meta"/> + <XNet v-else-if="widgetProps.view === 1" :connection="connection" :meta="meta"/> + <XCpu v-else-if="widgetProps.view === 2" :connection="connection" :meta="meta"/> + <XMemory v-else-if="widgetProps.view === 3" :connection="connection" :meta="meta"/> + <XDisk v-else-if="widgetProps.view === 4" :connection="connection" :meta="meta"/> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import define from '../define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from '../widget'; import MkContainer from '@/components/ui/container.vue'; import XCpuMemory from './cpu-mem.vue'; import XNet from './net.vue'; @@ -25,59 +26,61 @@ import XDisk from './disk.vue'; import * as os from '@/os'; import { stream } from '@/stream'; -const widget = define({ - name: 'serverMetric', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - transparent: { - type: 'boolean', - default: false, - }, - view: { - type: 'number', - default: 0, - hidden: true, - }, - }) +const name = 'serverMetric'; + +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, + }, + transparent: { + type: 'boolean' as const, + default: false, + }, + view: { + type: 'number' as const, + default: 0, + hidden: true, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const meta = ref(null); + +os.api('server-info', {}).then(res => { + meta.value = res; }); -export default defineComponent({ - components: { - MkContainer, - XCpuMemory, - XNet, - XCpu, - XMemory, - XDisk, - }, - extends: widget, - data() { - return { - meta: null, - connection: null, - }; - }, - created() { - os.api('server-info', {}).then(res => { - this.meta = res; - }); - this.connection = markRaw(stream.useChannel('serverStats')); - }, - unmounted() { - this.connection.dispose(); - }, - methods: { - toggleView() { - if (this.props.view == 4) { - this.props.view = 0; - } else { - this.props.view++; - } - this.save(); - }, +const toggleView = () => { + if (widgetProps.view == 4) { + widgetProps.view = 0; + } else { + widgetProps.view++; } + save(); +}; + +const connection = stream.useChannel('serverStats'); +onUnmounted(() => { + connection.dispose(); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/slideshow.vue b/packages/client/src/widgets/slideshow.vue index 0909bda67c..ac0c6c9e07 100644 --- a/packages/client/src/widgets/slideshow.vue +++ b/packages/client/src/widgets/slideshow.vue @@ -1,126 +1,116 @@ <template> -<div class="kvausudm _panel"> +<div class="kvausudm _panel" :style="{ height: widgetProps.height + 'px' }"> <div @click="choose"> - <p v-if="props.folderId == null"> - <template v-if="isCustomizeMode">{{ $t('folder-customize-mode') }}</template> - <template v-else>{{ $ts.folder }}</template> + <p v-if="widgetProps.folderId == null"> + {{ $ts.folder }} </p> - <p v-if="props.folderId != null && images.length === 0 && !fetching">{{ $t('no-image') }}</p> + <p v-if="widgetProps.folderId != null && images.length === 0 && !fetching">{{ $t('no-image') }}</p> <div ref="slideA" class="slide a"></div> <div ref="slideB" class="slide b"></div> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; +<script lang="ts" setup> +import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; -const widget = define({ - name: 'slideshow', - props: () => ({ - height: { - type: 'number', - default: 300, - }, - folderId: { - type: 'string', - default: null, - hidden: true, - }, - }) +const name = 'slideshow'; + +const widgetPropsDef = { + height: { + type: 'number' as const, + default: 300, + }, + folderId: { + type: 'string' as const, + default: null, + hidden: true, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const images = ref([]); +const fetching = ref(true); +const slideA = ref<HTMLElement>(); +const slideB = ref<HTMLElement>(); + +const change = () => { + if (images.value.length == 0) return; + + const index = Math.floor(Math.random() * images.value.length); + const img = `url(${ images.value[index].url })`; + + slideB.value.style.backgroundImage = img; + + slideB.value.classList.add('anime'); + setTimeout(() => { + // 既にこのウィジェットがunmountされていたら要素がない + if (slideA.value == null) return; + + slideA.value.style.backgroundImage = img; + + slideB.value.classList.remove('anime'); + }, 1000); +}; + +const fetch = () => { + fetching.value = true; + + os.api('drive/files', { + folderId: widgetProps.folderId, + type: 'image/*', + limit: 100 + }).then(res => { + images.value = res; + fetching.value = false; + slideA.value.style.backgroundImage = ''; + slideB.value.style.backgroundImage = ''; + change(); + }); +}; + +const choose = () => { + os.selectDriveFolder(false).then(folder => { + if (folder == null) { + return; + } + widgetProps.folderId = folder.id; + save(); + fetch(); + }); +}; + +onMounted(() => { + if (widgetProps.folderId != null) { + fetch(); + } + + const intervalId = setInterval(change, 10000); + onUnmounted(() => { + clearInterval(intervalId); + }); }); -export default defineComponent({ - extends: widget, - data() { - return { - images: [], - fetching: true, - clock: null - }; - }, - mounted() { - this.$nextTick(() => { - this.applySize(); - }); - - if (this.props.folderId != null) { - this.fetch(); - } - - this.clock = setInterval(this.change, 10000); - }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - applySize() { - let h; - - if (this.props.size == 1) { - h = 250; - } else { - h = 170; - } - - this.$el.style.height = `${h}px`; - }, - resize() { - if (this.props.size == 1) { - this.props.size = 0; - } else { - this.props.size++; - } - this.save(); - - this.applySize(); - }, - change() { - if (this.images.length == 0) return; - - const index = Math.floor(Math.random() * this.images.length); - const img = `url(${ this.images[index].url })`; - - (this.$refs.slideB as any).style.backgroundImage = img; - - this.$refs.slideB.classList.add('anime'); - setTimeout(() => { - // 既にこのウィジェットがunmountされていたら要素がない - if ((this.$refs.slideA as any) == null) return; - - (this.$refs.slideA as any).style.backgroundImage = img; - - this.$refs.slideB.classList.remove('anime'); - }, 1000); - }, - fetch() { - this.fetching = true; - - os.api('drive/files', { - folderId: this.props.folderId, - type: 'image/*', - limit: 100 - }).then(images => { - this.images = images; - this.fetching = false; - (this.$refs.slideA as any).style.backgroundImage = ''; - (this.$refs.slideB as any).style.backgroundImage = ''; - this.change(); - }); - }, - choose() { - os.selectDriveFolder(false).then(folder => { - if (folder == null) { - return; - } - this.props.folderId = folder.id; - this.save(); - this.fetch(); - }); - } - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/timeline.vue b/packages/client/src/widgets/timeline.vue index aee6a35b1d..fa700cc8ee 100644 --- a/packages/client/src/widgets/timeline.vue +++ b/packages/client/src/widgets/timeline.vue @@ -1,116 +1,129 @@ <template> -<MkContainer :show-header="props.showHeader" :style="`height: ${props.height}px;`" :scrollable="true"> +<MkContainer :show-header="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true"> <template #header> <button class="_button" @click="choose"> - <i v-if="props.src === 'home'" class="fas fa-home"></i> - <i v-else-if="props.src === 'local'" class="fas fa-comments"></i> - <i v-else-if="props.src === 'social'" class="fas fa-share-alt"></i> - <i v-else-if="props.src === 'global'" class="fas fa-globe"></i> - <i v-else-if="props.src === 'list'" class="fas fa-list-ul"></i> - <i v-else-if="props.src === 'antenna'" class="fas fa-satellite"></i> - <span style="margin-left: 8px;">{{ props.src === 'list' ? props.list.name : props.src === 'antenna' ? props.antenna.name : $t('_timelines.' + props.src) }}</span> + <i v-if="widgetProps.src === 'home'" class="fas fa-home"></i> + <i v-else-if="widgetProps.src === 'local'" class="fas fa-comments"></i> + <i v-else-if="widgetProps.src === 'social'" class="fas fa-share-alt"></i> + <i v-else-if="widgetProps.src === 'global'" class="fas fa-globe"></i> + <i v-else-if="widgetProps.src === 'list'" class="fas fa-list-ul"></i> + <i v-else-if="widgetProps.src === 'antenna'" class="fas fa-satellite"></i> + <span style="margin-left: 8px;">{{ widgetProps.src === 'list' ? widgetProps.list.name : widgetProps.src === 'antenna' ? widgetProps.antenna.name : $t('_timelines.' + widgetProps.src) }}</span> <i :class="menuOpened ? 'fas fa-angle-up' : 'fas fa-angle-down'" style="margin-left: 8px;"></i> </button> </template> <div> - <XTimeline :key="props.src === 'list' ? `list:${props.list.id}` : props.src === 'antenna' ? `antenna:${props.antenna.id}` : props.src" :src="props.src" :list="props.list ? props.list.id : null" :antenna="props.antenna ? props.antenna.id : null"/> + <XTimeline :key="widgetProps.src === 'list' ? `list:${widgetProps.list.id}` : widgetProps.src === 'antenna' ? `antenna:${widgetProps.antenna.id}` : widgetProps.src" :src="widgetProps.src" :list="widgetProps.list ? widgetProps.list.id : null" :antenna="widgetProps.antenna ? widgetProps.antenna.id : null"/> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import * as os from '@/os'; import MkContainer from '@/components/ui/container.vue'; import XTimeline from '@/components/timeline.vue'; -import define from './define'; -import * as os from '@/os'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -const widget = define({ - name: 'timeline', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - height: { - type: 'number', - default: 300, - }, - src: { - type: 'string', - default: 'home', - hidden: true, - }, - list: { - type: 'object', - default: null, - hidden: true, - }, - }) -}); +const name = 'timeline'; -export default defineComponent({ - components: { - MkContainer, - XTimeline, +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, - - data() { - return { - menuOpened: false, - }; + height: { + type: 'number' as const, + default: 300, }, + src: { + type: 'string' as const, + default: 'home', + hidden: true, + }, + antenna: { + type: 'object' as const, + default: null, + hidden: true, + }, + list: { + type: 'object' as const, + default: null, + hidden: true, + }, +}; - methods: { - async choose(ev) { - this.menuOpened = true; - const [antennas, lists] = await Promise.all([ - os.api('antennas/list'), - os.api('users/lists/list') - ]); - const antennaItems = antennas.map(antenna => ({ - text: antenna.name, - icon: 'fas fa-satellite', - action: () => { - this.props.antenna = antenna; - this.setSrc('antenna'); - } - })); - const listItems = lists.map(list => ({ - text: list.name, - icon: 'fas fa-list-ul', - action: () => { - this.props.list = list; - this.setSrc('list'); - } - })); - os.popupMenu([{ - text: this.$ts._timelines.home, - icon: 'fas fa-home', - action: () => { this.setSrc('home') } - }, { - text: this.$ts._timelines.local, - icon: 'fas fa-comments', - action: () => { this.setSrc('local') } - }, { - text: this.$ts._timelines.social, - icon: 'fas fa-share-alt', - action: () => { this.setSrc('social') } - }, { - text: this.$ts._timelines.global, - icon: 'fas fa-globe', - action: () => { this.setSrc('global') } - }, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget || ev.target).then(() => { - this.menuOpened = false; - }); - }, +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; - setSrc(src) { - this.props.src = src; - this.save(); - }, - } +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const menuOpened = ref(false); + +const setSrc = (src) => { + widgetProps.src = src; + save(); +}; + +const choose = async (ev) => { + menuOpened.value = true; + const [antennas, lists] = await Promise.all([ + os.api('antennas/list'), + os.api('users/lists/list') + ]); + const antennaItems = antennas.map(antenna => ({ + text: antenna.name, + icon: 'fas fa-satellite', + action: () => { + widgetProps.antenna = antenna; + setSrc('antenna'); + } + })); + const listItems = lists.map(list => ({ + text: list.name, + icon: 'fas fa-list-ul', + action: () => { + widgetProps.list = list; + setSrc('list'); + } + })); + os.popupMenu([{ + text: i18n.locale._timelines.home, + icon: 'fas fa-home', + action: () => { setSrc('home') } + }, { + text: i18n.locale._timelines.local, + icon: 'fas fa-comments', + action: () => { setSrc('local') } + }, { + text: i18n.locale._timelines.social, + icon: 'fas fa-share-alt', + action: () => { setSrc('social') } + }, { + text: i18n.locale._timelines.global, + icon: 'fas fa-globe', + action: () => { setSrc('global') } + }, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget || ev.target).then(() => { + menuOpened.value = false; + }); +}; + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue index ffad93c02b..3905daa673 100644 --- a/packages/client/src/widgets/trends.vue +++ b/packages/client/src/widgets/trends.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="props.showHeader"> +<MkContainer :show-header="widgetProps.showHeader"> <template #header><i class="fas fa-hashtag"></i>{{ $ts._widgets.trends }}</template> <div class="wbrkwala"> @@ -17,49 +17,59 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import MkContainer from '@/components/ui/container.vue'; -import define from './define'; import MkMiniChart from '@/components/mini-chart.vue'; import * as os from '@/os'; -const widget = define({ - name: 'hashtags', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - }) +const name = 'hashtags'; + +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const stats = ref([]); +const fetching = ref(true); + +const fetch = () => { + os.api('hashtags/trend').then(stats => { + stats.value = stats; + fetching.value = false; + }); +}; + +onMounted(() => { + fetch(); + const intervalId = setInterval(fetch, 1000 * 60); + onUnmounted(() => { + clearInterval(intervalId); + }); }); -export default defineComponent({ - components: { - MkContainer, MkMiniChart - }, - extends: widget, - data() { - return { - stats: [], - fetching: true, - }; - }, - mounted() { - this.fetch(); - this.clock = setInterval(this.fetch, 1000 * 60); - }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - fetch() { - os.api('hashtags/trend').then(stats => { - this.stats = stats; - this.fetching = false; - }); - } - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/widget.ts b/packages/client/src/widgets/widget.ts new file mode 100644 index 0000000000..81239bfb3b --- /dev/null +++ b/packages/client/src/widgets/widget.ts @@ -0,0 +1,71 @@ +import { reactive, watch } from 'vue'; +import { throttle } from 'throttle-debounce'; +import { Form, GetFormResultType } from '@/scripts/form'; +import * as os from '@/os'; + +export type Widget<P extends Record<string, unknown>> = { + id: string; + data: Partial<P>; +}; + +export type WidgetComponentProps<P extends Record<string, unknown>> = { + widget?: Widget<P>; +}; + +export type WidgetComponentEmits<P extends Record<string, unknown>> = { + (e: 'updateProps', props: P); +}; + +export type WidgetComponentExpose = { + name: string; + id: string | null; + configure: () => void; +}; + +export const useWidgetPropsManager = <F extends Form & Record<string, { default: any; }>>( + name: string, + propsDef: F, + props: Readonly<WidgetComponentProps<GetFormResultType<F>>>, + emit: WidgetComponentEmits<GetFormResultType<F>>, +): { + widgetProps: GetFormResultType<F>; + save: () => void; + configure: () => void; +} => { + const widgetProps = reactive(props.widget ? JSON.parse(JSON.stringify(props.widget.data)) : {}); + + const mergeProps = () => { + for (const prop of Object.keys(propsDef)) { + if (widgetProps.hasOwnProperty(prop)) continue; + widgetProps[prop] = propsDef[prop].default; + } + }; + watch(widgetProps, () => { + mergeProps(); + }, { deep: true, immediate: true, }); + + const save = throttle(3000, () => { + emit('updateProps', widgetProps) + }); + + const configure = async () => { + const form = JSON.parse(JSON.stringify(propsDef)); + for (const item of Object.keys(form)) { + form[item].default = widgetProps[item]; + } + const { canceled, result } = await os.form(name, form); + if (canceled) return; + + for (const key of Object.keys(result)) { + widgetProps[key] = result[key]; + } + + save(); + }; + + return { + widgetProps, + save, + configure, + }; +}; From d8f3622e166b988840d88dd0be561cdf712c7876 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 9 Jan 2022 00:00:27 +0900 Subject: [PATCH 02/81] fix --- packages/client/src/widgets/rss.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue index aa82054580..7a2272d744 100644 --- a/packages/client/src/widgets/rss.vue +++ b/packages/client/src/widgets/rss.vue @@ -50,7 +50,7 @@ const items = ref([]); const fetching = ref(true); const tick = () => { - fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.props.url}`, {}).then(res => { + fetch(`https://api.rss2json.com/v1/api.json?rss_url=${widgetProps.url}`, {}).then(res => { res.json().then(feed => { items.value = feed.items; fetching.value = false; From a10be38d0ee0e01e278422d58e2f2df7e20d3c40 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 9 Jan 2022 18:50:03 +0900 Subject: [PATCH 03/81] bye chat ui --- CHANGELOG.md | 1 + packages/client/src/init.ts | 1 - packages/client/src/menu.ts | 7 - .../src/ui/chat/date-separated-list.vue | 157 --- packages/client/src/ui/chat/header-clock.vue | 62 - packages/client/src/ui/chat/index.vue | 463 ------- packages/client/src/ui/chat/note-header.vue | 99 -- packages/client/src/ui/chat/note-preview.vue | 112 -- packages/client/src/ui/chat/note.sub.vue | 137 -- packages/client/src/ui/chat/note.vue | 1143 ----------------- packages/client/src/ui/chat/notes.vue | 94 -- packages/client/src/ui/chat/pages/channel.vue | 259 ---- .../client/src/ui/chat/pages/timeline.vue | 222 ---- packages/client/src/ui/chat/post-form.vue | 770 ----------- packages/client/src/ui/chat/side.vue | 157 --- packages/client/src/ui/chat/store.ts | 17 - .../client/src/ui/chat/sub-note-content.vue | 62 - packages/client/src/ui/chat/widgets.vue | 62 - 18 files changed, 1 insertion(+), 3824 deletions(-) delete mode 100644 packages/client/src/ui/chat/date-separated-list.vue delete mode 100644 packages/client/src/ui/chat/header-clock.vue delete mode 100644 packages/client/src/ui/chat/index.vue delete mode 100644 packages/client/src/ui/chat/note-header.vue delete mode 100644 packages/client/src/ui/chat/note-preview.vue delete mode 100644 packages/client/src/ui/chat/note.sub.vue delete mode 100644 packages/client/src/ui/chat/note.vue delete mode 100644 packages/client/src/ui/chat/notes.vue delete mode 100644 packages/client/src/ui/chat/pages/channel.vue delete mode 100644 packages/client/src/ui/chat/pages/timeline.vue delete mode 100644 packages/client/src/ui/chat/post-form.vue delete mode 100644 packages/client/src/ui/chat/side.vue delete mode 100644 packages/client/src/ui/chat/store.ts delete mode 100644 packages/client/src/ui/chat/sub-note-content.vue delete mode 100644 packages/client/src/ui/chat/widgets.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 99ab40f761..501e5cad98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Changes - Room機能が削除されました - 後日別リポジトリとして復活予定です +- Chat UIが削除されました ### Improvements diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index 2263b4ca3c..af70aec70a 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -172,7 +172,6 @@ const app = createApp(await ( !$i ? import('@/ui/visitor.vue') : ui === 'deck' ? import('@/ui/deck.vue') : ui === 'desktop' ? import('@/ui/desktop.vue') : - ui === 'chat' ? import('@/ui/chat/index.vue') : ui === 'classic' ? import('@/ui/classic.vue') : import('@/ui/universal.vue') ).then(x => x.default)); diff --git a/packages/client/src/menu.ts b/packages/client/src/menu.ts index ea6f801fec..98a892d569 100644 --- a/packages/client/src/menu.ts +++ b/packages/client/src/menu.ts @@ -198,13 +198,6 @@ export const menuDef = reactive({ localStorage.setItem('ui', 'classic'); unisonReload(); } - }, { - text: 'Chat (β)', - active: ui === 'chat', - action: () => { - localStorage.setItem('ui', 'chat'); - unisonReload(); - } }, /*{ text: i18n.locale.desktop + ' (β)', active: ui === 'desktop', diff --git a/packages/client/src/ui/chat/date-separated-list.vue b/packages/client/src/ui/chat/date-separated-list.vue deleted file mode 100644 index 1a36aca6dd..0000000000 --- a/packages/client/src/ui/chat/date-separated-list.vue +++ /dev/null @@ -1,157 +0,0 @@ -<script lang="ts"> -import { defineComponent, h, PropType, TransitionGroup } from 'vue'; -import MkAd from '@/components/global/ad.vue'; - -export default defineComponent({ - props: { - items: { - type: Array as PropType<{ id: string; createdAt: string; _shouldInsertAd_: boolean; }[]>, - required: true, - }, - reversed: { - type: Boolean, - required: false, - default: false - }, - ad: { - type: Boolean, - required: false, - default: false - }, - }, - - render() { - const getDateText = (time: string) => { - const date = new Date(time).getDate(); - const month = new Date(time).getMonth() + 1; - return this.$t('monthAndDay', { - month: month.toString(), - day: date.toString() - }); - } - - return h(this.reversed ? 'div' : TransitionGroup, { - class: 'hmjzthxl', - name: this.reversed ? 'list-reversed' : 'list', - tag: 'div', - }, this.items.map((item, i) => { - const el = this.$slots.default({ - item: item - })[0]; - if (el.key == null && item.id) el.key = item.id; - - if ( - i != this.items.length - 1 && - new Date(item.createdAt).getDate() != new Date(this.items[i + 1].createdAt).getDate() - ) { - const separator = h('div', { - class: 'separator', - key: item.id + ':separator', - }, h('p', { - class: 'date' - }, [ - h('span', [ - h('i', { - class: 'fas fa-angle-up icon', - }), - getDateText(item.createdAt) - ]), - h('span', [ - getDateText(this.items[i + 1].createdAt), - h('i', { - class: 'fas fa-angle-down icon', - }) - ]) - ])); - - return [el, separator]; - } else { - if (this.ad && item._shouldInsertAd_) { - return [h(MkAd, { - class: 'a', // advertiseの意(ブロッカー対策) - key: item.id + ':ad', - prefer: ['horizontal', 'horizontal-big'], - }), el]; - } else { - return el; - } - } - })); - }, -}); -</script> - -<style lang="scss"> -.hmjzthxl { - > .list-move { - transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1); - } - > .list-enter-active { - transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1); - } - > .list-enter-from { - opacity: 0; - transform: translateY(-64px); - } - - > .list-reversed-enter-active, > .list-reversed-leave-active { - transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1); - } - > .list-reversed-enter-from { - opacity: 0; - transform: translateY(64px); - } -} -</style> - -<style lang="scss"> -.hmjzthxl { - > .separator { - text-align: center; - position: relative; - - &:before { - content: ""; - display: block; - position: absolute; - top: 50%; - left: 0; - right: 0; - margin: auto; - width: calc(100% - 32px); - height: 1px; - background: var(--divider); - } - - > .date { - display: inline-block; - position: relative; - margin: 0; - padding: 0 16px; - line-height: 32px; - text-align: center; - font-size: 12px; - color: var(--dateLabelFg); - background: var(--panel); - - > span { - &:first-child { - margin-right: 8px; - - > .icon { - margin-right: 8px; - } - } - - &:last-child { - margin-left: 8px; - - > .icon { - margin-left: 8px; - } - } - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/header-clock.vue b/packages/client/src/ui/chat/header-clock.vue deleted file mode 100644 index 3488289c21..0000000000 --- a/packages/client/src/ui/chat/header-clock.vue +++ /dev/null @@ -1,62 +0,0 @@ -<template> -<div class="acemodlh _monospace"> - <div> - <span v-text="y"></span>/<span v-text="m"></span>/<span v-text="d"></span> - </div> - <div> - <span v-text="hh"></span> - <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> - <span v-text="mm"></span> - <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> - <span v-text="ss"></span> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; - -export default defineComponent({ - data() { - return { - clock: null, - y: null, - m: null, - d: null, - hh: null, - mm: null, - ss: null, - showColon: true, - }; - }, - created() { - this.tick(); - this.clock = setInterval(this.tick, 1000); - }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - tick() { - const now = new Date(); - this.y = now.getFullYear().toString(); - this.m = (now.getMonth() + 1).toString().padStart(2, '0'); - this.d = now.getDate().toString().padStart(2, '0'); - this.hh = now.getHours().toString().padStart(2, '0'); - this.mm = now.getMinutes().toString().padStart(2, '0'); - this.ss = now.getSeconds().toString().padStart(2, '0'); - this.showColon = now.getSeconds() % 2 === 0; - } - } -}); -</script> - -<style lang="scss" scoped> -.acemodlh { - opacity: 0.7; - font-size: 0.85em; - line-height: 1em; - text-align: center; -} -</style> diff --git a/packages/client/src/ui/chat/index.vue b/packages/client/src/ui/chat/index.vue deleted file mode 100644 index f66ab4dcee..0000000000 --- a/packages/client/src/ui/chat/index.vue +++ /dev/null @@ -1,463 +0,0 @@ -<template> -<div class="mk-app" @contextmenu.self.prevent="onContextmenu"> - <XSidebar ref="menu" class="menu" :default-hidden="true"/> - - <div class="nav"> - <header class="header"> - <div class="left"> - <button class="_button account" @click="openAccountMenu"> - <MkAvatar :user="$i" class="avatar"/><!--<MkAcct class="text" :user="$i"/>--> - </button> - </div> - <div class="right"> - <MkA v-tooltip="$ts.messaging" class="item" to="/my/messaging"><i class="fas fa-comments icon"></i><span v-if="$i.hasUnreadMessagingMessage" class="indicator"><i class="fas fa-circle"></i></span></MkA> - <MkA v-tooltip="$ts.directNotes" class="item" to="/my/messages"><i class="fas fa-envelope icon"></i><span v-if="$i.hasUnreadSpecifiedNotes" class="indicator"><i class="fas fa-circle"></i></span></MkA> - <MkA v-tooltip="$ts.mentions" class="item" to="/my/mentions"><i class="fas fa-at icon"></i><span v-if="$i.hasUnreadMentions" class="indicator"><i class="fas fa-circle"></i></span></MkA> - <MkA v-tooltip="$ts.notifications" class="item" to="/my/notifications"><i class="fas fa-bell icon"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></MkA> - </div> - </header> - <div class="body"> - <div class="container"> - <div class="header">{{ $ts.timeline }}</div> - <div class="body"> - <MkA to="/timeline/home" class="item" :class="{ active: tl === 'home' }"><i class="fas fa-home icon"></i>{{ $ts._timelines.home }}</MkA> - <MkA to="/timeline/local" class="item" :class="{ active: tl === 'local' }"><i class="fas fa-comments icon"></i>{{ $ts._timelines.local }}</MkA> - <MkA to="/timeline/social" class="item" :class="{ active: tl === 'social' }"><i class="fas fa-share-alt icon"></i>{{ $ts._timelines.social }}</MkA> - <MkA to="/timeline/global" class="item" :class="{ active: tl === 'global' }"><i class="fas fa-globe icon"></i>{{ $ts._timelines.global }}</MkA> - </div> - </div> - <div v-if="followedChannels" class="container"> - <div class="header">{{ $ts.channel }} ({{ $ts.following }})<button class="_button add" @click="addChannel"><i class="fas fa-plus"></i></button></div> - <div class="body"> - <MkA v-for="channel in followedChannels" :key="channel.id" :to="`/channels/${ channel.id }`" class="item" :class="{ active: tl === `channel:${ channel.id }`, read: !channel.hasUnreadNote }"><i class="fas fa-satellite-dish icon"></i>{{ channel.name }}</MkA> - </div> - </div> - <div v-if="featuredChannels" class="container"> - <div class="header">{{ $ts.channel }}<button class="_button add" @click="addChannel"><i class="fas fa-plus"></i></button></div> - <div class="body"> - <MkA v-for="channel in featuredChannels" :key="channel.id" :to="`/channels/${ channel.id }`" class="item" :class="{ active: tl === `channel:${ channel.id }` }"><i class="fas fa-satellite-dish icon"></i>{{ channel.name }}</MkA> - </div> - </div> - <div v-if="lists" class="container"> - <div class="header">{{ $ts.lists }}<button class="_button add" @click="addList"><i class="fas fa-plus"></i></button></div> - <div class="body"> - <MkA v-for="list in lists" :key="list.id" :to="`/my/list/${ list.id }`" class="item" :class="{ active: tl === `list:${ list.id }` }"><i class="fas fa-list-ul icon"></i>{{ list.name }}</MkA> - </div> - </div> - <div v-if="antennas" class="container"> - <div class="header">{{ $ts.antennas }}<button class="_button add" @click="addAntenna"><i class="fas fa-plus"></i></button></div> - <div class="body"> - <MkA v-for="antenna in antennas" :key="antenna.id" :to="`/my/antenna/${ antenna.id }`" class="item" :class="{ active: tl === `antenna:${ antenna.id }` }"><i class="fas fa-satellite icon"></i>{{ antenna.name }}</MkA> - </div> - </div> - <div class="container"> - <div class="body"> - <MkA to="/my/favorites" class="item"><i class="fas fa-star icon"></i>{{ $ts.favorites }}</MkA> - </div> - </div> - <MkAd class="a" :prefer="['square']"/> - </div> - <footer class="footer"> - <div class="left"> - <button class="_button menu" @click="showMenu"> - <i class="fas fa-bars icon"></i> - </button> - </div> - <div class="right"> - <button v-tooltip="$ts.search" class="_button item search" @click="search"> - <i class="fas fa-search icon"></i> - </button> - <MkA v-tooltip="$ts.settings" class="item" to="/settings"><i class="fas fa-cog icon"></i></MkA> - </div> - </footer> - </div> - - <main class="main" @contextmenu.stop="onContextmenu"> - <header class="header"> - <MkHeader class="header" :info="pageInfo" :menu="menu" :center="false" @click="onHeaderClick"/> - </header> - <router-view v-slot="{ Component }"> - <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> - <keep-alive :include="['timeline']"> - <component :is="Component" :ref="changePage" class="body"/> - </keep-alive> - </transition> - </router-view> - </main> - - <XSide ref="side" class="side" @open="sideViewOpening = true" @close="sideViewOpening = false"/> - <div class="side widgets" :class="{ sideViewOpening }"> - <XWidgets/> - </div> - - <XCommon/> -</div> -</template> - -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; -import { instanceName, url } from '@/config'; -import XSidebar from '@/ui/_common_/sidebar.vue'; -import XWidgets from './widgets.vue'; -import XCommon from '../_common_/common.vue'; -import XSide from './side.vue'; -import XHeaderClock from './header-clock.vue'; -import * as os from '@/os'; -import { router } from '@/router'; -import { menuDef } from '@/menu'; -import { search } from '@/scripts/search'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { store } from './store'; -import * as symbols from '@/symbols'; -import { openAccountMenu } from '@/account'; - -export default defineComponent({ - components: { - XCommon, - XSidebar, - XWidgets, - XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる - XHeaderClock, - }, - - provide() { - return { - sideViewHook: (path) => { - this.$refs.side.navigate(path); - } - }; - }, - - data() { - return { - pageInfo: null, - lists: null, - antennas: null, - followedChannels: null, - featuredChannels: null, - currentChannel: null, - menuDef: menuDef, - sideViewOpening: false, - instanceName, - }; - }, - - computed: { - menu() { - return [{ - icon: 'fas fa-columns', - text: this.$ts.openInSideView, - action: () => { - this.$refs.side.navigate(this.$route.path); - } - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(this.$route.path); - } - }]; - } - }, - - created() { - if (window.innerWidth < 1024) { - localStorage.setItem('ui', 'default'); - location.reload(); - } - - os.api('users/lists/list').then(lists => { - this.lists = lists; - }); - - os.api('antennas/list').then(antennas => { - this.antennas = antennas; - }); - - os.api('channels/followed', { limit: 20 }).then(channels => { - this.followedChannels = channels; - }); - - // TODO: pagination - os.api('channels/featured', { limit: 20 }).then(channels => { - this.featuredChannels = channels; - }); - }, - - methods: { - changePage(page) { - console.log(page); - if (page == null) return; - if (page[symbols.PAGE_INFO]) { - this.pageInfo = page[symbols.PAGE_INFO]; - document.title = `${this.pageInfo.title} | ${instanceName}`; - } - }, - - showMenu() { - this.$refs.menu.show(); - }, - - post() { - os.post(); - }, - - search() { - search(); - }, - - back() { - history.back(); - }, - - top() { - window.scroll({ top: 0, behavior: 'smooth' }); - }, - - onTransition() { - if (window._scroll) window._scroll(); - }, - - onHeaderClick() { - window.scroll({ top: 0, behavior: 'smooth' }); - }, - - onContextmenu(e) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(e.target)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return; - if (window.getSelection().toString() !== '') return; - const path = this.$route.path; - os.contextMenu([{ - type: 'label', - text: path, - }, { - icon: 'fas fa-columns', - text: this.$ts.openInSideView, - action: () => { - this.$refs.side.navigate(path); - } - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(path); - } - }], e); - }, - - openAccountMenu, - } -}); -</script> - -<style lang="scss" scoped> -.mk-app { - $header-height: 54px; // TODO: どこかに集約したい - $ui-font-size: 1em; // TODO: どこかに集約したい - - // ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ - height: calc(var(--vh, 1vh) * 100); - display: flex; - - > .nav { - display: flex; - flex-direction: column; - width: 250px; - height: 100vh; - border-right: solid 4px var(--divider); - - > .header, > .footer { - $padding: 8px; - display: flex; - align-items: center; - z-index: 1000; - height: $header-height; - padding: $padding; - box-sizing: border-box; - user-select: none; - - &.header { - border-bottom: solid 0.5px var(--divider); - } - - &.footer { - border-top: solid 0.5px var(--divider); - } - - > .left, > .right { - > .item, > .menu { - display: inline-flex; - vertical-align: middle; - height: ($header-height - ($padding * 2)); - width: ($header-height - ($padding * 2)); - box-sizing: border-box; - //opacity: 0.6; - position: relative; - border-radius: 5px; - - &:hover { - background: rgba(0, 0, 0, 0.05); - } - - > .icon { - margin: auto; - } - - > .indicator { - position: absolute; - top: 8px; - right: 8px; - color: var(--indicator); - font-size: 8px; - line-height: 8px; - animation: blink 1s infinite; - } - } - } - - > .left { - flex: 1; - min-width: 0; - - > .account { - display: flex; - align-items: center; - padding: 0 8px; - - > .avatar { - width: 26px; - height: 26px; - margin-right: 8px; - } - - > .text { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-size: 0.9em; - } - } - } - - > .right { - margin-left: auto; - } - } - - > .body { - flex: 1; - min-width: 0; - overflow: auto; - - > .container { - margin-top: 8px; - margin-bottom: 8px; - - & + .container { - margin-top: 16px; - } - - > .header { - display: flex; - font-size: 0.9em; - padding: 8px 16px; - position: sticky; - top: 0; - background: var(--X17); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); - z-index: 1; - color: var(--fgTransparentWeak); - - > .add { - margin-left: auto; - color: var(--fgTransparentWeak); - - &:hover { - color: var(--fg); - } - } - } - - > .body { - padding: 0 8px; - - > .item { - display: block; - padding: 6px 8px; - border-radius: 4px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - - &:hover { - text-decoration: none; - background: rgba(0, 0, 0, 0.05); - } - - &.active, &.active:hover { - background: var(--accent); - color: #fff !important; - } - - &.read { - color: var(--fgTransparent); - } - - > .icon { - margin-right: 8px; - opacity: 0.6; - } - } - } - } - - > .a { - margin: 12px; - } - } - } - - > .main { - display: flex; - flex: 1; - flex-direction: column; - min-width: 0; - height: 100vh; - position: relative; - background: var(--panel); - - > .header { - z-index: 1000; - height: $header-height; - background-color: var(--panel); - border-bottom: solid 0.5px var(--divider); - user-select: none; - } - - > .body { - width: 100%; - box-sizing: border-box; - overflow: auto; - } - } - - > .side { - width: 350px; - border-left: solid 4px var(--divider); - background: var(--panel); - - &.widgets.sideViewOpening { - @media (max-width: 1400px) { - display: none; - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/note-header.vue b/packages/client/src/ui/chat/note-header.vue deleted file mode 100644 index 5f87fdd14e..0000000000 --- a/packages/client/src/ui/chat/note-header.vue +++ /dev/null @@ -1,99 +0,0 @@ -<template> -<header class="dehvdgxo"> - <MkA v-user-preview="note.user.id" class="name" :to="userPage(note.user)"> - <MkUserName :user="note.user"/> - </MkA> - <span v-if="note.user.isBot" class="is-bot">bot</span> - <span class="username"><MkAcct :user="note.user"/></span> - <div class="info"> - <MkA class="created-at" :to="notePage(note)"> - <MkTime :time="note.createdAt"/> - </MkA> - <span v-if="note.visibility !== 'public'" class="visibility"> - <i v-if="note.visibility === 'home'" class="fas fa-home"></i> - <i v-else-if="note.visibility === 'followers'" class="fas fa-unlock"></i> - <i v-else-if="note.visibility === 'specified'" class="fas fa-envelope"></i> - </span> - <span v-if="note.localOnly" class="localOnly"><i class="fas fa-biohazard"></i></span> - </div> -</header> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { notePage } from '@/filters/note'; -import { userPage } from '@/filters/user'; -import * as os from '@/os'; - -export default defineComponent({ - props: { - note: { - type: Object, - required: true - }, - }, - - data() { - return { - }; - }, - - methods: { - notePage, - userPage - } -}); -</script> - -<style lang="scss" scoped> -.dehvdgxo { - display: flex; - align-items: baseline; - white-space: nowrap; - font-size: 0.9em; - - > .name { - display: block; - margin: 0 .5em 0 0; - padding: 0; - overflow: hidden; - font-size: 1em; - font-weight: bold; - text-decoration: none; - text-overflow: ellipsis; - - &:hover { - text-decoration: underline; - } - } - - > .is-bot { - flex-shrink: 0; - align-self: center; - margin: 0 .5em 0 0; - padding: 1px 6px; - font-size: 80%; - border: solid 0.5px var(--divider); - border-radius: 3px; - } - - > .username { - margin: 0 .5em 0 0; - overflow: hidden; - text-overflow: ellipsis; - } - - > .info { - font-size: 0.9em; - opacity: 0.7; - - > .visibility { - margin-left: 8px; - } - - > .localOnly { - margin-left: 8px; - } - } -} -</style> diff --git a/packages/client/src/ui/chat/note-preview.vue b/packages/client/src/ui/chat/note-preview.vue deleted file mode 100644 index c28591815e..0000000000 --- a/packages/client/src/ui/chat/note-preview.vue +++ /dev/null @@ -1,112 +0,0 @@ -<template> -<div class="hduudsxk"> - <MkAvatar class="avatar" :user="note.user"/> - <div class="main"> - <XNoteHeader class="header" :note="note" :mini="true"/> - <div class="body"> - <p v-if="note.cw != null" class="cw"> - <span v-if="note.cw != ''" class="text">{{ note.cw }}</span> - <XCwButton v-model="showContent" :note="note"/> - </p> - <div v-show="note.cw == null || showContent" class="content"> - <XSubNote-content class="text" :note="note"/> - </div> - </div> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import XNoteHeader from './note-header.vue'; -import XSubNoteContent from './sub-note-content.vue'; -import XCwButton from '@/components/cw-button.vue'; -import * as os from '@/os'; - -export default defineComponent({ - components: { - XNoteHeader, - XSubNoteContent, - XCwButton, - }, - - props: { - note: { - type: Object, - required: true - } - }, - - data() { - return { - showContent: false - }; - } -}); -</script> - -<style lang="scss" scoped> -.hduudsxk { - display: flex; - margin: 0; - padding: 0; - overflow: hidden; - font-size: 0.95em; - - > .avatar { - - @media (min-width: 350px) { - margin: 0 10px 0 0; - width: 44px; - height: 44px; - } - - @media (min-width: 500px) { - margin: 0 12px 0 0; - width: 48px; - height: 48px; - } - } - - > .avatar { - flex-shrink: 0; - display: block; - margin: 0 10px 0 0; - width: 40px; - height: 40px; - border-radius: 8px; - } - - > .main { - flex: 1; - min-width: 0; - - > .header { - margin-bottom: 2px; - } - - > .body { - - > .cw { - cursor: default; - display: block; - margin: 0; - padding: 0; - overflow-wrap: break-word; - - > .text { - margin-right: 8px; - } - } - - > .content { - > .text { - cursor: default; - margin: 0; - padding: 0; - } - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/note.sub.vue b/packages/client/src/ui/chat/note.sub.vue deleted file mode 100644 index b61b7521a8..0000000000 --- a/packages/client/src/ui/chat/note.sub.vue +++ /dev/null @@ -1,137 +0,0 @@ -<template> -<div class="wrpstxzv" :class="{ children }"> - <div class="main"> - <MkAvatar class="avatar" :user="note.user"/> - <div class="body"> - <XNoteHeader class="header" :note="note" :mini="true"/> - <div class="body"> - <p v-if="note.cw != null" class="cw"> - <Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis"/> - <XCwButton v-model="showContent" :note="note"/> - </p> - <div v-show="note.cw == null || showContent" class="content"> - <XSubNote-content class="text" :note="note"/> - </div> - </div> - </div> - </div> - <XSub v-for="reply in replies" :key="reply.id" :note="reply" class="reply" :detail="true" :children="true"/> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import XNoteHeader from './note-header.vue'; -import XSubNoteContent from './sub-note-content.vue'; -import XCwButton from '@/components/cw-button.vue'; -import * as os from '@/os'; - -export default defineComponent({ - name: 'XSub', - - components: { - XNoteHeader, - XSubNoteContent, - XCwButton, - }, - - props: { - note: { - type: Object, - required: true - }, - detail: { - type: Boolean, - required: false, - default: false - }, - children: { - type: Boolean, - required: false, - default: false - }, - // TODO - truncate: { - type: Boolean, - default: true - } - }, - - data() { - return { - showContent: false, - replies: [], - }; - }, - - created() { - if (this.detail) { - os.api('notes/children', { - noteId: this.note.id, - limit: 5 - }).then(replies => { - this.replies = replies; - }); - } - }, -}); -</script> - -<style lang="scss" scoped> -.wrpstxzv { - padding: 16px 16px; - font-size: 0.8em; - - &.children { - padding: 10px 0 0 16px; - font-size: 1em; - } - - > .main { - display: flex; - - > .avatar { - flex-shrink: 0; - display: block; - margin: 0 8px 0 0; - width: 36px; - height: 36px; - } - - > .body { - flex: 1; - min-width: 0; - - > .header { - margin-bottom: 2px; - } - - > .body { - > .cw { - cursor: default; - display: block; - margin: 0; - padding: 0; - overflow-wrap: break-word; - - > .text { - margin-right: 8px; - } - } - - > .content { - > .text { - margin: 0; - padding: 0; - } - } - } - } - } - - > .reply { - border-left: solid 0.5px var(--divider); - margin-top: 10px; - } -} -</style> diff --git a/packages/client/src/ui/chat/note.vue b/packages/client/src/ui/chat/note.vue deleted file mode 100644 index fa5faa4ec3..0000000000 --- a/packages/client/src/ui/chat/note.vue +++ /dev/null @@ -1,1143 +0,0 @@ -<template> -<div - v-if="!muted" - v-show="!isDeleted" - v-hotkey="keymap" - class="vfzoeqcg" - :tabindex="!isDeleted ? '-1' : null" - :class="{ renote: isRenote, highlighted: appearNote._prId_ || appearNote._featuredId_, operating }" -> - <XSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> - <div v-if="pinned" class="info"><i class="fas fa-thumbtack"></i> {{ $ts.pinnedNote }}</div> - <div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ $ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ $ts.hideThisNote }} <i class="fas fa-times"></i></button></div> - <div v-if="appearNote._featuredId_" class="info"><i class="fas fa-bolt"></i> {{ $ts.featured }}</div> - <div v-if="isRenote" class="renote"> - <MkAvatar class="avatar" :user="note.user"/> - <i class="fas fa-retweet"></i> - <I18n :src="$ts.renotedBy" tag="span"> - <template #user> - <MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)"> - <MkUserName :user="note.user"/> - </MkA> - </template> - </I18n> - <div class="info"> - <button ref="renoteTime" class="_button time" @click="showRenoteMenu()"> - <i v-if="isMyRenote" class="fas fa-ellipsis-h dropdownIcon"></i> - <MkTime :time="note.createdAt"/> - </button> - <span v-if="note.visibility !== 'public'" class="visibility"> - <i v-if="note.visibility === 'home'" class="fas fa-home"></i> - <i v-else-if="note.visibility === 'followers'" class="fas fa-unlock"></i> - <i v-else-if="note.visibility === 'specified'" class="fas fa-envelope"></i> - </span> - <span v-if="note.localOnly" class="localOnly"><i class="fas fa-biohazard"></i></span> - </div> - </div> - <article class="article" @contextmenu.stop="onContextmenu"> - <MkAvatar class="avatar" :user="appearNote.user"/> - <div class="main"> - <XNoteHeader class="header" :note="appearNote" :mini="true"/> - <MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/> - <div class="body"> - <p v-if="appearNote.cw != null" class="cw"> - <Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> - <XCwButton v-model="showContent" :note="appearNote"/> - </p> - <div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed }"> - <div class="text"> - <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $ts.private }})</span> - <MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA> - <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> - <a v-if="appearNote.renote != null" class="rp">RN:</a> - </div> - <div v-if="appearNote.files.length > 0" class="files"> - <XMediaList :media-list="appearNote.files"/> - </div> - <XPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/> - <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/> - <div v-if="appearNote.renote" class="renote"><XNoteSimple :note="appearNote.renote"/></div> - <button v-if="collapsed" class="fade _button" @click="collapsed = false"> - <span>{{ $ts.showMore }}</span> - </button> - </div> - <MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="fas fa-satellite-dish"></i> {{ appearNote.channel.name }}</MkA> - </div> - <XReactionsViewer ref="reactionsViewer" :note="appearNote"/> - <footer class="footer _panel"> - <button v-tooltip="$ts.reply" class="button _button" @click="reply()"> - <template v-if="appearNote.reply"><i class="fas fa-reply-all"></i></template> - <template v-else><i class="fas fa-reply"></i></template> - <p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p> - </button> - <button v-if="canRenote" ref="renoteButton" v-tooltip="$ts.renote" class="button _button" @click="renote()"> - <i class="fas fa-retweet"></i><p v-if="appearNote.renoteCount > 0" class="count">{{ appearNote.renoteCount }}</p> - </button> - <button v-else class="button _button"> - <i class="fas fa-ban"></i> - </button> - <button v-if="appearNote.myReaction == null" ref="reactButton" v-tooltip="$ts.reaction" class="button _button" @click="react()"> - <i class="fas fa-plus"></i> - </button> - <button v-if="appearNote.myReaction != null" ref="reactButton" v-tooltip="$ts.reaction" class="button _button reacted" @click="undoReact(appearNote)"> - <i class="fas fa-minus"></i> - </button> - <button ref="menuButton" class="button _button" @click="menu()"> - <i class="fas fa-ellipsis-h"></i> - </button> - </footer> - </div> - </article> -</div> -<div v-else class="muted" @click="muted = false"> - <I18n :src="$ts.userSaysSomething" tag="small"> - <template #name> - <MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)"> - <MkUserName :user="appearNote.user"/> - </MkA> - </template> - </I18n> -</div> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent, markRaw } from 'vue'; -import * as mfm from 'mfm-js'; -import { sum } from '@/scripts/array'; -import XSub from './note.sub.vue'; -import XNoteHeader from './note-header.vue'; -import XNoteSimple from './note-preview.vue'; -import XReactionsViewer from '@/components/reactions-viewer.vue'; -import XMediaList from '@/components/media-list.vue'; -import XCwButton from '@/components/cw-button.vue'; -import XPoll from '@/components/poll.vue'; -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'; -import { userPage } from '@/filters/user'; -import * as os from '@/os'; -import { stream } from '@/stream'; -import { noteActions, noteViewInterruptors } from '@/store'; -import { reactionPicker } from '@/scripts/reaction-picker'; -import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; - -export default defineComponent({ - components: { - XSub, - XNoteHeader, - XNoteSimple, - XReactionsViewer, - XMediaList, - XCwButton, - XPoll, - MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')), - MkInstanceTicker: defineAsyncComponent(() => import('@/components/instance-ticker.vue')), - }, - - inject: { - inChannel: { - default: null - }, - }, - - props: { - note: { - type: Object, - required: true - }, - pinned: { - type: Boolean, - required: false, - default: false - }, - }, - - emits: ['update:note'], - - data() { - return { - connection: null, - replies: [], - showContent: false, - collapsed: false, - isDeleted: false, - muted: false, - operating: false, - }; - }, - - computed: { - rs() { - return this.$store.state.reactions; - }, - keymap(): any { - return { - 'r': () => this.reply(true), - 'e|a|plus': () => this.react(true), - 'q': () => this.renote(true), - 'f|b': this.favorite, - 'delete|ctrl+d': this.del, - 'ctrl+q': this.renoteDirectly, - 'up|k|shift+tab': this.focusBefore, - 'down|j|tab': this.focusAfter, - 'esc': this.blur, - 'm|o': () => this.menu(true), - 's': this.toggleShowContent, - '1': () => this.reactDirectly(this.rs[0]), - '2': () => this.reactDirectly(this.rs[1]), - '3': () => this.reactDirectly(this.rs[2]), - '4': () => this.reactDirectly(this.rs[3]), - '5': () => this.reactDirectly(this.rs[4]), - '6': () => this.reactDirectly(this.rs[5]), - '7': () => this.reactDirectly(this.rs[6]), - '8': () => this.reactDirectly(this.rs[7]), - '9': () => this.reactDirectly(this.rs[8]), - '0': () => this.reactDirectly(this.rs[9]), - }; - }, - - isRenote(): boolean { - return (this.note.renote && - this.note.text == null && - this.note.fileIds.length == 0 && - this.note.poll == null); - }, - - appearNote(): any { - return this.isRenote ? this.note.renote : this.note; - }, - - isMyNote(): boolean { - return this.$i && (this.$i.id === this.appearNote.userId); - }, - - isMyRenote(): boolean { - return this.$i && (this.$i.id === this.note.userId); - }, - - canRenote(): boolean { - return ['public', 'home'].includes(this.appearNote.visibility) || this.isMyNote; - }, - - reactionsCount(): number { - return this.appearNote.reactions - ? sum(Object.values(this.appearNote.reactions)) - : 0; - }, - - urls(): string[] { - if (this.appearNote.text) { - return extractUrlFromMfm(mfm.parse(this.appearNote.text)); - } else { - return null; - } - }, - - showTicker() { - if (this.$store.state.instanceTicker === 'always') return true; - if (this.$store.state.instanceTicker === 'remote' && this.appearNote.user.instance) return true; - return false; - } - }, - - async created() { - if (this.$i) { - this.connection = stream; - } - - this.collapsed = this.appearNote.cw == null && this.appearNote.text && ( - (this.appearNote.text.split('\n').length > 9) || - (this.appearNote.text.length > 500) - ); - this.muted = await checkWordMute(this.appearNote, this.$i, this.$store.state.mutedWords); - - // plugin - if (noteViewInterruptors.length > 0) { - let result = this.note; - for (const interruptor of noteViewInterruptors) { - result = await interruptor.handler(JSON.parse(JSON.stringify(result))); - } - this.$emit('update:note', Object.freeze(result)); - } - }, - - mounted() { - this.capture(true); - - if (this.$i) { - this.connection.on('_connected_', this.onStreamConnected); - } - }, - - beforeUnmount() { - this.decapture(true); - - if (this.$i) { - this.connection.off('_connected_', this.onStreamConnected); - } - }, - - methods: { - updateAppearNote(v) { - this.$emit('update:note', Object.freeze(this.isRenote ? { - ...this.note, - renote: { - ...this.note.renote, - ...v - } - } : { - ...this.note, - ...v - })); - }, - - readPromo() { - os.api('promo/read', { - noteId: this.appearNote.id - }); - this.isDeleted = true; - }, - - capture(withHandler = false) { - if (this.$i) { - // TODO: このノートがストリーミング経由で流れてきた場合のみ sr する - this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id }); - if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); - } - }, - - decapture(withHandler = false) { - if (this.$i) { - this.connection.send('un', { - id: this.appearNote.id - }); - if (withHandler) this.connection.off('noteUpdated', this.onStreamNoteUpdated); - } - }, - - onStreamConnected() { - this.capture(); - }, - - onStreamNoteUpdated(data) { - const { type, id, body } = data; - - if (id !== this.appearNote.id) return; - - switch (type) { - case 'reacted': { - const reaction = body.reaction; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - if (body.emoji) { - const emojis = this.appearNote.emojis || []; - if (!emojis.includes(body.emoji)) { - n.emojis = [...emojis, body.emoji]; - } - } - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Increment the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: currentCount + 1 - }; - - if (body.userId === this.$i.id) { - n.myReaction = reaction; - } - - this.updateAppearNote(n); - break; - } - - case 'unreacted': { - const reaction = body.reaction; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Decrement the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: Math.max(0, currentCount - 1) - }; - - if (body.userId === this.$i.id) { - n.myReaction = null; - } - - this.updateAppearNote(n); - break; - } - - case 'pollVoted': { - const choice = body.choice; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - const choices = [...this.appearNote.poll.choices]; - choices[choice] = { - ...choices[choice], - votes: choices[choice].votes + 1, - ...(body.userId === this.$i.id ? { - isVoted: true - } : {}) - }; - - n.poll = { - ...this.appearNote.poll, - choices: choices - }; - - this.updateAppearNote(n); - break; - } - - case 'deleted': { - this.isDeleted = true; - break; - } - } - }, - - reply(viaKeyboard = false) { - pleaseLogin(); - this.operating = true; - os.post({ - reply: this.appearNote, - animation: !viaKeyboard, - }, () => { - this.operating = false; - this.focus(); - }); - }, - - renote(viaKeyboard = false) { - pleaseLogin(); - this.operating = true; - this.blur(); - os.popupMenu([{ - text: this.$ts.renote, - icon: 'fas fa-retweet', - action: () => { - os.api('notes/create', { - renoteId: this.appearNote.id - }); - } - }, { - text: this.$ts.quote, - icon: 'fas fa-quote-right', - action: () => { - os.post({ - renote: this.appearNote, - }); - } - }], this.$refs.renoteButton, { - viaKeyboard - }).then(() => { - this.operating = false; - }); - }, - - renoteDirectly() { - os.apiWithDialog('notes/create', { - renoteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.renoted, - }); - }, (e: Error) => { - if (e.id === 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4') { - os.alert({ - type: 'error', - text: this.$ts.cantRenote, - }); - } else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') { - os.alert({ - type: 'error', - text: this.$ts.cantReRenote, - }); - } - }); - }, - - async react(viaKeyboard = false) { - pleaseLogin(); - this.operating = true; - this.blur(); - reactionPicker.show(this.$refs.reactButton, reaction => { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, () => { - this.operating = false; - this.focus(); - }); - }, - - reactDirectly(reaction) { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, - - undoReact(note) { - const oldReaction = note.myReaction; - if (!oldReaction) return; - os.api('notes/reactions/delete', { - noteId: note.id - }); - }, - - favorite() { - pleaseLogin(); - os.apiWithDialog('notes/favorites/create', { - noteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.favorited, - }); - }, (e: Error) => { - if (e.id === 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6') { - os.alert({ - type: 'error', - text: this.$ts.alreadyFavorited, - }); - } else if (e.id === '6dd26674-e060-4816-909a-45ba3f4da458') { - os.alert({ - type: 'error', - text: this.$ts.cantFavorite, - }); - } - }); - }, - - del() { - os.confirm({ - type: 'warning', - text: this.$ts.noteDeleteConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - }); - }, - - delEdit() { - os.confirm({ - type: 'warning', - text: this.$ts.deleteAndEditConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - - os.post({ initialNote: this.appearNote, renote: this.appearNote.renote, reply: this.appearNote.reply, channel: this.appearNote.channel }); - }); - }, - - toggleFavorite(favorite: boolean) { - os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { - noteId: this.appearNote.id - }); - }, - - toggleWatch(watch: boolean) { - os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { - noteId: this.appearNote.id - }); - }, - - getMenu() { - let menu; - if (this.$i) { - const statePromise = os.api('notes/state', { - noteId: this.appearNote.id - }); - - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined, - { - icon: 'fas fa-share-alt', - text: this.$ts.share, - action: this.share - }, - null, - statePromise.then(state => state.isFavorited ? { - icon: 'fas fa-star', - text: this.$ts.unfavorite, - action: () => this.toggleFavorite(false) - } : { - icon: 'fas fa-star', - text: this.$ts.favorite, - action: () => this.toggleFavorite(true) - }), - { - icon: 'fas fa-paperclip', - text: this.$ts.clip, - action: () => this.clip() - }, - (this.appearNote.userId != this.$i.id) ? statePromise.then(state => state.isWatching ? { - icon: 'fas fa-eye-slash', - text: this.$ts.unwatch, - action: () => this.toggleWatch(false) - } : { - icon: 'fas fa-eye', - text: this.$ts.watch, - action: () => this.toggleWatch(true) - }) : undefined, - this.appearNote.userId == this.$i.id ? (this.$i.pinnedNoteIds || []).includes(this.appearNote.id) ? { - icon: 'fas fa-thumbtack', - text: this.$ts.unpin, - action: () => this.togglePin(false) - } : { - icon: 'fas fa-thumbtack', - text: this.$ts.pin, - action: () => this.togglePin(true) - } : undefined, - /* - ...(this.$i.isModerator || this.$i.isAdmin ? [ - null, - { - icon: 'fas fa-bullhorn', - text: this.$ts.promote, - action: this.promote - }] - : [] - ),*/ - ...(this.appearNote.userId != this.$i.id ? [ - null, - { - icon: 'fas fa-exclamation-circle', - text: this.$ts.reportAbuse, - action: () => { - const u = `${url}/notes/${this.appearNote.id}`; - os.popup(import('@/components/abuse-report-window.vue'), { - user: this.appearNote.user, - initialComment: `Note: ${u}\n-----\n` - }, {}, 'closed'); - } - }] - : [] - ), - ...(this.appearNote.userId == this.$i.id || this.$i.isModerator || this.$i.isAdmin ? [ - null, - this.appearNote.userId == this.$i.id ? { - icon: 'fas fa-edit', - text: this.$ts.deleteAndEdit, - action: this.delEdit - } : undefined, - { - icon: 'fas fa-trash-alt', - text: this.$ts.delete, - danger: true, - action: this.del - }] - : [] - )] - .filter(x => x !== undefined); - } else { - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined] - .filter(x => x !== undefined); - } - - if (noteActions.length > 0) { - menu = menu.concat([null, ...noteActions.map(action => ({ - icon: 'fas fa-plug', - text: action.title, - action: () => { - action.handler(this.appearNote); - } - }))]); - } - - return menu; - }, - - onContextmenu(e) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(e.target)) return; - if (window.getSelection().toString() !== '') return; - - if (this.$store.state.useReactionPickerForContextMenu) { - e.preventDefault(); - this.react(); - } else { - os.contextMenu(this.getMenu(), e).then(this.focus); - } - }, - - menu(viaKeyboard = false) { - this.operating = true; - os.popupMenu(this.getMenu(), this.$refs.menuButton, { - viaKeyboard - }).then(() => { - this.operating = false; - this.focus(); - }); - }, - - showRenoteMenu(viaKeyboard = false) { - if (!this.isMyRenote) return; - os.popupMenu([{ - text: this.$ts.unrenote, - icon: 'fas fa-trash-alt', - danger: true, - action: () => { - os.api('notes/delete', { - noteId: this.note.id - }); - this.isDeleted = true; - } - }], this.$refs.renoteTime, { - viaKeyboard: viaKeyboard - }); - }, - - toggleShowContent() { - this.showContent = !this.showContent; - }, - - copyContent() { - copyToClipboard(this.appearNote.text); - os.success(); - }, - - copyLink() { - copyToClipboard(`${url}/notes/${this.appearNote.id}`); - os.success(); - }, - - togglePin(pin: boolean) { - os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { - noteId: this.appearNote.id - }, undefined, null, e => { - if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { - os.alert({ - type: 'error', - text: this.$ts.pinLimitExceeded - }); - } - }); - }, - - async clip() { - const clips = await os.api('clips/list'); - os.popupMenu([{ - icon: 'fas fa-plus', - text: this.$ts.createNew, - action: async () => { - const { canceled, result } = await os.form(this.$ts.createNewClip, { - name: { - type: 'string', - label: this.$ts.name - }, - description: { - type: 'string', - required: false, - multiline: true, - label: this.$ts.description - }, - isPublic: { - type: 'boolean', - label: this.$ts.public, - default: false - } - }); - if (canceled) return; - - const clip = await os.apiWithDialog('clips/create', result); - - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }, null, ...clips.map(clip => ({ - text: clip.name, - action: () => { - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }))], this.$refs.menuButton, { - }).then(this.focus); - }, - - async promote() { - const { canceled, result: days } = await os.inputNumber({ - title: this.$ts.numberOfDays, - }); - - if (canceled) return; - - os.apiWithDialog('admin/promo/create', { - noteId: this.appearNote.id, - expiresAt: Date.now() + (86400000 * days) - }); - }, - - share() { - navigator.share({ - title: this.$t('noteOf', { user: this.appearNote.user.name }), - text: this.appearNote.text, - url: `${url}/notes/${this.appearNote.id}` - }); - }, - - focus() { - this.$el.focus(); - }, - - blur() { - this.$el.blur(); - }, - - focusBefore() { - focusPrev(this.$el); - }, - - focusAfter() { - focusNext(this.$el); - }, - - userPage - } -}); -</script> - -<style lang="scss" scoped> -.vfzoeqcg { - position: relative; - contain: content; - - // これらの指定はパフォーマンス向上には有効だが、ノートの高さは一定でないため、 - // 下の方までスクロールすると上のノートの高さがここで決め打ちされたものに変化し、表示しているノートの位置が変わってしまう - // ノートがマウントされたときに自身の高さを取得し contain-intrinsic-size を設定しなおせばほぼ解決できそうだが、 - // 今度はその処理自体がパフォーマンス低下の原因にならないか懸念される。また、被リアクションでも高さは変化するため、やはり多少のズレは生じる - // 一度レンダリングされた要素はブラウザがよしなにサイズを覚えておいてくれるような実装になるまで待った方が良さそう(なるのか?) - //content-visibility: auto; - //contain-intrinsic-size: 0 128px; - - &:focus-visible { - outline: none; - } - - &:hover { - background: rgba(0, 0, 0, 0.05); - } - - &:hover, &.operating { - > .article > .main > .footer { - display: block; - } - } - - &.renote { - background: rgba(128, 255, 0, 0.05); - } - - &.highlighted { - background: rgba(255, 128, 0, 0.05); - } - - > .info { - display: flex; - align-items: center; - padding: 12px 16px 4px 16px; - line-height: 24px; - font-size: 85%; - white-space: pre; - color: #d28a3f; - - > i { - margin-right: 4px; - } - - > .hide { - margin-left: 16px; - color: inherit; - opacity: 0.7; - } - } - - > .info + .article { - padding-top: 8px; - } - - > .reply-to { - opacity: 0.7; - padding-bottom: 0; - } - - > .renote { - display: flex; - align-items: center; - padding: 12px 16px 4px 16px; - line-height: 28px; - white-space: pre; - color: var(--renote); - font-size: 0.9em; - - > .avatar { - flex-shrink: 0; - display: inline-block; - width: 28px; - height: 28px; - margin: 0 8px 0 0; - border-radius: 6px; - } - - > i { - margin-right: 4px; - } - - > span { - overflow: hidden; - flex-shrink: 1; - text-overflow: ellipsis; - white-space: nowrap; - - > .name { - font-weight: bold; - } - } - - > .info { - margin-left: 8px; - font-size: 0.9em; - opacity: 0.7; - - > .time { - flex-shrink: 0; - color: inherit; - - > .dropdownIcon { - margin-right: 4px; - } - } - - > .visibility { - margin-left: 8px; - } - - > .localOnly { - margin-left: 8px; - } - } - } - - > .renote + .article { - padding-top: 8px; - } - - > .article { - display: flex; - padding: 12px 16px; - - > .avatar { - flex-shrink: 0; - display: block; - position: sticky; - top: 0; - margin: 0 14px 0 0; - width: 46px; - height: 46px; - } - - > .main { - flex: 1; - min-width: 0; - - > .body { - > .cw { - cursor: default; - display: block; - margin: 0; - padding: 0; - overflow-wrap: break-word; - - > .text { - margin-right: 8px; - } - } - - > .content { - &.collapsed { - position: relative; - max-height: 9em; - overflow: hidden; - - > .fade { - display: block; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 64px; - background: linear-gradient(0deg, var(--panel), var(--X15)); - - > span { - display: inline-block; - background: var(--panel); - padding: 6px 10px; - font-size: 0.8em; - border-radius: 999px; - box-shadow: 0 2px 6px rgb(0 0 0 / 20%); - } - - &:hover { - > span { - background: var(--panelHighlight); - } - } - } - } - - > .text { - overflow-wrap: break-word; - - > .reply { - color: var(--accent); - margin-right: 0.5em; - } - - > .rp { - margin-left: 4px; - font-style: oblique; - color: var(--renote); - } - } - - > .files { - max-width: 500px; - } - - > .url-preview { - margin-top: 8px; - max-width: 500px; - } - - > .poll { - font-size: 80%; - max-width: 500px; - } - - > .renote { - padding: 8px 0; - - > * { - padding: 16px; - border: dashed 1px var(--renote); - border-radius: 8px; - } - } - } - - > .channel { - opacity: 0.7; - font-size: 80%; - } - } - - > .footer { - display: none; - position: absolute; - top: 8px; - right: 8px; - padding: 0 6px; - opacity: 0.7; - - &:hover { - opacity: 1; - } - - > .button { - margin: 0; - padding: 8px; - opacity: 0.7; - - &:hover { - color: var(--accent); - } - - > .count { - display: inline; - margin: 0 0 0 8px; - opacity: 0.7; - } - - &.reacted { - color: var(--accent); - } - } - } - } - } - - > .reply { - border-top: solid 0.5px var(--divider); - } -} - -.muted { - padding: 8px 16px; - opacity: 0.7; - - &:hover { - background: rgba(0, 0, 0, 0.05); - } -} -</style> diff --git a/packages/client/src/ui/chat/notes.vue b/packages/client/src/ui/chat/notes.vue deleted file mode 100644 index 51d4afcf54..0000000000 --- a/packages/client/src/ui/chat/notes.vue +++ /dev/null @@ -1,94 +0,0 @@ -<template> -<div class=""> - <div v-if="empty" class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ $ts.noNotes }}</div> - </div> - - <MkLoading v-if="fetching"/> - - <MkError v-if="error" @retry="init()"/> - - <div v-show="more && reversed" style="margin-bottom: var(--margin);"> - <MkButton style="margin: 0 auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> - </div> - - <XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :ad="true"> - <XNote :key="note._featuredId_ || note._prId_ || note.id" :note="note" @update:note="updated(note, $event)"/> - </XList> - - <div v-show="more && !reversed" style="margin-top: var(--margin);"> - <MkButton v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" style="margin: 0 auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import paging from '@/scripts/paging'; -import XNote from './note.vue'; -import XList from './date-separated-list.vue'; -import MkButton from '@/components/ui/button.vue'; - -export default defineComponent({ - components: { - XNote, XList, MkButton, - }, - - mixins: [ - paging({ - before: (self) => { - self.$emit('before'); - }, - - after: (self, e) => { - self.$emit('after', e); - } - }), - ], - - props: { - pagination: { - required: true - }, - - prop: { - type: String, - required: false - } - }, - - emits: ['before', 'after'], - - computed: { - notes(): any[] { - return this.prop ? this.items.map(item => item[this.prop]) : this.items; - }, - - reversed(): boolean { - return this.pagination.reversed; - } - }, - - methods: { - updated(oldValue, newValue) { - const i = this.notes.findIndex(n => n === oldValue); - if (this.prop) { - this.items[i][this.prop] = newValue; - } else { - this.items[i] = newValue; - } - }, - - focus() { - this.$refs.notes.focus(); - } - } -}); -</script> diff --git a/packages/client/src/ui/chat/pages/channel.vue b/packages/client/src/ui/chat/pages/channel.vue deleted file mode 100644 index 2755cc92b7..0000000000 --- a/packages/client/src/ui/chat/pages/channel.vue +++ /dev/null @@ -1,259 +0,0 @@ -<template> -<div v-if="channel" class="hhizbblb"> - <div v-if="date" class="info"> - <MkInfo>{{ $ts.showingPastTimeline }} <button class="_textButton clear" @click="timetravel()">{{ $ts.clear }}</button></MkInfo> - </div> - <div ref="body" class="tl"> - <div v-if="queue > 0" class="new" :style="{ width: width + 'px', bottom: bottom + 'px' }"><button class="_buttonPrimary" @click="goTop()">{{ $ts.newNoteRecived }}</button></div> - <XNotes ref="tl" v-follow="true" class="tl" :pagination="pagination" @queue="queueUpdated"/> - </div> - <div class="bottom"> - <div v-if="typers.length > 0" class="typers"> - <I18n :src="$ts.typingUsers" text-tag="span" class="users"> - <template #users> - <b v-for="user in typers" :key="user.id" class="user">{{ user.username }}</b> - </template> - </I18n> - <MkEllipsis/> - </div> - <XPostForm :channel="channel"/> - </div> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent, markRaw } from 'vue'; -import * as Misskey from 'misskey-js'; -import XNotes from '../notes.vue'; -import * as os from '@/os'; -import { stream } from '@/stream'; -import * as sound from '@/scripts/sound'; -import { scrollToBottom, getScrollPosition, getScrollContainer } from '@/scripts/scroll'; -import follow from '@/directives/follow-append'; -import XPostForm from '../post-form.vue'; -import MkInfo from '@/components/ui/info.vue'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - XNotes, - XPostForm, - MkInfo, - }, - - directives: { - follow - }, - - provide() { - return { - inChannel: true - }; - }, - - props: { - channelId: { - type: String, - required: true - }, - }, - - data() { - return { - channel: null as Misskey.entities.Channel | null, - connection: null, - pagination: null, - baseQuery: { - includeMyRenotes: this.$store.state.showMyRenotes, - includeRenotedMyNotes: this.$store.state.showRenotedMyNotes, - includeLocalRenotes: this.$store.state.showLocalRenotes - }, - queue: 0, - width: 0, - top: 0, - bottom: 0, - typers: [], - date: null, - [symbols.PAGE_INFO]: computed(() => ({ - title: this.channel ? this.channel.name : '-', - subtitle: this.channel ? this.channel.description : '-', - icon: 'fas fa-satellite-dish', - actions: [{ - icon: this.channel?.isFollowing ? 'fas fa-star' : 'far fa-star', - text: this.channel?.isFollowing ? this.$ts.unfollow : this.$ts.follow, - highlighted: this.channel?.isFollowing, - handler: this.toggleChannelFollow - }, { - icon: 'fas fa-search', - text: this.$ts.inChannelSearch, - handler: this.inChannelSearch - }, { - icon: 'fas fa-calendar-alt', - text: this.$ts.jumpToSpecifiedDate, - handler: this.timetravel - }] - })), - }; - }, - - async created() { - this.channel = await os.api('channels/show', { channelId: this.channelId }); - - const prepend = note => { - (this.$refs.tl as any).prepend(note); - - this.$emit('note'); - - sound.play(note.userId === this.$i.id ? 'noteMy' : 'note'); - }; - - this.connection = markRaw(stream.useChannel('channel', { - channelId: this.channelId - })); - this.connection.on('note', prepend); - this.connection.on('typers', typers => { - this.typers = this.$i ? typers.filter(u => u.id !== this.$i.id) : typers; - }); - - this.pagination = { - endpoint: 'channels/timeline', - reversed: true, - limit: 10, - params: init => ({ - channelId: this.channelId, - untilDate: this.date?.getTime(), - ...this.baseQuery - }) - }; - }, - - mounted() { - - }, - - beforeUnmount() { - this.connection.dispose(); - }, - - methods: { - focus() { - this.$refs.body.focus(); - }, - - goTop() { - const container = getScrollContainer(this.$refs.body); - container.scrollTop = 0; - }, - - queueUpdated(q) { - if (this.$refs.body.offsetWidth !== 0) { - const rect = this.$refs.body.getBoundingClientRect(); - this.width = this.$refs.body.offsetWidth; - this.top = rect.top; - this.bottom = this.$refs.body.offsetHeight; - } - this.queue = q; - }, - - async inChannelSearch() { - const { canceled, result: query } = await os.inputText({ - title: this.$ts.inChannelSearch, - }); - if (canceled || query == null || query === '') return; - router.push(`/search?q=${encodeURIComponent(query)}&channel=${this.channelId}`); - }, - - async toggleChannelFollow() { - if (this.channel.isFollowing) { - await os.apiWithDialog('channels/unfollow', { - channelId: this.channel.id - }); - this.channel.isFollowing = false; - } else { - await os.apiWithDialog('channels/follow', { - channelId: this.channel.id - }); - this.channel.isFollowing = true; - } - }, - - openChannelMenu(ev) { - os.popupMenu([{ - text: this.$ts.copyUrl, - icon: 'fas fa-link', - action: () => { - copyToClipboard(`${url}/channels/${this.currentChannel.id}`); - } - }], ev.currentTarget || ev.target); - }, - - timetravel(date?: Date) { - this.date = date; - this.$refs.tl.reload(); - } - } -}); -</script> - -<style lang="scss" scoped> -.hhizbblb { - display: flex; - flex-direction: column; - flex: 1; - overflow: auto; - - > .info { - padding: 16px 16px 0 16px; - } - - > .top { - padding: 16px 16px 0 16px; - } - - > .bottom { - padding: 0 16px 16px 16px; - position: relative; - - > .typers { - position: absolute; - bottom: 100%; - padding: 0 8px 0 8px; - font-size: 0.9em; - background: var(--panel); - border-radius: 0 8px 0 0; - color: var(--fgTransparentWeak); - - > .users { - > .user + .user:before { - content: ", "; - font-weight: normal; - } - - > .user:last-of-type:after { - content: " "; - } - } - } - } - - > .tl { - position: relative; - padding: 16px 0; - flex: 1; - min-width: 0; - overflow: auto; - - > .new { - position: fixed; - z-index: 1000; - - > button { - display: block; - margin: 16px auto; - padding: 8px 16px; - border-radius: 32px; - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/pages/timeline.vue b/packages/client/src/ui/chat/pages/timeline.vue deleted file mode 100644 index f67d333398..0000000000 --- a/packages/client/src/ui/chat/pages/timeline.vue +++ /dev/null @@ -1,222 +0,0 @@ -<template> -<div class="dbiokgaf"> - <div v-if="date" class="info"> - <MkInfo>{{ $ts.showingPastTimeline }} <button class="_textButton clear" @click="timetravel()">{{ $ts.clear }}</button></MkInfo> - </div> - <div class="top"> - <XPostForm/> - </div> - <div ref="body" class="tl"> - <div v-if="queue > 0" class="new" :style="{ width: width + 'px', top: top + 'px' }"><button class="_buttonPrimary" @click="goTop()">{{ $ts.newNoteRecived }}</button></div> - <XNotes ref="tl" class="tl" :pagination="pagination" @queue="queueUpdated"/> - </div> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent, markRaw } from 'vue'; -import XNotes from '../notes.vue'; -import * as os from '@/os'; -import { stream } from '@/stream'; -import * as sound from '@/scripts/sound'; -import { scrollToBottom, getScrollPosition, getScrollContainer } from '@/scripts/scroll'; -import follow from '@/directives/follow-append'; -import XPostForm from '../post-form.vue'; -import MkInfo from '@/components/ui/info.vue'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - XNotes, - XPostForm, - MkInfo, - }, - - directives: { - follow - }, - - props: { - src: { - type: String, - required: true - }, - }, - - data() { - return { - connection: null, - connection2: null, - pagination: null, - baseQuery: { - includeMyRenotes: this.$store.state.showMyRenotes, - includeRenotedMyNotes: this.$store.state.showRenotedMyNotes, - includeLocalRenotes: this.$store.state.showLocalRenotes - }, - query: {}, - queue: 0, - width: 0, - top: 0, - bottom: 0, - typers: [], - date: null, - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.timeline, - icon: 'fas fa-home', - actions: [{ - icon: 'fas fa-calendar-alt', - text: this.$ts.jumpToSpecifiedDate, - handler: this.timetravel - }] - })), - }; - }, - - created() { - const prepend = note => { - (this.$refs.tl as any).prepend(note); - - this.$emit('note'); - - sound.play(note.userId === this.$i.id ? 'noteMy' : 'note'); - }; - - const onChangeFollowing = () => { - if (!this.$refs.tl.backed) { - this.$refs.tl.reload(); - } - }; - - let endpoint; - - if (this.src == 'home') { - endpoint = 'notes/timeline'; - this.connection = markRaw(stream.useChannel('homeTimeline')); - this.connection.on('note', prepend); - - this.connection2 = markRaw(stream.useChannel('main')); - this.connection2.on('follow', onChangeFollowing); - this.connection2.on('unfollow', onChangeFollowing); - } else if (this.src == 'local') { - endpoint = 'notes/local-timeline'; - this.connection = markRaw(stream.useChannel('localTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'social') { - endpoint = 'notes/hybrid-timeline'; - this.connection = markRaw(stream.useChannel('hybridTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'global') { - endpoint = 'notes/global-timeline'; - this.connection = markRaw(stream.useChannel('globalTimeline')); - this.connection.on('note', prepend); - } - - this.pagination = { - endpoint: endpoint, - limit: 10, - params: init => ({ - untilDate: this.date?.getTime(), - ...this.baseQuery, ...this.query - }) - }; - }, - - mounted() { - - }, - - beforeUnmount() { - this.connection.dispose(); - if (this.connection2) this.connection2.dispose(); - }, - - methods: { - focus() { - this.$refs.body.focus(); - }, - - goTop() { - const container = getScrollContainer(this.$refs.body); - container.scrollTop = 0; - }, - - queueUpdated(q) { - if (this.$refs.body.offsetWidth !== 0) { - const rect = this.$refs.body.getBoundingClientRect(); - this.width = this.$refs.body.offsetWidth; - this.top = rect.top; - this.bottom = this.$refs.body.offsetHeight; - } - this.queue = q; - }, - - timetravel(date?: Date) { - this.date = date; - this.$refs.tl.reload(); - } - } -}); -</script> - -<style lang="scss" scoped> -.dbiokgaf { - display: flex; - flex-direction: column; - flex: 1; - overflow: auto; - - > .info { - padding: 16px 16px 0 16px; - } - - > .top { - padding: 16px 16px 0 16px; - } - - > .bottom { - padding: 0 16px 16px 16px; - position: relative; - - > .typers { - position: absolute; - bottom: 100%; - padding: 0 8px 0 8px; - font-size: 0.9em; - background: var(--panel); - border-radius: 0 8px 0 0; - color: var(--fgTransparentWeak); - - > .users { - > .user + .user:before { - content: ", "; - font-weight: normal; - } - - > .user:last-of-type:after { - content: " "; - } - } - } - } - - > .tl { - position: relative; - padding: 16px 0; - flex: 1; - min-width: 0; - overflow: auto; - - > .new { - position: fixed; - z-index: 1000; - - > button { - display: block; - margin: 16px auto; - padding: 8px 16px; - border-radius: 32px; - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/post-form.vue b/packages/client/src/ui/chat/post-form.vue deleted file mode 100644 index 0f04096653..0000000000 --- a/packages/client/src/ui/chat/post-form.vue +++ /dev/null @@ -1,770 +0,0 @@ -<template> -<div class="pxiwixjf" - @dragover.stop="onDragover" - @dragenter="onDragenter" - @dragleave="onDragleave" - @drop.stop="onDrop" -> - <div class="form"> - <div v-if="quoteId" class="with-quote"><i class="fas fa-quote-left"></i> {{ $ts.quoteAttached }}<button @click="quoteId = null"><i class="fas fa-times"></i></button></div> - <div v-if="visibility === 'specified'" class="to-specified"> - <span style="margin-right: 8px;">{{ $ts.recipient }}</span> - <div class="visibleUsers"> - <span v-for="u in visibleUsers" :key="u.id"> - <MkAcct :user="u"/> - <button class="_button" @click="removeVisibleUser(u)"><i class="fas fa-times"></i></button> - </span> - <button class="_buttonPrimary" @click="addVisibleUser"><i class="fas fa-plus fa-fw"></i></button> - </div> - </div> - <input v-show="useCw" ref="cw" v-model="cw" class="cw" :placeholder="$ts.annotation" @keydown="onKeydown"> - <textarea ref="text" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> - <XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> - <XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/> - <footer> - <div class="left"> - <button v-tooltip="$ts.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button> - <button v-tooltip="$ts.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="fas fa-poll-h"></i></button> - <button v-tooltip="$ts.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="fas fa-eye-slash"></i></button> - <button v-tooltip="$ts.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button> - <button v-tooltip="$ts.emoji" class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button> - <button v-if="postFormActions.length > 0" v-tooltip="$ts.plugin" class="_button" @click="showActions"><i class="fas fa-plug"></i></button> - </div> - <div class="right"> - <span class="text-count" :class="{ over: textLength > max }">{{ max - textLength }}</span> - <span v-if="localOnly" class="local-only"><i class="fas fa-biohazard"></i></span> - <button ref="visibilityButton" v-tooltip="$ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> - <span v-if="visibility === 'public'"><i class="fas fa-globe"></i></span> - <span v-if="visibility === 'home'"><i class="fas fa-home"></i></span> - <span v-if="visibility === 'followers'"><i class="fas fa-unlock"></i></span> - <span v-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span> - </button> - <button class="submit _buttonPrimary" :disabled="!canPost" @click="post">{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button> - </div> - </footer> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; -import insertTextAtCursor from 'insert-text-at-cursor'; -import { length } from 'stringz'; -import { toASCII } from 'punycode/'; -import * as mfm from 'mfm-js'; -import { host, url } from '@/config'; -import { erase, unique } from '@/scripts/array'; -import { extractMentions } from '@/scripts/extract-mentions'; -import * as Acct from 'misskey-js/built/acct'; -import { formatTimeString } from '@/scripts/format-time-string'; -import { Autocomplete } from '@/scripts/autocomplete'; -import * as os from '@/os'; -import { stream } from '@/stream'; -import { selectFiles } from '@/scripts/select-file'; -import { notePostInterruptors, postFormActions } from '@/store'; -import { throttle } from 'throttle-debounce'; - -export default defineComponent({ - components: { - XPostFormAttaches: defineAsyncComponent(() => import('@/components/post-form-attaches.vue')), - XPollEditor: defineAsyncComponent(() => import('@/components/poll-editor.vue')) - }, - - props: { - reply: { - type: Object, - required: false - }, - renote: { - type: Object, - required: false - }, - channel: { - type: String, - required: false - }, - mention: { - type: Object, - required: false - }, - specified: { - type: Object, - required: false - }, - initialText: { - type: String, - required: false - }, - initialNote: { - type: Object, - required: false - }, - share: { - type: Boolean, - required: false, - default: false - }, - autofocus: { - type: Boolean, - required: false, - default: false - }, - }, - - emits: ['posted', 'cancel', 'esc'], - - data() { - return { - posting: false, - text: '', - files: [], - poll: null, - useCw: false, - cw: null, - localOnly: this.$store.state.rememberNoteVisibility ? this.$store.state.localOnly : this.$store.state.defaultNoteLocalOnly, - visibility: this.$store.state.rememberNoteVisibility ? this.$store.state.visibility : this.$store.state.defaultNoteVisibility, - visibleUsers: [], - autocomplete: null, - draghover: false, - quoteId: null, - recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'), - imeText: '', - typing: throttle(3000, () => { - if (this.channel) { - stream.send('typingOnChannel', { channel: this.channel }); - } - }), - postFormActions, - }; - }, - - computed: { - draftKey(): string { - let key = this.channel ? `channel:${this.channel}` : ''; - - if (this.renote) { - key += `renote:${this.renote.id}`; - } else if (this.reply) { - key += `reply:${this.reply.id}`; - } else { - key += 'note'; - } - - return key; - }, - - placeholder(): string { - if (this.renote) { - return this.$ts._postForm.quotePlaceholder; - } else if (this.reply) { - return this.$ts._postForm.replyPlaceholder; - } else if (this.channel) { - return this.$ts._postForm.channelPlaceholder; - } else { - const xs = [ - this.$ts._postForm._placeholders.a, - this.$ts._postForm._placeholders.b, - this.$ts._postForm._placeholders.c, - this.$ts._postForm._placeholders.d, - this.$ts._postForm._placeholders.e, - this.$ts._postForm._placeholders.f - ]; - return xs[Math.floor(Math.random() * xs.length)]; - } - }, - - submitText(): string { - return this.renote - ? this.$ts.quote - : this.reply - ? this.$ts.reply - : this.$ts.note; - }, - - textLength(): number { - return length((this.text + this.imeText).trim()); - }, - - canPost(): boolean { - return !this.posting && - (1 <= this.textLength || 1 <= this.files.length || !!this.poll || !!this.renote) && - (this.textLength <= this.max) && - (!this.poll || this.poll.choices.length >= 2); - }, - - max(): number { - return this.$instance ? this.$instance.maxNoteTextLength : 1000; - } - }, - - mounted() { - if (this.initialText) { - this.text = this.initialText; - } - - if (this.mention) { - this.text = this.mention.host ? `@${this.mention.username}@${toASCII(this.mention.host)}` : `@${this.mention.username}`; - this.text += ' '; - } - - if (this.reply && (this.reply.user.username != this.$i.username || (this.reply.user.host != null && this.reply.user.host != host))) { - this.text = `@${this.reply.user.username}${this.reply.user.host != null ? '@' + toASCII(this.reply.user.host) : ''} `; - } - - if (this.reply && this.reply.text != null) { - const ast = mfm.parse(this.reply.text); - - for (const x of extractMentions(ast)) { - const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`; - - // 自分は除外 - if (this.$i.username == x.username && x.host == null) continue; - if (this.$i.username == x.username && x.host == host) continue; - - // 重複は除外 - if (this.text.indexOf(`${mention} `) != -1) continue; - - this.text += `${mention} `; - } - } - - if (this.channel) { - this.visibility = 'public'; - this.localOnly = true; // TODO: チャンネルが連合するようになった折には消す - } - - // 公開以外へのリプライ時は元の公開範囲を引き継ぐ - if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) { - this.visibility = this.reply.visibility; - if (this.reply.visibility === 'specified') { - os.api('users/show', { - userIds: this.reply.visibleUserIds.filter(uid => uid !== this.$i.id && uid !== this.reply.userId) - }).then(users => { - this.visibleUsers.push(...users); - }); - - if (this.reply.userId !== this.$i.id) { - os.api('users/show', { userId: this.reply.userId }).then(user => { - this.visibleUsers.push(user); - }); - } - } - } - - if (this.specified) { - this.visibility = 'specified'; - this.visibleUsers.push(this.specified); - } - - // keep cw when reply - if (this.$store.state.keepCw && this.reply && this.reply.cw) { - this.useCw = true; - this.cw = this.reply.cw; - } - - if (this.autofocus) { - this.focus(); - - this.$nextTick(() => { - this.focus(); - }); - } - - // TODO: detach when unmount - new Autocomplete(this.$refs.text, this, { model: 'text' }); - new Autocomplete(this.$refs.cw, this, { model: 'cw' }); - - this.$nextTick(() => { - // 書きかけの投稿を復元 - if (!this.share && !this.mention && !this.specified) { - const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftKey]; - if (draft) { - this.text = draft.data.text; - this.useCw = draft.data.useCw; - this.cw = draft.data.cw; - this.visibility = draft.data.visibility; - this.localOnly = draft.data.localOnly; - this.files = (draft.data.files || []).filter(e => e); - if (draft.data.poll) { - this.poll = draft.data.poll; - } - } - } - - // 削除して編集 - if (this.initialNote) { - const init = this.initialNote; - this.text = init.text ? init.text : ''; - this.files = init.files; - this.cw = init.cw; - this.useCw = init.cw != null; - if (init.poll) { - this.poll = init.poll; - } - this.visibility = init.visibility; - this.localOnly = init.localOnly; - this.quoteId = init.renote ? init.renote.id : null; - } - - this.$nextTick(() => this.watch()); - }); - }, - - methods: { - watch() { - this.$watch('text', () => this.saveDraft()); - this.$watch('useCw', () => this.saveDraft()); - this.$watch('cw', () => this.saveDraft()); - this.$watch('poll', () => this.saveDraft()); - this.$watch('files', () => this.saveDraft(), { deep: true }); - this.$watch('visibility', () => this.saveDraft()); - this.$watch('localOnly', () => this.saveDraft()); - }, - - togglePoll() { - if (this.poll) { - this.poll = null; - } else { - this.poll = { - choices: ['', ''], - multiple: false, - expiresAt: null, - expiredAfter: null, - }; - } - }, - - addTag(tag: string) { - insertTextAtCursor(this.$refs.text, ` #${tag} `); - }, - - focus() { - (this.$refs.text as any).focus(); - }, - - chooseFileFrom(ev) { - selectFiles(ev.currentTarget || ev.target, this.$ts.attachFile).then(files => { - for (const file of files) { - this.files.push(file); - } - }); - }, - - detachFile(id) { - this.files = this.files.filter(x => x.id != id); - }, - - updateFiles(files) { - this.files = files; - }, - - updateFileSensitive(file, sensitive) { - this.files[this.files.findIndex(x => x.id === file.id)].isSensitive = sensitive; - }, - - updateFileName(file, name) { - this.files[this.files.findIndex(x => x.id === file.id)].name = name; - }, - - upload(file: File, name?: string) { - os.upload(file, this.$store.state.uploadFolder, name).then(res => { - this.files.push(res); - }); - }, - - onPollUpdate(poll) { - this.poll = poll; - this.saveDraft(); - }, - - setVisibility() { - if (this.channel) { - // TODO: information dialog - return; - } - - os.popup(import('@/components/visibility-picker.vue'), { - currentVisibility: this.visibility, - currentLocalOnly: this.localOnly, - src: this.$refs.visibilityButton - }, { - changeVisibility: visibility => { - this.visibility = visibility; - if (this.$store.state.rememberNoteVisibility) { - this.$store.set('visibility', visibility); - } - }, - changeLocalOnly: localOnly => { - this.localOnly = localOnly; - if (this.$store.state.rememberNoteVisibility) { - this.$store.set('localOnly', localOnly); - } - } - }, 'closed'); - }, - - addVisibleUser() { - os.selectUser().then(user => { - this.visibleUsers.push(user); - }); - }, - - removeVisibleUser(user) { - this.visibleUsers = erase(user, this.visibleUsers); - }, - - clear() { - this.text = ''; - this.files = []; - this.poll = null; - this.quoteId = null; - }, - - onKeydown(e: KeyboardEvent) { - if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post(); - if (e.which === 27) this.$emit('esc'); - this.typing(); - }, - - onCompositionUpdate(e: CompositionEvent) { - this.imeText = e.data; - this.typing(); - }, - - onCompositionEnd(e: CompositionEvent) { - this.imeText = ''; - }, - - async onPaste(e: ClipboardEvent) { - for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) { - if (item.kind == 'file') { - const file = item.getAsFile(); - const lio = file.name.lastIndexOf('.'); - const ext = lio >= 0 ? file.name.slice(lio) : ''; - const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`; - this.upload(file, formatted); - } - } - - const paste = e.clipboardData.getData('text'); - - if (!this.renote && !this.quoteId && paste.startsWith(url + '/notes/')) { - e.preventDefault(); - - os.confirm({ - type: 'info', - text: this.$ts.quoteQuestion, - }).then(({ canceled }) => { - if (canceled) { - insertTextAtCursor(this.$refs.text, paste); - return; - } - - this.quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1]; - }); - } - }, - - onDragover(e) { - if (!e.dataTransfer.items[0]) return; - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - if (isFile || isDriveFile) { - e.preventDefault(); - this.draghover = true; - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } - }, - - onDragenter(e) { - this.draghover = true; - }, - - onDragleave(e) { - this.draghover = false; - }, - - onDrop(e): void { - this.draghover = false; - - // ファイルだったら - if (e.dataTransfer.files.length > 0) { - e.preventDefault(); - for (const x of Array.from(e.dataTransfer.files)) this.upload(x); - return; - } - - //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - this.files.push(file); - e.preventDefault(); - } - //#endregion - }, - - saveDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - - data[this.draftKey] = { - updatedAt: new Date(), - data: { - text: this.text, - useCw: this.useCw, - cw: this.cw, - visibility: this.visibility, - localOnly: this.localOnly, - files: this.files, - poll: this.poll - } - }; - - localStorage.setItem('drafts', JSON.stringify(data)); - }, - - deleteDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - - delete data[this.draftKey]; - - localStorage.setItem('drafts', JSON.stringify(data)); - }, - - async post() { - let data = { - text: this.text == '' ? undefined : this.text, - fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, - replyId: this.reply ? this.reply.id : undefined, - renoteId: this.renote ? this.renote.id : this.quoteId ? this.quoteId : undefined, - channelId: this.channel ? this.channel : undefined, - poll: this.poll, - cw: this.useCw ? this.cw || '' : undefined, - localOnly: this.localOnly, - visibility: this.visibility, - visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined, - }; - - // plugin - if (notePostInterruptors.length > 0) { - for (const interruptor of notePostInterruptors) { - data = await interruptor.handler(JSON.parse(JSON.stringify(data))); - } - } - - this.posting = true; - os.api('notes/create', data).then(() => { - this.clear(); - this.$nextTick(() => { - this.deleteDraft(); - this.$emit('posted'); - if (this.text && this.text != '') { - const hashtags = mfm.parse(this.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); - const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; - localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); - } - this.posting = false; - }); - }).catch(err => { - this.posting = false; - os.alert({ - type: 'error', - text: err.message + '\n' + (err as any).id, - }); - }); - }, - - cancel() { - this.$emit('cancel'); - }, - - insertMention() { - os.selectUser().then(user => { - insertTextAtCursor(this.$refs.text, '@' + Acct.toString(user) + ' '); - }); - }, - - async insertEmoji(ev) { - os.openEmojiPicker(ev.currentTarget || ev.target, {}, this.$refs.text); - }, - - showActions(ev) { - os.popupMenu(postFormActions.map(action => ({ - text: action.title, - action: () => { - action.handler({ - text: this.text - }, (key, value) => { - if (key === 'text') { this.text = value; } - }); - } - })), ev.currentTarget || ev.target); - } - } -}); -</script> - -<style lang="scss" scoped> -.pxiwixjf { - position: relative; - border: solid 0.5px var(--divider); - border-radius: 8px; - - > .form { - > .preview { - padding: 16px; - } - - > .with-quote { - margin: 0 0 8px 0; - color: var(--accent); - - > button { - padding: 4px 8px; - color: var(--accentAlpha04); - - &:hover { - color: var(--accentAlpha06); - } - - &:active { - color: var(--accentDarken30); - } - } - } - - > .to-specified { - padding: 6px 24px; - margin-bottom: 8px; - overflow: auto; - white-space: nowrap; - - > .visibleUsers { - display: inline; - top: -1px; - font-size: 14px; - - > button { - padding: 4px; - border-radius: 8px; - } - - > span { - margin-right: 14px; - padding: 8px 0 8px 8px; - border-radius: 8px; - background: var(--X4); - - > button { - padding: 4px 8px; - } - } - } - } - - > .cw, - > .text { - display: block; - box-sizing: border-box; - padding: 16px; - margin: 0; - width: 100%; - font-size: 16px; - border: none; - border-radius: 0; - background: transparent; - color: var(--fg); - font-family: inherit; - - &:focus { - outline: none; - } - - &:disabled { - opacity: 0.5; - } - } - - > .cw { - z-index: 1; - padding-bottom: 8px; - border-bottom: solid 0.5px var(--divider); - } - - > .text { - max-width: 100%; - min-width: 100%; - min-height: 60px; - - &.withCw { - padding-top: 8px; - } - } - - > footer { - $height: 44px; - display: flex; - padding: 0 8px 8px 8px; - line-height: $height; - - > .left { - > button { - display: inline-block; - padding: 0; - margin: 0; - font-size: 16px; - width: $height; - height: $height; - border-radius: 6px; - - &:hover { - background: var(--X5); - } - - &.active { - color: var(--accent); - } - } - } - - > .right { - margin-left: auto; - - > .text-count { - opacity: 0.7; - } - - > .visibility { - width: $height; - margin: 0 8px; - - & + .localOnly { - margin-left: 0 !important; - } - } - - > .local-only { - margin: 0 0 0 12px; - opacity: 0.7; - } - - > .submit { - margin: 0; - padding: 0 12px; - line-height: 34px; - font-weight: bold; - border-radius: 4px; - - &:disabled { - opacity: 0.7; - } - - > i { - margin-left: 6px; - } - } - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/side.vue b/packages/client/src/ui/chat/side.vue deleted file mode 100644 index 548a46102b..0000000000 --- a/packages/client/src/ui/chat/side.vue +++ /dev/null @@ -1,157 +0,0 @@ -<template> -<div v-if="component" class="mrajymqm _narrow_"> - <header class="header" @contextmenu.prevent.stop="onContextmenu"> - <MkHeader class="title" :info="pageInfo" :center="false"/> - </header> - <component :is="component" v-bind="props" :ref="changePage" class="body"/> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { resolve } from '@/router'; -import { url } from '@/config'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - }, - - provide() { - return { - navHook: (path) => { - this.navigate(path); - } - }; - }, - - data() { - return { - path: null, - component: null, - props: {}, - pageInfo: null, - history: [], - }; - }, - - computed: { - url(): string { - return url + this.path; - } - }, - - methods: { - changePage(page) { - if (page == null) return; - if (page[symbols.PAGE_INFO]) { - this.pageInfo = page[symbols.PAGE_INFO]; - } - }, - - navigate(path, record = true) { - if (record && this.path) this.history.push(this.path); - this.path = path; - const { component, props } = resolve(path); - this.component = component; - this.props = props; - this.$emit('open'); - }, - - back() { - this.navigate(this.history.pop(), false); - }, - - close() { - this.path = null; - this.component = null; - this.props = {}; - this.$emit('close'); - }, - - onContextmenu(e) { - os.contextMenu([{ - type: 'label', - text: this.path, - }, { - icon: 'fas fa-expand-alt', - text: this.$ts.showInPage, - action: () => { - this.$router.push(this.path); - this.close(); - } - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(this.path); - this.close(); - } - }, null, { - icon: 'fas fa-external-link-alt', - text: this.$ts.openInNewTab, - action: () => { - window.open(this.url, '_blank'); - this.close(); - } - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: () => { - copyToClipboard(this.url); - } - }], e); - } - } -}); -</script> - -<style lang="scss" scoped> -.mrajymqm { - $header-height: 54px; // TODO: どこかに集約したい - - --root-margin: 16px; - --margin: var(--marginHalf); - - height: 100%; - overflow: auto; - box-sizing: border-box; - - > .header { - display: flex; - position: sticky; - z-index: 1000; - top: 0; - height: $header-height; - width: 100%; - font-weight: bold; - //background-color: var(--panel); - -webkit-backdrop-filter: var(--blur, blur(32px)); - backdrop-filter: var(--blur, blur(32px)); - background-color: var(--header); - border-bottom: solid 0.5px var(--divider); - box-sizing: border-box; - - > ._button { - height: $header-height; - width: $header-height; - - &:hover { - color: var(--fgHighlighted); - } - } - - > .title { - flex: 1; - position: relative; - } - } - - > .body { - - } -} -</style> - diff --git a/packages/client/src/ui/chat/store.ts b/packages/client/src/ui/chat/store.ts deleted file mode 100644 index 389d56afb6..0000000000 --- a/packages/client/src/ui/chat/store.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { markRaw } from 'vue'; -import { Storage } from '../../pizzax'; - -export const store = markRaw(new Storage('chatUi', { - widgets: { - where: 'account', - default: [] as { - name: string; - id: string; - data: Record<string, any>; - }[] - }, - tl: { - where: 'deviceAccount', - default: 'home' - }, -})); diff --git a/packages/client/src/ui/chat/sub-note-content.vue b/packages/client/src/ui/chat/sub-note-content.vue deleted file mode 100644 index a85096ebc9..0000000000 --- a/packages/client/src/ui/chat/sub-note-content.vue +++ /dev/null @@ -1,62 +0,0 @@ -<template> -<div class="wrmlmaau"> - <div class="body"> - <span v-if="note.isHidden" style="opacity: 0.5">({{ $ts.private }})</span> - <span v-if="note.deletedAt" style="opacity: 0.5">({{ $ts.deleted }})</span> - <MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="fas fa-reply"></i></MkA> - <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :custom-emojis="note.emojis"/> - <MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA> - </div> - <details v-if="note.files.length > 0"> - <summary>({{ $t('withNFiles', { n: note.files.length }) }})</summary> - <XMediaList :media-list="note.files"/> - </details> - <details v-if="note.poll"> - <summary>{{ $ts.poll }}</summary> - <XPoll :note="note"/> - </details> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import XPoll from '@/components/poll.vue'; -import XMediaList from '@/components/media-list.vue'; -import * as os from '@/os'; - -export default defineComponent({ - components: { - XPoll, - XMediaList, - }, - props: { - note: { - type: Object, - required: true - } - }, - data() { - return { - }; - } -}); -</script> - -<style lang="scss" scoped> -.wrmlmaau { - overflow-wrap: break-word; - - > .body { - > .reply { - margin-right: 6px; - color: var(--accent); - } - - > .rp { - margin-left: 4px; - font-style: oblique; - color: var(--renote); - } - } -} -</style> diff --git a/packages/client/src/ui/chat/widgets.vue b/packages/client/src/ui/chat/widgets.vue deleted file mode 100644 index 337d5a7b58..0000000000 --- a/packages/client/src/ui/chat/widgets.vue +++ /dev/null @@ -1,62 +0,0 @@ -<template> -<div class="qydbhufi"> - <XWidgets :edit="edit" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> - - <button v-if="edit" class="_textButton" style="font-size: 0.9em;" @click="edit = false">{{ $ts.editWidgetsExit }}</button> - <button v-else class="_textButton" style="font-size: 0.9em;" @click="edit = true">{{ $ts.editWidgets }}</button> -</div> -</template> - -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; -import XWidgets from '@/components/widgets.vue'; -import { store } from './store'; - -export default defineComponent({ - components: { - XWidgets, - }, - - data() { - return { - edit: false, - widgets: store.reactiveState.widgets - }; - }, - - methods: { - addWidget(widget) { - store.set('widgets', [widget, ...store.state.widgets]); - }, - - removeWidget(widget) { - store.set('widgets', store.state.widgets.filter(w => w.id != widget.id)); - }, - - updateWidget({ id, data }) { - // TODO: throttleしたい - store.set('widgets', store.state.widgets.map(w => w.id === id ? { - ...w, - data: data - } : w)); - }, - - updateWidgets(widgets) { - store.set('widgets', widgets); - } - } -}); -</script> - -<style lang="scss" scoped> -.qydbhufi { - height: 100%; - box-sizing: border-box; - overflow: auto; - padding: var(--margin); - - ::v-deep(._panel) { - box-shadow: none; - } -} -</style> From 586c11251a8c0e7ca9f8f3bbaad9bf745e6ef948 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 9 Jan 2022 21:35:35 +0900 Subject: [PATCH 04/81] wip: migrate paging components to composition api #7681 --- .../client/src/components/form/pagination.vue | 44 --- packages/client/src/components/notes.vue | 133 +++------ .../client/src/components/notifications.vue | 198 +++++--------- .../client/src/components/ui/pagination.vue | 258 ++++++++++++++++-- packages/client/src/components/user-list.vue | 104 ++----- .../client/src/pages/settings/security.vue | 8 +- .../client/src/pages/user/index.timeline.vue | 60 ++-- packages/client/src/scripts/paging.ts | 246 ----------------- 8 files changed, 374 insertions(+), 677 deletions(-) delete mode 100644 packages/client/src/components/form/pagination.vue delete mode 100644 packages/client/src/scripts/paging.ts diff --git a/packages/client/src/components/form/pagination.vue b/packages/client/src/components/form/pagination.vue deleted file mode 100644 index 3d3b40a783..0000000000 --- a/packages/client/src/components/form/pagination.vue +++ /dev/null @@ -1,44 +0,0 @@ -<template> -<FormSlot> - <template #label><slot name="label"></slot></template> - <div class="abcaccfa"> - <slot :items="items"></slot> - <div v-if="empty" key="_empty_" class="empty"> - <slot name="empty"></slot> - </div> - <MkButton v-show="more" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> - </div> -</FormSlot> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; -import FormSlot from './slot.vue'; -import paging from '@/scripts/paging'; - -export default defineComponent({ - components: { - MkButton, - FormSlot, - }, - - mixins: [ - paging({}), - ], - - props: { - pagination: { - required: true - }, - }, -}); -</script> - -<style lang="scss" scoped> -.abcaccfa { -} -</style> diff --git a/packages/client/src/components/notes.vue b/packages/client/src/components/notes.vue index 4136f72b1b..82703d71c7 100644 --- a/packages/client/src/components/notes.vue +++ b/packages/client/src/components/notes.vue @@ -1,114 +1,49 @@ <template> -<transition name="fade" mode="out-in"> - <MkLoading v-if="fetching"/> - - <MkError v-else-if="error" @retry="init()"/> - - <div v-else-if="empty" class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ $ts.noNotes }}</div> - </div> - - <div v-else class="giivymft" :class="{ noGap }"> - <div v-show="more && reversed" style="margin-bottom: var(--margin);"> - <MkButton style="margin: 0 auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMoreFeature"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> +<MkPagination ref="pagingComponent" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noNotes }}</div> </div> + </template> - <XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :no-gap="noGap" :ad="true" class="notes"> - <XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note" @update:note="updated(note, $event)"/> - </XList> - - <div v-show="more && !reversed" style="margin-top: var(--margin);"> - <MkButton v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" style="margin: 0 auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> + <template #default="{ items: notes }"> + <div class="giivymft" :class="{ noGap }"> + <XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" :no-gap="noGap" :ad="true" class="notes"> + <XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note" @update:note="updated(note, $event)"/> + </XList> </div> - </div> -</transition> + </template> +</MkPagination> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import paging from '@/scripts/paging'; -import XNote from './note.vue'; -import XList from './date-separated-list.vue'; -import MkButton from '@/components/ui/button.vue'; +<script lang="ts" setup> +import { ref } from 'vue'; +import XNote from '@/components/note.vue'; +import XList from '@/components/date-separated-list.vue'; +import MkPagination from '@/components/ui/pagination.vue'; +import { Paging } from '@/components/ui/pagination.vue'; -export default defineComponent({ - components: { - XNote, XList, MkButton, +const props = defineProps<{ + pagination: Paging; + noGap?: boolean; +}>(); + +const pagingComponent = ref<InstanceType<typeof MkPagination>>(); + +const updated = (oldValue, newValue) => { + const i = pagingComponent.value.items.findIndex(n => n === oldValue); + pagingComponent.value.items[i] = newValue; +}; + +defineExpose({ + prepend: (note) => { + pagingComponent.value?.prepend(note); }, - - mixins: [ - paging({ - before: (self) => { - self.$emit('before'); - }, - - after: (self, e) => { - self.$emit('after', e); - } - }), - ], - - props: { - pagination: { - required: true - }, - prop: { - type: String, - required: false - }, - noGap: { - type: Boolean, - required: false, - default: false - }, - }, - - emits: ['before', 'after'], - - computed: { - notes(): any[] { - return this.prop ? this.items.map(item => item[this.prop]) : this.items; - }, - - reversed(): boolean { - return this.pagination.reversed; - } - }, - - methods: { - updated(oldValue, newValue) { - const i = this.notes.findIndex(n => n === oldValue); - if (this.prop) { - this.items[i][this.prop] = newValue; - } else { - this.items[i] = newValue; - } - }, - - focus() { - this.$refs.notes.focus(); - } - } }); </script> <style lang="scss" scoped> -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.125s ease; -} -.fade-enter-from, -.fade-leave-to { - opacity: 0; -} - .giivymft { &.noGap { > .notes { diff --git a/packages/client/src/components/notifications.vue b/packages/client/src/components/notifications.vue index 328888c355..aa4b480694 100644 --- a/packages/client/src/components/notifications.vue +++ b/packages/client/src/components/notifications.vue @@ -1,159 +1,85 @@ <template> -<transition name="fade" mode="out-in"> - <MkLoading v-if="fetching"/> +<MkPagination ref="pagingComponent" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noNotifications }}</div> + </div> + </template> - <MkError v-else-if="error" @retry="init()"/> - - <p v-else-if="empty" class="mfcuwfyp">{{ $ts.noNotifications }}</p> - - <div v-else> - <XList v-slot="{ item: notification }" class="elsfgstc" :items="items" :no-gap="true"> + <template #default="{ items: notifications }"> + <XList v-slot="{ item: notification }" class="elsfgstc" :items="notifications" :no-gap="true"> <XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note" @update:note="noteUpdated(notification.note, $event)"/> <XNotification v-else :key="notification.id" :notification="notification" :with-time="true" :full="true" class="_panel notification"/> </XList> - - <MkButton v-show="more" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" primary style="margin: var(--margin) auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> - </div> -</transition> + </template> +</MkPagination> </template> -<script lang="ts"> -import { defineComponent, PropType, markRaw } from 'vue'; -import paging from '@/scripts/paging'; -import XNotification from './notification.vue'; -import XList from './date-separated-list.vue'; -import XNote from './note.vue'; +<script lang="ts" setup> +import { defineComponent, PropType, markRaw, onUnmounted, onMounted, computed, ref } from 'vue'; import { notificationTypes } from 'misskey-js'; +import MkPagination from '@/components/ui/pagination.vue'; +import { Paging } from '@/components/ui/pagination.vue'; +import XNotification from '@/components/notification.vue'; +import XList from '@/components/date-separated-list.vue'; +import XNote from '@/components/note.vue'; import * as os from '@/os'; import { stream } from '@/stream'; -import MkButton from '@/components/ui/button.vue'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - XNotification, - XList, - XNote, - MkButton, - }, +const props = defineProps<{ + includeTypes?: PropType<typeof notificationTypes[number][]>; + unreadOnly?: boolean; +}>(); - mixins: [ - paging({}), - ], +const pagingComponent = ref<InstanceType<typeof MkPagination>>(); - props: { - includeTypes: { - type: Array as PropType<typeof notificationTypes[number][]>, - required: false, - default: null, - }, - unreadOnly: { - type: Boolean, - required: false, - default: false, - }, - }, +const allIncludeTypes = computed(() => props.includeTypes ?? notificationTypes.filter(x => !$i.mutingNotificationTypes.includes(x))); - data() { - return { - connection: null, - pagination: { - endpoint: 'i/notifications', - limit: 10, - params: () => ({ - includeTypes: this.allIncludeTypes || undefined, - unreadOnly: this.unreadOnly, - }) - }, - }; - }, +const pagination: Paging = { + endpoint: 'i/notifications' as const, + limit: 10, + params: computed(() => ({ + includeTypes: allIncludeTypes.value || undefined, + unreadOnly: props.unreadOnly, + })), +}; - computed: { - allIncludeTypes() { - return this.includeTypes ?? notificationTypes.filter(x => !this.$i.mutingNotificationTypes.includes(x)); - } - }, - - watch: { - includeTypes: { - handler() { - this.reload(); - }, - deep: true - }, - unreadOnly: { - handler() { - this.reload(); - }, - }, - // TODO: vue/vuexのバグか仕様かは不明なものの、プロフィール更新するなどして $i が更新されると、 - // mutingNotificationTypes に変化が無くてもこのハンドラーが呼び出され無駄なリロードが発生するのを直す - '$i.mutingNotificationTypes': { - handler() { - if (this.includeTypes === null) { - this.reload(); - } - }, - deep: true - } - }, - - mounted() { - this.connection = markRaw(stream.useChannel('main')); - this.connection.on('notification', this.onNotification); - }, - - beforeUnmount() { - this.connection.dispose(); - }, - - methods: { - onNotification(notification) { - const isMuted = !this.allIncludeTypes.includes(notification.type); - if (isMuted || document.visibilityState === 'visible') { - stream.send('readNotification', { - id: notification.id - }); - } - - if (!isMuted) { - this.prepend({ - ...notification, - isRead: document.visibilityState === 'visible' - }); - } - }, - - noteUpdated(oldValue, newValue) { - const i = this.items.findIndex(n => n.note === oldValue); - this.items[i] = { - ...this.items[i], - note: newValue - }; - }, +const onNotification = (notification) => { + const isMuted = !allIncludeTypes.value.includes(notification.type); + if (isMuted || document.visibilityState === 'visible') { + stream.send('readNotification', { + id: notification.id + }); } + + if (!isMuted) { + pagingComponent.value.prepend({ + ...notification, + isRead: document.visibilityState === 'visible' + }); + } +}; + +const noteUpdated = (oldValue, newValue) => { + const i = pagingComponent.value.items.findIndex(n => n.note === oldValue); + pagingComponent.value.items[i] = { + ...pagingComponent.value.items[i], + note: newValue, + }; +}; + +onMounted(() => { + const connection = stream.useChannel('main'); + connection.on('notification', onNotification); + onUnmounted(() => { + connection.dispose(); + }); }); </script> <style lang="scss" scoped> -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.125s ease; -} -.fade-enter-from, -.fade-leave-to { - opacity: 0; -} - -.mfcuwfyp { - margin: 0; - padding: 16px; - text-align: center; - color: var(--fg); -} - .elsfgstc { background: var(--panel); } diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue index 64af4a54f7..79744e528d 100644 --- a/packages/client/src/components/ui/pagination.vue +++ b/packages/client/src/components/ui/pagination.vue @@ -13,43 +13,247 @@ </slot> </div> - <div v-else class="cxiknjgy"> + <div v-else ref="rootEl"> <slot :items="items"></slot> - <div v-show="more" key="_more_" class="more _gap"> - <MkButton v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> + <div v-show="more" key="_more_" class="cxiknjgy _gap"> + <MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore"> + {{ $ts.loadMore }} </MkButton> + <MkLoading v-else class="loading"/> </div> </div> </transition> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from './button.vue'; -import paging from '@/scripts/paging'; +<script lang="ts" setup> +import { computed, ComputedRef, isRef, markRaw, onActivated, onDeactivated, Ref, ref, watch } from 'vue'; +import * as misskey from 'misskey-js'; +import * as os from '@/os'; +import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from '@/scripts/scroll'; +import MkButton from '@/components/ui/button.vue'; -export default defineComponent({ - components: { - MkButton - }, +const SECOND_FETCH_LIMIT = 30; - mixins: [ - paging({}), - ], +export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints> = { + endpoint: E; + limit: number; + params?: misskey.Endpoints[E]['req'] | ComputedRef<misskey.Endpoints[E]['req']>; - props: { - pagination: { - required: true - }, + /** + * 検索APIのような、ページング不可なエンドポイントを利用する場合 + * (そのようなAPIをこの関数で使うのは若干矛盾してるけど) + */ + noPaging?: boolean; - disableAutoLoad: { - type: Boolean, - required: false, - default: false, + /** + * items 配列の中身を逆順にする(新しい方が最後) + */ + reversed?: boolean; +}; + +const props = withDefaults(defineProps<{ + pagination: Paging; + disableAutoLoad?: boolean; + displayLimit?: number; +}>(), { + displayLimit: 30, +}); + +const rootEl = ref<HTMLElement>(); +const items = ref([]); +const queue = ref([]); +const offset = ref(0); +const fetching = ref(true); +const moreFetching = ref(false); +const inited = ref(false); +const more = ref(false); +const backed = ref(false); // 遡り中か否か +const isBackTop = ref(false); +const empty = computed(() => items.value.length === 0 && !fetching.value && inited.value); +const error = computed(() => !fetching.value && !inited.value); + +const init = async () => { + queue.value = []; + fetching.value = true; + const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; + await os.api(props.pagination.endpoint, { + ...params, + limit: props.pagination.noPaging ? (props.pagination.limit || 10) : (props.pagination.limit || 10) + 1, + }).then(res => { + for (let i = 0; i < res.length; i++) { + const item = res[i]; + markRaw(item); + if (props.pagination.reversed) { + if (i === res.length - 2) item._shouldInsertAd_ = true; + } else { + if (i === 3) item._shouldInsertAd_ = true; + } } - }, + if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) { + res.pop(); + items.value = props.pagination.reversed ? [...res].reverse() : res; + more.value = true; + } else { + items.value = props.pagination.reversed ? [...res].reverse() : res; + more.value = false; + } + offset.value = res.length; + inited.value = true; + fetching.value = false; + }, e => { + fetching.value = false; + }); +}; + +const reload = () => { + items.value = []; + init(); +}; + +const fetchMore = async () => { + if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return; + moreFetching.value = true; + backed.value = true; + const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; + await os.api(props.pagination.endpoint, { + ...params, + limit: SECOND_FETCH_LIMIT + 1, + ...(props.pagination.offsetMode ? { + offset: offset.value, + } : { + untilId: props.pagination.reversed ? items.value[0].id : items.value[items.value.length - 1].id, + }), + }).then(res => { + for (let i = 0; i < res.length; i++) { + const item = res[i]; + markRaw(item); + if (props.pagination.reversed) { + if (i === res.length - 9) item._shouldInsertAd_ = true; + } else { + if (i === 10) item._shouldInsertAd_ = true; + } + } + if (res.length > SECOND_FETCH_LIMIT) { + res.pop(); + items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); + more.value = true; + } else { + items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); + more.value = false; + } + offset.value += res.length; + moreFetching.value = false; + }, e => { + moreFetching.value = false; + }); +}; + +const fetchMoreAhead = async () => { + if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return; + moreFetching.value = true; + const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; + await os.api(props.pagination.endpoint, { + ...params, + limit: SECOND_FETCH_LIMIT + 1, + ...(props.pagination.offsetMode ? { + offset: offset.value, + } : { + sinceId: props.pagination.reversed ? items.value[0].id : items.value[items.value.length - 1].id, + }), + }).then(res => { + for (const item of res) { + markRaw(item); + } + if (res.length > SECOND_FETCH_LIMIT) { + res.pop(); + items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); + more.value = true; + } else { + items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); + more.value = false; + } + offset.value += res.length; + moreFetching.value = false; + }, e => { + moreFetching.value = false; + }); +}; + +const prepend = (item) => { + if (props.pagination.reversed) { + const container = getScrollContainer(rootEl.value); + const pos = getScrollPosition(rootEl.value); + const viewHeight = container.clientHeight; + const height = container.scrollHeight; + const isBottom = (pos + viewHeight > height - 32); + if (isBottom) { + // オーバーフローしたら古いアイテムは捨てる + if (items.value.length >= props.displayLimit) { + // このやり方だとVue 3.2以降アニメーションが動かなくなる + //items.value = items.value.slice(-props.displayLimit); + while (items.value.length >= props.displayLimit) { + items.value.shift(); + } + more.value = true; + } + } + items.value.push(item); + // TODO + } else { + const isTop = isBackTop.value || (document.body.contains(rootEl.value) && isTopVisible(rootEl.value)); + console.log(item, top); + + if (isTop) { + // Prepend the item + items.value.unshift(item); + + // オーバーフローしたら古いアイテムは捨てる + if (items.value.length >= props.displayLimit) { + // このやり方だとVue 3.2以降アニメーションが動かなくなる + //this.items = items.value.slice(0, props.displayLimit); + while (items.value.length >= props.displayLimit) { + items.value.pop(); + } + more.value = true; + } + } else { + queue.value.push(item); + onScrollTop(rootEl.value, () => { + for (const item of queue.value) { + prepend(item); + } + queue.value = []; + }); + } + } +}; + +const append = (item) => { + items.value.push(item); +}; + +watch(props.pagination.params, init, { deep: true }); +watch(queue, (a, b) => { + if (a.length === 0 && b.length === 0) return; + this.$emit('queue', queue.value.length); +}, { deep: true }); + +init(); + +onActivated(() => { + isBackTop.value = false; +}); + +onDeactivated(() => { + isBackTop.value = window.scrollY === 0; +}); + +defineExpose({ + items, + reload, + fetchMoreAhead, + prepend, + append, }); </script> @@ -64,11 +268,9 @@ export default defineComponent({ } .cxiknjgy { - > .more > .button { + > .button { margin-left: auto; margin-right: auto; - height: 48px; - min-width: 150px; } } </style> diff --git a/packages/client/src/components/user-list.vue b/packages/client/src/components/user-list.vue index 2148dab608..3e273721c7 100644 --- a/packages/client/src/components/user-list.vue +++ b/packages/client/src/components/user-list.vue @@ -1,91 +1,39 @@ <template> -<MkError v-if="error" @retry="init()"/> +<MkPagination ref="pagingComponent" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noUsers }}</div> + </div> + </template> -<div v-else class="efvhhmdq _isolated"> - <div v-if="empty" class="no-users"> - <p>{{ $ts.noUsers }}</p> - </div> - <div class="users"> - <MkUserInfo v-for="user in users" :key="user.id" class="user" :user="user"/> - </div> - <button v-show="more" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" class="more" :class="{ fetching: moreFetching }" :disabled="moreFetching" @click="fetchMore"> - <template v-if="moreFetching"><i class="fas fa-spinner fa-pulse fa-fw"></i></template>{{ moreFetching ? $ts.loading : $ts.loadMore }} - </button> -</div> + <template #default="{ items: users }"> + <div class="efvhhmdq"> + <MkUserInfo v-for="user in users" :key="user.id" class="user" :user="user"/> + </div> + </template> +</MkPagination> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import paging from '@/scripts/paging'; -import MkUserInfo from './user-info.vue'; +<script lang="ts" setup> +import { ref } from 'vue'; +import MkUserInfo from '@/components/user-info.vue'; +import MkPagination from '@/components/ui/pagination.vue'; +import { Paging } from '@/components/ui/pagination.vue'; import { userPage } from '@/filters/user'; -export default defineComponent({ - components: { - MkUserInfo, - }, +const props = defineProps<{ + pagination: Paging; + noGap?: boolean; +}>(); - mixins: [ - paging({}), - ], - - props: { - pagination: { - required: true - }, - extract: { - required: false - }, - expanded: { - type: Boolean, - default: true - }, - }, - - computed: { - users() { - return this.extract ? this.extract(this.items) : this.items; - } - }, - - methods: { - userPage - } -}); +const pagingComponent = ref<InstanceType<typeof MkPagination>>(); </script> <style lang="scss" scoped> .efvhhmdq { - > .no-users { - text-align: center; - } - - > .users { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - grid-gap: var(--margin); - } - - > .more { - display: block; - width: 100%; - padding: 16px; - - &:hover { - background: rgba(#000, 0.025); - } - - &:active { - background: rgba(#000, 0.05); - } - - &.fetching { - cursor: wait; - } - - > i { - margin-right: 4px; - } - } + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + grid-gap: var(--margin); } </style> diff --git a/packages/client/src/pages/settings/security.vue b/packages/client/src/pages/settings/security.vue index 82a21d5b16..03f2d6300b 100644 --- a/packages/client/src/pages/settings/security.vue +++ b/packages/client/src/pages/settings/security.vue @@ -12,7 +12,7 @@ <FormSection> <template #label>{{ $ts.signinHistory }}</template> - <FormPagination :pagination="pagination"> + <MkPagination :pagination="pagination"> <template v-slot="{items}"> <div> <div v-for="item in items" :key="item.id" v-panel class="timnmucd"> @@ -25,7 +25,7 @@ </div> </div> </template> - </FormPagination> + </MkPagination> </FormSection> <FormSection> @@ -42,7 +42,7 @@ import { defineComponent } from 'vue'; import FormSection from '@/components/form/section.vue'; import FormSlot from '@/components/form/slot.vue'; import FormButton from '@/components/ui/button.vue'; -import FormPagination from '@/components/form/pagination.vue'; +import MkPagination from '@/components/ui/pagination.vue'; import X2fa from './2fa.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; @@ -51,7 +51,7 @@ export default defineComponent({ components: { FormSection, FormButton, - FormPagination, + MkPagination, FormSlot, X2fa, }, diff --git a/packages/client/src/pages/user/index.timeline.vue b/packages/client/src/pages/user/index.timeline.vue index 2ffa496979..7396a76efe 100644 --- a/packages/client/src/pages/user/index.timeline.vue +++ b/packages/client/src/pages/user/index.timeline.vue @@ -1,60 +1,36 @@ <template> <div v-sticky-container class="yrzkoczt"> - <MkTab v-model="with_" class="tab"> + <MkTab v-model="include" class="tab"> <option :value="null">{{ $ts.notes }}</option> <option value="replies">{{ $ts.notesAndReplies }}</option> <option value="files">{{ $ts.withFiles }}</option> </MkTab> - <XNotes ref="timeline" :no-gap="true" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/> + <XNotes ref="timeline" :no-gap="true" :pagination="pagination"/> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref, computed } from 'vue'; +import * as misskey from 'misskey-js'; import XNotes from '@/components/notes.vue'; import MkTab from '@/components/tab.vue'; import * as os from '@/os'; -export default defineComponent({ - components: { - XNotes, - MkTab, - }, +const props = defineProps<{ + user: misskey.entities.UserDetailed; +}>(); - props: { - user: { - type: Object, - required: true, - }, - }, +const include = ref<string | null>(null); - data() { - return { - date: null, - with_: null, - pagination: { - endpoint: 'users/notes', - limit: 10, - params: init => ({ - userId: this.user.id, - includeReplies: this.with_ === 'replies', - withFiles: this.with_ === 'files', - untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined), - }) - } - }; - }, - - watch: { - user() { - this.$refs.timeline.reload(); - }, - - with_() { - this.$refs.timeline.reload(); - }, - }, -}); +const pagination = { + endpoint: 'users/notes' as const, + limit: 10, + params: computed(() => ({ + userId: props.user.id, + includeReplies: include.value === 'replies', + withFiles: include.value === 'files', + })), +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/scripts/paging.ts b/packages/client/src/scripts/paging.ts deleted file mode 100644 index ef63ecc450..0000000000 --- a/packages/client/src/scripts/paging.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { markRaw } from 'vue'; -import * as os from '@/os'; -import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from './scroll'; - -const SECOND_FETCH_LIMIT = 30; - -// reversed: items 配列の中身を逆順にする(新しい方が最後) - -export default (opts) => ({ - emits: ['queue'], - - data() { - return { - items: [], - queue: [], - offset: 0, - fetching: true, - moreFetching: false, - inited: false, - more: false, - backed: false, // 遡り中か否か - isBackTop: false, - }; - }, - - computed: { - empty(): boolean { - return this.items.length === 0 && !this.fetching && this.inited; - }, - - error(): boolean { - return !this.fetching && !this.inited; - }, - }, - - watch: { - pagination: { - handler() { - this.init(); - }, - deep: true - }, - - queue: { - handler(a, b) { - if (a.length === 0 && b.length === 0) return; - this.$emit('queue', this.queue.length); - }, - deep: true - } - }, - - created() { - opts.displayLimit = opts.displayLimit || 30; - this.init(); - }, - - activated() { - this.isBackTop = false; - }, - - deactivated() { - this.isBackTop = window.scrollY === 0; - }, - - methods: { - reload() { - this.items = []; - this.init(); - }, - - replaceItem(finder, data) { - const i = this.items.findIndex(finder); - this.items[i] = data; - }, - - removeItem(finder) { - const i = this.items.findIndex(finder); - this.items.splice(i, 1); - }, - - async init() { - this.queue = []; - this.fetching = true; - if (opts.before) opts.before(this); - let params = typeof this.pagination.params === 'function' ? this.pagination.params(true) : this.pagination.params; - if (params && params.then) params = await params; - if (params === null) return; - const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; - await os.api(endpoint, { - ...params, - limit: this.pagination.noPaging ? (this.pagination.limit || 10) : (this.pagination.limit || 10) + 1, - }).then(items => { - for (let i = 0; i < items.length; i++) { - const item = items[i]; - markRaw(item); - if (this.pagination.reversed) { - if (i === items.length - 2) item._shouldInsertAd_ = true; - } else { - if (i === 3) item._shouldInsertAd_ = true; - } - } - if (!this.pagination.noPaging && (items.length > (this.pagination.limit || 10))) { - items.pop(); - this.items = this.pagination.reversed ? [...items].reverse() : items; - this.more = true; - } else { - this.items = this.pagination.reversed ? [...items].reverse() : items; - this.more = false; - } - this.offset = items.length; - this.inited = true; - this.fetching = false; - if (opts.after) opts.after(this, null); - }, e => { - this.fetching = false; - if (opts.after) opts.after(this, e); - }); - }, - - async fetchMore() { - if (!this.more || this.fetching || this.moreFetching || this.items.length === 0) return; - this.moreFetching = true; - this.backed = true; - let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params; - if (params && params.then) params = await params; - const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; - await os.api(endpoint, { - ...params, - limit: SECOND_FETCH_LIMIT + 1, - ...(this.pagination.offsetMode ? { - offset: this.offset, - } : { - untilId: this.pagination.reversed ? this.items[0].id : this.items[this.items.length - 1].id, - }), - }).then(items => { - for (let i = 0; i < items.length; i++) { - const item = items[i]; - markRaw(item); - if (this.pagination.reversed) { - if (i === items.length - 9) item._shouldInsertAd_ = true; - } else { - if (i === 10) item._shouldInsertAd_ = true; - } - } - if (items.length > SECOND_FETCH_LIMIT) { - items.pop(); - this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); - this.more = true; - } else { - this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); - this.more = false; - } - this.offset += items.length; - this.moreFetching = false; - }, e => { - this.moreFetching = false; - }); - }, - - async fetchMoreFeature() { - if (!this.more || this.fetching || this.moreFetching || this.items.length === 0) return; - this.moreFetching = true; - let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params; - if (params && params.then) params = await params; - const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; - await os.api(endpoint, { - ...params, - limit: SECOND_FETCH_LIMIT + 1, - ...(this.pagination.offsetMode ? { - offset: this.offset, - } : { - sinceId: this.pagination.reversed ? this.items[0].id : this.items[this.items.length - 1].id, - }), - }).then(items => { - for (const item of items) { - markRaw(item); - } - if (items.length > SECOND_FETCH_LIMIT) { - items.pop(); - this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); - this.more = true; - } else { - this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); - this.more = false; - } - this.offset += items.length; - this.moreFetching = false; - }, e => { - this.moreFetching = false; - }); - }, - - prepend(item) { - if (this.pagination.reversed) { - const container = getScrollContainer(this.$el); - const pos = getScrollPosition(this.$el); - const viewHeight = container.clientHeight; - const height = container.scrollHeight; - const isBottom = (pos + viewHeight > height - 32); - if (isBottom) { - // オーバーフローしたら古いアイテムは捨てる - if (this.items.length >= opts.displayLimit) { - // このやり方だとVue 3.2以降アニメーションが動かなくなる - //this.items = this.items.slice(-opts.displayLimit); - while (this.items.length >= opts.displayLimit) { - this.items.shift(); - } - this.more = true; - } - } - this.items.push(item); - // TODO - } else { - const isTop = this.isBackTop || (document.body.contains(this.$el) && isTopVisible(this.$el)); - - if (isTop) { - // Prepend the item - this.items.unshift(item); - - // オーバーフローしたら古いアイテムは捨てる - if (this.items.length >= opts.displayLimit) { - // このやり方だとVue 3.2以降アニメーションが動かなくなる - //this.items = this.items.slice(0, opts.displayLimit); - while (this.items.length >= opts.displayLimit) { - this.items.pop(); - } - this.more = true; - } - } else { - this.queue.push(item); - onScrollTop(this.$el, () => { - for (const item of this.queue) { - this.prepend(item); - } - this.queue = []; - }); - } - } - }, - - append(item) { - this.items.push(item); - }, - } -}); From d3315bda1120440fd4ae6c2650569ee38444f225 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 9 Jan 2022 22:57:27 +0900 Subject: [PATCH 05/81] wip: migrate paging components to composition api --- packages/client/src/components/timeline.vue | 293 ++++++++---------- .../client/src/components/ui/pagination.vue | 6 +- 2 files changed, 131 insertions(+), 168 deletions(-) diff --git a/packages/client/src/components/timeline.vue b/packages/client/src/components/timeline.vue index 53697671b2..a7af02c30b 100644 --- a/packages/client/src/components/timeline.vue +++ b/packages/client/src/components/timeline.vue @@ -1,184 +1,143 @@ <template> -<XNotes ref="tl" :no-gap="!$store.state.showGapBetweenNotesInTimeline" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)" @queue="$emit('queue', $event)"/> +<XNotes ref="tlComponent" :no-gap="!$store.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)"/> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { ref, computed, provide, onUnmounted } from 'vue'; import XNotes from './notes.vue'; import * as os from '@/os'; import { stream } from '@/stream'; import * as sound from '@/scripts/sound'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - XNotes - }, +const props = defineProps<{ + src: string; + list?: string; + antenna?: string; + channel?: string; + sound?: boolean; +}>(); - provide() { - return { - inChannel: this.src === 'channel' - }; - }, +const emit = defineEmits<{ + (e: 'note'): void; + (e: 'queue', count: number): void; +}>(); - props: { - src: { - type: String, - required: true - }, - list: { - type: String, - required: false - }, - antenna: { - type: String, - required: false - }, - channel: { - type: String, - required: false - }, - sound: { - type: Boolean, - required: false, - default: false, - } - }, +provide('inChannel', computed(() => props.src === 'channel')); - emits: ['note', 'queue', 'before', 'after'], +const tlComponent = ref<InstanceType<typeof XNotes>>(); - data() { - return { - connection: null, - connection2: null, - pagination: null, - baseQuery: { - includeMyRenotes: this.$store.state.showMyRenotes, - includeRenotedMyNotes: this.$store.state.showRenotedMyNotes, - includeLocalRenotes: this.$store.state.showLocalRenotes - }, - query: {}, - date: null - }; - }, +const prepend = note => { + tlComponent.value.prepend(note); - created() { - const prepend = note => { - (this.$refs.tl as any).prepend(note); + emit('note'); - this.$emit('note'); - - if (this.sound) { - sound.play(note.userId === this.$i.id ? 'noteMy' : 'note'); - } - }; - - const onUserAdded = () => { - (this.$refs.tl as any).reload(); - }; - - const onUserRemoved = () => { - (this.$refs.tl as any).reload(); - }; - - const onChangeFollowing = () => { - if (!this.$refs.tl.backed) { - this.$refs.tl.reload(); - } - }; - - let endpoint; - - if (this.src == 'antenna') { - endpoint = 'antennas/notes'; - this.query = { - antennaId: this.antenna - }; - this.connection = markRaw(stream.useChannel('antenna', { - antennaId: this.antenna - })); - this.connection.on('note', prepend); - } else if (this.src == 'home') { - endpoint = 'notes/timeline'; - this.connection = markRaw(stream.useChannel('homeTimeline')); - this.connection.on('note', prepend); - - this.connection2 = markRaw(stream.useChannel('main')); - this.connection2.on('follow', onChangeFollowing); - this.connection2.on('unfollow', onChangeFollowing); - } else if (this.src == 'local') { - endpoint = 'notes/local-timeline'; - this.connection = markRaw(stream.useChannel('localTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'social') { - endpoint = 'notes/hybrid-timeline'; - this.connection = markRaw(stream.useChannel('hybridTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'global') { - endpoint = 'notes/global-timeline'; - this.connection = markRaw(stream.useChannel('globalTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'mentions') { - endpoint = 'notes/mentions'; - this.connection = markRaw(stream.useChannel('main')); - this.connection.on('mention', prepend); - } else if (this.src == 'directs') { - endpoint = 'notes/mentions'; - this.query = { - visibility: 'specified' - }; - const onNote = note => { - if (note.visibility == 'specified') { - prepend(note); - } - }; - this.connection = markRaw(stream.useChannel('main')); - this.connection.on('mention', onNote); - } else if (this.src == 'list') { - endpoint = 'notes/user-list-timeline'; - this.query = { - listId: this.list - }; - this.connection = markRaw(stream.useChannel('userList', { - listId: this.list - })); - this.connection.on('note', prepend); - this.connection.on('userAdded', onUserAdded); - this.connection.on('userRemoved', onUserRemoved); - } else if (this.src == 'channel') { - endpoint = 'channels/timeline'; - this.query = { - channelId: this.channel - }; - this.connection = markRaw(stream.useChannel('channel', { - channelId: this.channel - })); - this.connection.on('note', prepend); - } - - this.pagination = { - endpoint: endpoint, - limit: 10, - params: init => ({ - untilDate: this.date?.getTime(), - ...this.baseQuery, ...this.query - }) - }; - }, - - beforeUnmount() { - this.connection.dispose(); - if (this.connection2) this.connection2.dispose(); - }, - - methods: { - focus() { - this.$refs.tl.focus(); - }, - - timetravel(date?: Date) { - this.date = date; - this.$refs.tl.reload(); - } + if (props.sound) { + sound.play($i && (note.userId === $i.id) ? 'noteMy' : 'note'); } +}; + +const onUserAdded = () => { + tlComponent.value.reload(); +}; + +const onUserRemoved = () => { + tlComponent.value.reload(); +}; + +const onChangeFollowing = () => { + if (!tlComponent.value.backed) { + tlComponent.value.reload(); + } +}; + +let endpoint; +let query; +let connection; +let connection2; + +if (props.src === 'antenna') { + endpoint = 'antennas/notes'; + query = { + antennaId: props.antenna + }; + connection = stream.useChannel('antenna', { + antennaId: props.antenna + }); + connection.on('note', prepend); +} else if (props.src === 'home') { + endpoint = 'notes/timeline'; + connection = stream.useChannel('homeTimeline'); + connection.on('note', prepend); + + connection2 = stream.useChannel('main'); + connection2.on('follow', onChangeFollowing); + connection2.on('unfollow', onChangeFollowing); +} else if (props.src === 'local') { + endpoint = 'notes/local-timeline'; + connection = stream.useChannel('localTimeline'); + connection.on('note', prepend); +} else if (props.src === 'social') { + endpoint = 'notes/hybrid-timeline'; + connection = stream.useChannel('hybridTimeline'); + connection.on('note', prepend); +} else if (props.src === 'global') { + endpoint = 'notes/global-timeline'; + connection = stream.useChannel('globalTimeline'); + connection.on('note', prepend); +} else if (props.src === 'mentions') { + endpoint = 'notes/mentions'; + connection = stream.useChannel('main'); + connection.on('mention', prepend); +} else if (props.src === 'directs') { + endpoint = 'notes/mentions'; + query = { + visibility: 'specified' + }; + const onNote = note => { + if (note.visibility == 'specified') { + prepend(note); + } + }; + connection = stream.useChannel('main'); + connection.on('mention', onNote); +} else if (props.src === 'list') { + endpoint = 'notes/user-list-timeline'; + query = { + listId: props.list + }; + connection = stream.useChannel('userList', { + listId: props.list + }); + connection.on('note', prepend); + connection.on('userAdded', onUserAdded); + connection.on('userRemoved', onUserRemoved); +} else if (props.src === 'channel') { + endpoint = 'channels/timeline'; + query = { + channelId: props.channel + }; + connection = stream.useChannel('channel', { + channelId: props.channel + }); + connection.on('note', prepend); +} + +const pagination = { + endpoint: endpoint, + limit: 10, + params: query, +}; + +onUnmounted(() => { + connection.dispose(); + if (connection2) connection2.dispose(); }); + +/* TODO +const timetravel = (date?: Date) => { + this.date = date; + this.$refs.tl.reload(); +}; +*/ </script> diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue index 79744e528d..ab55b3c5bf 100644 --- a/packages/client/src/components/ui/pagination.vue +++ b/packages/client/src/components/ui/pagination.vue @@ -59,6 +59,10 @@ const props = withDefaults(defineProps<{ displayLimit: 30, }); +const emit = defineEmits<{ + (e: 'queue', count: number): void; +}>(); + const rootEl = ref<HTMLElement>(); const items = ref([]); const queue = ref([]); @@ -235,7 +239,7 @@ const append = (item) => { watch(props.pagination.params, init, { deep: true }); watch(queue, (a, b) => { if (a.length === 0 && b.length === 0) return; - this.$emit('queue', queue.value.length); + emit('queue', queue.value.length); }, { deep: true }); init(); From f9882a0c5cd1364d806d2487fe5f9e4f7e2d8351 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Mon, 10 Jan 2022 00:45:20 +0900 Subject: [PATCH 06/81] wip: migrate paging components to composition api --- packages/client/src/components/notes.vue | 3 +- .../client/src/components/notifications.vue | 13 +++--- .../client/src/components/ui/pagination.vue | 13 ++++-- packages/client/src/pages/favorites.vue | 40 ++++++++++++++++--- 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/packages/client/src/components/notes.vue b/packages/client/src/components/notes.vue index 82703d71c7..d6107216e2 100644 --- a/packages/client/src/components/notes.vue +++ b/packages/client/src/components/notes.vue @@ -32,8 +32,7 @@ const props = defineProps<{ const pagingComponent = ref<InstanceType<typeof MkPagination>>(); const updated = (oldValue, newValue) => { - const i = pagingComponent.value.items.findIndex(n => n === oldValue); - pagingComponent.value.items[i] = newValue; + pagingComponent.value?.updateItem(oldValue.id, () => newValue); }; defineExpose({ diff --git a/packages/client/src/components/notifications.vue b/packages/client/src/components/notifications.vue index aa4b480694..31511fb515 100644 --- a/packages/client/src/components/notifications.vue +++ b/packages/client/src/components/notifications.vue @@ -9,7 +9,7 @@ <template #default="{ items: notifications }"> <XList v-slot="{ item: notification }" class="elsfgstc" :items="notifications" :no-gap="true"> - <XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note" @update:note="noteUpdated(notification.note, $event)"/> + <XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note" @update:note="noteUpdated(notification, $event)"/> <XNotification v-else :key="notification.id" :notification="notification" :with-time="true" :full="true" class="_panel notification"/> </XList> </template> @@ -62,12 +62,11 @@ const onNotification = (notification) => { } }; -const noteUpdated = (oldValue, newValue) => { - const i = pagingComponent.value.items.findIndex(n => n.note === oldValue); - pagingComponent.value.items[i] = { - ...pagingComponent.value.items[i], - note: newValue, - }; +const noteUpdated = (item, note) => { + pagingComponent.value?.updateItem(item.id, old => ({ + ...old, + note: note, + })); }; onMounted(() => { diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue index ab55b3c5bf..38e1e96041 100644 --- a/packages/client/src/components/ui/pagination.vue +++ b/packages/client/src/components/ui/pagination.vue @@ -205,7 +205,6 @@ const prepend = (item) => { // TODO } else { const isTop = isBackTop.value || (document.body.contains(rootEl.value) && isTopVisible(rootEl.value)); - console.log(item, top); if (isTop) { // Prepend the item @@ -236,7 +235,15 @@ const append = (item) => { items.value.push(item); }; -watch(props.pagination.params, init, { deep: true }); +const updateItem = (id, replacer: (item: any) => any): void => { + const i = items.value.findIndex(item => item.id === id); + items.value[i] = replacer(items.value[i]); +}; + +if (props.pagination.params && isRef(props.pagination.params)) { + watch(props.pagination.params, init, { deep: true }); +} + watch(queue, (a, b) => { if (a.length === 0 && b.length === 0) return; emit('queue', queue.value.length); @@ -253,11 +260,11 @@ onDeactivated(() => { }); defineExpose({ - items, reload, fetchMoreAhead, prepend, append, + updateItem, }); </script> diff --git a/packages/client/src/pages/favorites.vue b/packages/client/src/pages/favorites.vue index 588b0fa66c..f530da2a19 100644 --- a/packages/client/src/pages/favorites.vue +++ b/packages/client/src/pages/favorites.vue @@ -1,19 +1,42 @@ <template> <MkSpacer :content-max="800"> - <XNotes :pagination="pagination" :detail="true" :prop="'note'"/> + <MkPagination ref="pagingComponent" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noNotes }}</div> + </div> + </template> + + <template #default="{ items }"> + <XList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false"> + <XNote :key="item.id" :note="item.note" :class="$style.note" @update:note="noteUpdated(item, $event)"/> + </XList> + </template> + </MkPagination> </MkSpacer> </template> <script lang="ts" setup> -import XNotes from '@/components/notes.vue'; +import { ref } from 'vue'; +import MkPagination from '@/components/ui/pagination.vue'; +import XNote from '@/components/note.vue'; +import XList from '@/components/date-separated-list.vue'; import * as symbols from '@/symbols'; import { i18n } from '@/i18n'; const pagination = { - endpoint: 'i/favorites', + endpoint: 'i/favorites' as const, limit: 10, - params: () => ({ - }), +}; + +const pagingComponent = ref<InstanceType<typeof MkPagination>>(); + +const noteUpdated = (item, note) => { + pagingComponent.value?.updateItem(item.id, old => ({ + ...old, + note: note, + })); }; defineExpose({ @@ -24,3 +47,10 @@ defineExpose({ }, }); </script> + +<style lang="scss" module> +.note { + background: var(--panel); + border-radius: var(--radius); +} +</style> \ No newline at end of file From 186a9e3b41c31cfbcf66e9d5c4716bb67d91ffe1 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Mon, 10 Jan 2022 01:00:50 +0900 Subject: [PATCH 07/81] wip: refactor(client): migrate paging components to composition api --- .../client/src/components/ui/pagination.vue | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue index 38e1e96041..351676fbf9 100644 --- a/packages/client/src/components/ui/pagination.vue +++ b/packages/client/src/components/ui/pagination.vue @@ -49,6 +49,8 @@ export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints> * items 配列の中身を逆順にする(新しい方が最後) */ reversed?: boolean; + + offsetMode?: boolean; }; const props = withDefaults(defineProps<{ @@ -63,9 +65,11 @@ const emit = defineEmits<{ (e: 'queue', count: number): void; }>(); +type Item = { id: string; [another: string]: unknown; }; + const rootEl = ref<HTMLElement>(); -const items = ref([]); -const queue = ref([]); +const items = ref<Item[]>([]); +const queue = ref<Item[]>([]); const offset = ref(0); const fetching = ref(true); const moreFetching = ref(false); @@ -76,7 +80,7 @@ const isBackTop = ref(false); const empty = computed(() => items.value.length === 0 && !fetching.value && inited.value); const error = computed(() => !fetching.value && !inited.value); -const init = async () => { +const init = async (): Promise<void> => { queue.value = []; fetching.value = true; const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; @@ -109,12 +113,12 @@ const init = async () => { }); }; -const reload = () => { +const reload = (): void => { items.value = []; init(); }; -const fetchMore = async () => { +const fetchMore = async (): Promise<void> => { if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return; moreFetching.value = true; backed.value = true; @@ -152,7 +156,7 @@ const fetchMore = async () => { }); }; -const fetchMoreAhead = async () => { +const fetchMoreAhead = async (): Promise<void> => { if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return; moreFetching.value = true; const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; @@ -183,9 +187,13 @@ const fetchMoreAhead = async () => { }); }; -const prepend = (item) => { +const prepend = (item: Item): void => { + if (rootEl.value == null) return; + if (props.pagination.reversed) { const container = getScrollContainer(rootEl.value); + if (container == null) return; // TODO? + const pos = getScrollPosition(rootEl.value); const viewHeight = container.clientHeight; const height = container.scrollHeight; @@ -231,11 +239,11 @@ const prepend = (item) => { } }; -const append = (item) => { +const append = (item: Item): void => { items.value.push(item); }; -const updateItem = (id, replacer: (item: any) => any): void => { +const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => { const i = items.value.findIndex(item => item.id === id); items.value[i] = replacer(items.value[i]); }; From 06125e682004cdc7a110912f8926cf11dc4c6352 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Mon, 10 Jan 2022 03:30:35 +0900 Subject: [PATCH 08/81] wip: refactor(client): migrate paging components to composition api --- packages/client/src/pages/admin/abuses.vue | 20 ++----------- packages/client/src/pages/admin/files.vue | 20 +++---------- packages/client/src/pages/admin/users.vue | 18 ++---------- packages/client/src/pages/channel.vue | 4 +-- packages/client/src/pages/clip.vue | 4 +-- packages/client/src/pages/federation.vue | 6 ++-- packages/client/src/pages/gallery/post.vue | 4 +-- packages/client/src/pages/page.vue | 4 +-- packages/client/src/pages/search.vue | 10 ++----- packages/client/src/pages/tag.vue | 12 ++------ .../client/src/pages/user/follow-list.vue | 29 +++++++++---------- packages/client/src/pages/user/gallery.vue | 12 ++------ packages/client/src/pages/user/pages.vue | 12 ++------ packages/client/src/pages/user/reactions.vue | 12 ++------ packages/client/src/ui/deck/direct-column.vue | 6 ++-- 15 files changed, 51 insertions(+), 122 deletions(-) diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue index 8df20097b3..04257f86ce 100644 --- a/packages/client/src/pages/admin/abuses.vue +++ b/packages/client/src/pages/admin/abuses.vue @@ -62,7 +62,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; @@ -97,29 +97,15 @@ export default defineComponent({ pagination: { endpoint: 'admin/abuse-user-reports', limit: 10, - params: () => ({ + params: computed(() => ({ state: this.state, reporterOrigin: this.reporterOrigin, targetUserOrigin: this.targetUserOrigin, - }), + })), }, } }, - watch: { - state() { - this.$refs.reports.reload(); - }, - - reporterOrigin() { - this.$refs.reports.reload(); - }, - - targetUserOrigin() { - this.$refs.reports.reload(); - }, - }, - mounted() { this.$emit('info', this[symbols.PAGE_INFO]); }, diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue index 032e394a66..f2027fe8ac 100644 --- a/packages/client/src/pages/admin/files.vue +++ b/packages/client/src/pages/admin/files.vue @@ -55,7 +55,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -97,27 +97,15 @@ export default defineComponent({ pagination: { endpoint: 'admin/drive/files', limit: 10, - params: () => ({ + params: computed(() => ({ type: (this.type && this.type !== '') ? this.type : null, origin: this.origin, - hostname: (this.hostname && this.hostname !== '') ? this.hostname : null, - }), + hostname: (this.searchHost && this.searchHost !== '') ? this.searchHost : null, + })), }, } }, - watch: { - type() { - this.$refs.files.reload(); - }, - origin() { - this.$refs.files.reload(); - }, - searchHost() { - this.$refs.files.reload(); - }, - }, - mounted() { this.$emit('info', this[symbols.PAGE_INFO]); }, diff --git a/packages/client/src/pages/admin/users.vue b/packages/client/src/pages/admin/users.vue index e7a3437167..33d6fa4fd7 100644 --- a/packages/client/src/pages/admin/users.vue +++ b/packages/client/src/pages/admin/users.vue @@ -62,7 +62,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -112,30 +112,18 @@ export default defineComponent({ pagination: { endpoint: 'admin/show-users', limit: 10, - params: () => ({ + params: computed(() => ({ sort: this.sort, state: this.state, origin: this.origin, username: this.searchUsername, hostname: this.searchHost, - }), + })), offsetMode: true }, } }, - watch: { - sort() { - this.$refs.users.reload(); - }, - state() { - this.$refs.users.reload(); - }, - origin() { - this.$refs.users.reload(); - }, - }, - async mounted() { this.$emit('info', this[symbols.PAGE_INFO]); }, diff --git a/packages/client/src/pages/channel.vue b/packages/client/src/pages/channel.vue index 67ab2d8981..598e173d81 100644 --- a/packages/client/src/pages/channel.vue +++ b/packages/client/src/pages/channel.vue @@ -69,9 +69,9 @@ export default defineComponent({ pagination: { endpoint: 'channels/timeline', limit: 10, - params: () => ({ + params: computed(() => ({ channelId: this.channelId, - }) + })) }, }; }, diff --git a/packages/client/src/pages/clip.vue b/packages/client/src/pages/clip.vue index 077a6ac8b5..b375856803 100644 --- a/packages/client/src/pages/clip.vue +++ b/packages/client/src/pages/clip.vue @@ -52,9 +52,9 @@ export default defineComponent({ pagination: { endpoint: 'clips/notes', limit: 10, - params: () => ({ + params: computed(() => ({ clipId: this.clipId, - }) + })) }, }; }, diff --git a/packages/client/src/pages/federation.vue b/packages/client/src/pages/federation.vue index a467c5eeb8..9815e68986 100644 --- a/packages/client/src/pages/federation.vue +++ b/packages/client/src/pages/federation.vue @@ -96,7 +96,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -130,7 +130,7 @@ export default defineComponent({ endpoint: 'federation/instances', limit: 10, offsetMode: true, - params: () => ({ + params: computed(() => ({ sort: this.sort, host: this.host != '' ? this.host : null, ...( @@ -141,7 +141,7 @@ export default defineComponent({ this.state === 'blocked' ? { blocked: true } : this.state === 'notResponding' ? { notResponding: true } : {}) - }) + })) }, } }, diff --git a/packages/client/src/pages/gallery/post.vue b/packages/client/src/pages/gallery/post.vue index 096947e6f8..9d769deca0 100644 --- a/packages/client/src/pages/gallery/post.vue +++ b/packages/client/src/pages/gallery/post.vue @@ -95,9 +95,9 @@ export default defineComponent({ otherPostsPagination: { endpoint: 'users/gallery/posts', limit: 6, - params: () => ({ + params: computed(() => ({ userId: this.post.user.id - }) + })), }, post: null, error: null, diff --git a/packages/client/src/pages/page.vue b/packages/client/src/pages/page.vue index 3a4803c3a3..5cb3948f1c 100644 --- a/packages/client/src/pages/page.vue +++ b/packages/client/src/pages/page.vue @@ -108,9 +108,9 @@ export default defineComponent({ otherPostsPagination: { endpoint: 'users/pages', limit: 6, - params: () => ({ + params: computed(() => ({ userId: this.page.user.id - }) + })), }, }; }, diff --git a/packages/client/src/pages/search.vue b/packages/client/src/pages/search.vue index 85d19bb255..771d0b557e 100644 --- a/packages/client/src/pages/search.vue +++ b/packages/client/src/pages/search.vue @@ -25,18 +25,12 @@ export default defineComponent({ pagination: { endpoint: 'notes/search', limit: 10, - params: () => ({ + params: computed(() => ({ query: this.$route.query.q, channelId: this.$route.query.channel, - }) + })) }, }; }, - - watch: { - $route() { - (this.$refs.notes as any).reload(); - } - }, }); </script> diff --git a/packages/client/src/pages/tag.vue b/packages/client/src/pages/tag.vue index a0c8367849..a9497ae801 100644 --- a/packages/client/src/pages/tag.vue +++ b/packages/client/src/pages/tag.vue @@ -5,7 +5,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import XNotes from '@/components/notes.vue'; import * as symbols from '@/symbols'; @@ -30,17 +30,11 @@ export default defineComponent({ pagination: { endpoint: 'notes/search-by-tag', limit: 10, - params: () => ({ + params: computed(() => ({ tag: this.tag, - }) + })) }, }; }, - - watch: { - tag() { - (this.$refs.notes as any).reload(); - } - }, }); </script> diff --git a/packages/client/src/pages/user/follow-list.vue b/packages/client/src/pages/user/follow-list.vue index 9fb8943fb8..e12ea477ca 100644 --- a/packages/client/src/pages/user/follow-list.vue +++ b/packages/client/src/pages/user/follow-list.vue @@ -1,6 +1,6 @@ <template> <div> - <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="mk-following-or-followers"> + <MkPagination v-slot="{items}" ref="list" :pagination="type === 'following' ? followingPagination : followersPagination" class="mk-following-or-followers"> <div class="users _isolated"> <MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" class="user" :user="user"/> </div> @@ -9,7 +9,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkUserInfo from '@/components/user-info.vue'; import MkPagination from '@/components/ui/pagination.vue'; @@ -32,25 +32,22 @@ export default defineComponent({ data() { return { - pagination: { - endpoint: () => this.type === 'following' ? 'users/following' : 'users/followers', + followingPagination: { + endpoint: 'users/following', limit: 20, - params: { + params: computed(() => ({ userId: this.user.id, - } + })), + }, + followersPagination: { + endpoint: 'users/followers', + limit: 20, + params: computed(() => ({ + userId: this.user.id, + })), }, }; }, - - watch: { - type() { - this.$refs.list.reload(); - }, - - user() { - this.$refs.list.reload(); - } - } }); </script> diff --git a/packages/client/src/pages/user/gallery.vue b/packages/client/src/pages/user/gallery.vue index 860aa9f44f..88f0604f1f 100644 --- a/packages/client/src/pages/user/gallery.vue +++ b/packages/client/src/pages/user/gallery.vue @@ -9,7 +9,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkGalleryPostPreview from '@/components/gallery-post-preview.vue'; import MkPagination from '@/components/ui/pagination.vue'; @@ -31,18 +31,12 @@ export default defineComponent({ pagination: { endpoint: 'users/gallery/posts', limit: 6, - params: () => ({ + params: computed(() => ({ userId: this.user.id - }) + })), }, }; }, - - watch: { - user() { - this.$refs.list.reload(); - } - } }); </script> diff --git a/packages/client/src/pages/user/pages.vue b/packages/client/src/pages/user/pages.vue index 40d1fe3842..3075dd5729 100644 --- a/packages/client/src/pages/user/pages.vue +++ b/packages/client/src/pages/user/pages.vue @@ -7,7 +7,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkPagePreview from '@/components/page-preview.vue'; import MkPagination from '@/components/ui/pagination.vue'; @@ -29,18 +29,12 @@ export default defineComponent({ pagination: { endpoint: 'users/pages', limit: 20, - params: { + params: computed(() => ({ userId: this.user.id, - } + })), }, }; }, - - watch: { - user() { - this.$refs.list.reload(); - } - } }); </script> diff --git a/packages/client/src/pages/user/reactions.vue b/packages/client/src/pages/user/reactions.vue index 69c27de55b..f51f6669c3 100644 --- a/packages/client/src/pages/user/reactions.vue +++ b/packages/client/src/pages/user/reactions.vue @@ -14,7 +14,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkNote from '@/components/note.vue'; import MkReactionIcon from '@/components/reaction-icon.vue'; @@ -38,18 +38,12 @@ export default defineComponent({ pagination: { endpoint: 'users/reactions', limit: 20, - params: { + params: computed(() => ({ userId: this.user.id, - } + })), }, }; }, - - watch: { - user() { - this.$refs.list.reload(); - } - }, }); </script> diff --git a/packages/client/src/ui/deck/direct-column.vue b/packages/client/src/ui/deck/direct-column.vue index 6ef733dfd0..4206b09b97 100644 --- a/packages/client/src/ui/deck/direct-column.vue +++ b/packages/client/src/ui/deck/direct-column.vue @@ -7,7 +7,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import XColumn from './column.vue'; import XNotes from '@/components/notes.vue'; import * as os from '@/os'; @@ -34,9 +34,9 @@ export default defineComponent({ pagination: { endpoint: 'notes/mentions', limit: 10, - params: () => ({ + params: computed(() => ({ visibility: 'specified' - }) + })), }, } }, From f2cbd26a85d5170d487abb35d626931d6ba9f93b Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Mon, 10 Jan 2022 20:17:38 +0900 Subject: [PATCH 09/81] fix --- packages/client/src/components/ui/pagination.vue | 1 + packages/client/src/pages/admin/users.vue | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue index 351676fbf9..d4451e27cb 100644 --- a/packages/client/src/components/ui/pagination.vue +++ b/packages/client/src/components/ui/pagination.vue @@ -268,6 +268,7 @@ onDeactivated(() => { }); defineExpose({ + items, reload, fetchMoreAhead, prepend, diff --git a/packages/client/src/pages/admin/users.vue b/packages/client/src/pages/admin/users.vue index 33d6fa4fd7..a094227ae9 100644 --- a/packages/client/src/pages/admin/users.vue +++ b/packages/client/src/pages/admin/users.vue @@ -30,7 +30,7 @@ <template #prefix>@</template> <template #label>{{ $ts.username }}</template> </MkInput> - <MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" :disabled="pagination.params().origin === 'local'" @update:modelValue="$refs.users.reload()"> + <MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" :disabled="pagination.params.origin === 'local'" @update:modelValue="$refs.users.reload()"> <template #prefix>@</template> <template #label>{{ $ts.host }}</template> </MkInput> From 8855a5fffb70b03611e664acdbadbff17b9d1dc4 Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Tue, 11 Jan 2022 00:05:18 +0900 Subject: [PATCH 10/81] =?UTF-8?q?refactor:=20Composition=20API=E3=81=B8?= =?UTF-8?q?=E7=A7=BB=E8=A1=8C=20(#8121)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * components/abuse-report-window.vue * use <script setup> * :v: * components/analog-clock.vue * wip components/autocomplete.vue * :v: * :v: * fix * wip components/captcha.vue * clean up * components/channel-follow-button * components/channel-preview.vue * components/core-core.vue * components/code.vue * wip components/date-separated-list.vue * fix * fix autocomplete.vue * :v: * remove global property * use <script setup> * components/dialog.vue * clena up * fix dialog.vue * Resolve https://github.com/misskey-dev/misskey/pull/8121#discussion_r781250966 --- .../src/components/abuse-report-window.vue | 76 +-- .../client/src/components/analog-clock.vue | 148 ++--- .../client/src/components/autocomplete.vue | 541 +++++++++--------- packages/client/src/components/captcha.vue | 176 +++--- .../src/components/channel-follow-button.vue | 84 ++- .../client/src/components/channel-preview.vue | 41 +- packages/client/src/components/code-core.vue | 36 +- packages/client/src/components/code.vue | 30 +- packages/client/src/components/cw-button.vue | 2 +- .../src/components/date-separated-list.vue | 53 +- packages/client/src/components/dialog.vue | 183 +++--- 11 files changed, 614 insertions(+), 756 deletions(-) diff --git a/packages/client/src/components/abuse-report-window.vue b/packages/client/src/components/abuse-report-window.vue index 6b07639f6d..cd04f62bca 100644 --- a/packages/client/src/components/abuse-report-window.vue +++ b/packages/client/src/components/abuse-report-window.vue @@ -1,8 +1,8 @@ <template> -<XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="$emit('closed')"> +<XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')"> <template #header> <i class="fas fa-exclamation-circle" style="margin-right: 0.5em;"></i> - <I18n :src="$ts.reportAbuseOf" tag="span"> + <I18n :src="i18n.locale.reportAbuseOf" tag="span"> <template #name> <b><MkAcct :user="user"/></b> </template> @@ -11,65 +11,51 @@ <div class="dpvffvvy _monolithic_"> <div class="_section"> <MkTextarea v-model="comment"> - <template #label>{{ $ts.details }}</template> - <template #caption>{{ $ts.fillAbuseReportDescription }}</template> + <template #label>{{ i18n.locale.details }}</template> + <template #caption>{{ i18n.locale.fillAbuseReportDescription }}</template> </MkTextarea> </div> <div class="_section"> - <MkButton primary full :disabled="comment.length === 0" @click="send">{{ $ts.send }}</MkButton> + <MkButton primary full :disabled="comment.length === 0" @click="send">{{ i18n.locale.send }}</MkButton> </div> </div> </XWindow> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script setup lang="ts"> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; import XWindow from '@/components/ui/window.vue'; import MkTextarea from '@/components/form/textarea.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XWindow, - MkTextarea, - MkButton, - }, +const props = defineProps<{ + user: Misskey.entities.User; + initialComment?: string; +}>(); - props: { - user: { - type: Object, - required: true, - }, - initialComment: { - type: String, - required: false, - }, - }, +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); - emits: ['closed'], +const window = ref<InstanceType<typeof XWindow>>(); +const comment = ref(props.initialComment || ''); - data() { - return { - comment: this.initialComment || '', - }; - }, - - methods: { - send() { - os.apiWithDialog('users/report-abuse', { - userId: this.user.id, - comment: this.comment, - }, undefined, res => { - os.alert({ - type: 'success', - text: this.$ts.abuseReported - }); - this.$refs.window.close(); - }); - } - }, -}); +function send() { + os.apiWithDialog('users/report-abuse', { + userId: props.user.id, + comment: comment.value, + }, undefined).then(res => { + os.alert({ + type: 'success', + text: i18n.locale.abuseReported + }); + window.value?.close(); + emit('closed'); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/analog-clock.vue b/packages/client/src/components/analog-clock.vue index 450488b198..9ca511b6e9 100644 --- a/packages/client/src/components/analog-clock.vue +++ b/packages/client/src/components/analog-clock.vue @@ -40,106 +40,64 @@ </svg> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref, computed, onMounted, onBeforeUnmount } from 'vue'; import * as tinycolor from 'tinycolor2'; -export default defineComponent({ - props: { - thickness: { - type: Number, - default: 0.1 - } - }, +withDefaults(defineProps<{ + thickness: number; +}>(), { + thickness: 0.1, +}); - data() { - return { - now: new Date(), - enabled: true, +const now = ref(new Date()); +const enabled = ref(true); +const graduationsPadding = ref(0.5); +const handsPadding = ref(1); +const handsTailLength = ref(0.7); +const hHandLengthRatio = ref(0.75); +const mHandLengthRatio = ref(1); +const sHandLengthRatio = ref(1); +const computedStyle = getComputedStyle(document.documentElement); - graduationsPadding: 0.5, - handsPadding: 1, - handsTailLength: 0.7, - hHandLengthRatio: 0.75, - mHandLengthRatio: 1, - sHandLengthRatio: 1, - - computedStyle: getComputedStyle(document.documentElement) - }; - }, - - computed: { - dark(): boolean { - return tinycolor(this.computedStyle.getPropertyValue('--bg')).isDark(); - }, - - majorGraduationColor(): string { - return this.dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)'; - }, - minorGraduationColor(): string { - return this.dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; - }, - - sHandColor(): string { - return this.dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)'; - }, - mHandColor(): string { - return tinycolor(this.computedStyle.getPropertyValue('--fg')).toHexString(); - }, - hHandColor(): string { - return tinycolor(this.computedStyle.getPropertyValue('--accent')).toHexString(); - }, - - s(): number { - return this.now.getSeconds(); - }, - m(): number { - return this.now.getMinutes(); - }, - h(): number { - return this.now.getHours(); - }, - - hAngle(): number { - return Math.PI * (this.h % 12 + (this.m + this.s / 60) / 60) / 6; - }, - mAngle(): number { - return Math.PI * (this.m + this.s / 60) / 30; - }, - sAngle(): number { - return Math.PI * this.s / 30; - }, - - graduations(): any { - const angles = []; - for (let i = 0; i < 60; i++) { - const angle = Math.PI * i / 30; - angles.push(angle); - } - - return angles; - } - }, - - mounted() { - const update = () => { - if (this.enabled) { - this.tick(); - setTimeout(update, 1000); - } - }; - update(); - }, - - beforeUnmount() { - this.enabled = false; - }, - - methods: { - tick() { - this.now = new Date(); - } +const dark = computed(() => tinycolor(computedStyle.getPropertyValue('--bg')).isDark()); +const majorGraduationColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)'); +const minorGraduationColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'); +const sHandColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)'); +const mHandColor = computed(() => tinycolor(computedStyle.getPropertyValue('--fg')).toHexString()); +const hHandColor = computed(() => tinycolor(computedStyle.getPropertyValue('--accent')).toHexString()); +const s = computed(() => now.value.getSeconds()); +const m = computed(() => now.value.getMinutes()); +const h = computed(() => now.value.getHours()); +const hAngle = computed(() => Math.PI * (h.value % 12 + (m.value + s.value / 60) / 60) / 6); +const mAngle = computed(() => Math.PI * (m.value + s.value / 60) / 30); +const sAngle = computed(() => Math.PI * s.value / 30); +const graduations = computed(() => { + const angles: number[] = []; + for (let i = 0; i < 60; i++) { + const angle = Math.PI * i / 30; + angles.push(angle); } + + return angles; +}); + +function tick() { + now.value = new Date(); +} + +onMounted(() => { + const update = () => { + if (enabled.value) { + tick(); + setTimeout(update, 1000); + } + }; + update(); +}); + +onBeforeUnmount(() => { + enabled.value = false; }); </script> diff --git a/packages/client/src/components/autocomplete.vue b/packages/client/src/components/autocomplete.vue index 30be2ac741..7ba83b7cb1 100644 --- a/packages/client/src/components/autocomplete.vue +++ b/packages/client/src/components/autocomplete.vue @@ -1,5 +1,5 @@ <template> -<div class="swhvrteh _popup _shadow" :style="{ zIndex }" @contextmenu.prevent="() => {}"> +<div ref="rootEl" class="swhvrteh _popup _shadow" :style="{ zIndex }" @contextmenu.prevent="() => {}"> <ol v-if="type === 'user'" ref="suggests" class="users"> <li v-for="user in users" tabindex="-1" class="user" @click="complete(type, user)" @keydown="onKeydown"> <img class="avatar" :src="user.avatarUrl"/> @@ -8,7 +8,7 @@ </span> <span class="username">@{{ acct(user) }}</span> </li> - <li tabindex="-1" class="choose" @click="chooseUser()" @keydown="onKeydown">{{ $ts.selectUser }}</li> + <li tabindex="-1" class="choose" @click="chooseUser()" @keydown="onKeydown">{{ i18n.locale.selectUser }}</li> </ol> <ol v-else-if="hashtags.length > 0" ref="suggests" class="hashtags"> <li v-for="hashtag in hashtags" tabindex="-1" @click="complete(type, hashtag)" @keydown="onKeydown"> @@ -17,8 +17,8 @@ </ol> <ol v-else-if="emojis.length > 0" ref="suggests" class="emojis"> <li v-for="emoji in emojis" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown"> - <span v-if="emoji.isCustomEmoji" class="emoji"><img :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span> - <span v-else-if="!$store.state.useOsNativeEmojis" class="emoji"><img :src="emoji.url" :alt="emoji.emoji"/></span> + <span v-if="emoji.isCustomEmoji" class="emoji"><img :src="defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span> + <span v-else-if="!defaultStore.state.useOsNativeEmojis" class="emoji"><img :src="emoji.url" :alt="emoji.emoji"/></span> <span v-else class="emoji">{{ emoji.emoji }}</span> <span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span> <span v-if="emoji.aliasOf" class="alias">({{ emoji.aliasOf }})</span> @@ -33,15 +33,17 @@ </template> <script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import { emojilist } from '@/scripts/emojilist'; +import { markRaw, ref, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; import contains from '@/scripts/contains'; -import { twemojiSvgBase } from '@/scripts/twemoji-base'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import { acct } from '@/filters/user'; import * as os from '@/os'; -import { instance } from '@/instance'; import { MFM_TAGS } from '@/scripts/mfm-tags'; +import { defaultStore } from '@/store'; +import { emojilist } from '@/scripts/emojilist'; +import { instance } from '@/instance'; +import { twemojiSvgBase } from '@/scripts/twemoji-base'; +import { i18n } from '@/i18n'; type EmojiDef = { emoji: string; @@ -54,16 +56,14 @@ type EmojiDef = { const lib = emojilist.filter(x => x.category !== 'flags'); const char2file = (char: string) => { - let codes = Array.from(char).map(x => x.codePointAt(0).toString(16)); + let codes = Array.from(char).map(x => x.codePointAt(0)?.toString(16)); if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f'); - codes = codes.filter(x => x && x.length); - return codes.join('-'); + return codes.filter(x => x && x.length).join('-'); }; const emjdb: EmojiDef[] = lib.map(x => ({ emoji: x.char, name: x.name, - aliasOf: null, url: `${twemojiSvgBase}/${char2file(x.char)}.svg` })); @@ -112,291 +112,270 @@ emojiDefinitions.sort((a, b) => a.name.length - b.name.length); const emojiDb = markRaw(emojiDefinitions.concat(emjdb)); //#endregion -export default defineComponent({ - props: { - type: { - type: String, - required: true, - }, +export default { + emojiDb, + emojiDefinitions, + emojilist, + customEmojis, +}; +</script> - q: { - type: String, - required: false, - }, +<script lang="ts" setup> +const props = defineProps<{ + type: string; + q: string | null; + textarea: HTMLTextAreaElement; + close: () => void; + x: number; + y: number; +}>(); - textarea: { - type: HTMLTextAreaElement, - required: true, - }, +const emit = defineEmits<{ + (e: 'done', v: { type: string; value: any }): void; + (e: 'closed'): void; +}>(); - close: { - type: Function, - required: true, - }, +const suggests = ref<Element>(); +const rootEl = ref<HTMLDivElement>(); - x: { - type: Number, - required: true, - }, +const fetching = ref(true); +const users = ref<any[]>([]); +const hashtags = ref<any[]>([]); +const emojis = ref<(EmojiDef)[]>([]); +const items = ref<Element[] | HTMLCollection>([]); +const mfmTags = ref<string[]>([]); +const select = ref(-1); +const zIndex = os.claimZIndex('high'); - y: { - type: Number, - required: true, - }, - }, +function complete(type: string, value: any) { + emit('done', { type, value }); + emit('closed'); + if (type === 'emoji') { + let recents = defaultStore.state.recentlyUsedEmojis; + recents = recents.filter((e: any) => e !== value); + recents.unshift(value); + defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32)); + } +} - emits: ['done', 'closed'], +function setPosition() { + if (!rootEl.value) return; + if (props.x + rootEl.value.offsetWidth > window.innerWidth) { + rootEl.value.style.left = (window.innerWidth - rootEl.value.offsetWidth) + 'px'; + } else { + rootEl.value.style.left = `${props.x}px`; + } + if (props.y + rootEl.value.offsetHeight > window.innerHeight) { + rootEl.value.style.top = (props.y - rootEl.value.offsetHeight) + 'px'; + rootEl.value.style.marginTop = '0'; + } else { + rootEl.value.style.top = props.y + 'px'; + rootEl.value.style.marginTop = 'calc(1em + 8px)'; + } +} - data() { - return { - getStaticImageUrl, - fetching: true, - users: [], - hashtags: [], - emojis: [], - items: [], - mfmTags: [], - select: -1, - zIndex: os.claimZIndex('high'), +function exec() { + select.value = -1; + if (suggests.value) { + for (const el of Array.from(items.value)) { + el.removeAttribute('data-selected'); } - }, - - updated() { - this.setPosition(); - this.items = (this.$refs.suggests as Element | undefined)?.children || []; - }, - - mounted() { - this.setPosition(); - - this.textarea.addEventListener('keydown', this.onKeydown); - - for (const el of Array.from(document.querySelectorAll('body *'))) { - el.addEventListener('mousedown', this.onMousedown); + } + if (props.type === 'user') { + if (!props.q) { + users.value = []; + fetching.value = false; + return; } - this.$nextTick(() => { - this.exec(); + const cacheKey = `autocomplete:user:${props.q}`; + const cache = sessionStorage.getItem(cacheKey); - this.$watch('q', () => { - this.$nextTick(() => { - this.exec(); + if (cache) { + const users = JSON.parse(cache); + users.value = users; + fetching.value = false; + } else { + os.api('users/search-by-username-and-host', { + username: props.q, + limit: 10, + detail: false + }).then(searchedUsers => { + users.value = searchedUsers as any[]; + fetching.value = false; + // キャッシュ + sessionStorage.setItem(cacheKey, JSON.stringify(searchedUsers)); + }); + } + } else if (props.type === 'hashtag') { + if (!props.q || props.q == '') { + hashtags.value = JSON.parse(localStorage.getItem('hashtags') || '[]'); + fetching.value = false; + } else { + const cacheKey = `autocomplete:hashtag:${props.q}`; + const cache = sessionStorage.getItem(cacheKey); + if (cache) { + const hashtags = JSON.parse(cache); + hashtags.value = hashtags; + fetching.value = false; + } else { + os.api('hashtags/search', { + query: props.q, + limit: 30 + }).then(searchedHashtags => { + hashtags.value = searchedHashtags as any[]; + fetching.value = false; + // キャッシュ + sessionStorage.setItem(cacheKey, JSON.stringify(searchedHashtags)); }); + } + } + } else if (props.type === 'emoji') { + if (!props.q || props.q == '') { + // 最近使った絵文字をサジェスト + emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x) as EmojiDef[]; + return; + } + + const matched: EmojiDef[] = []; + const max = 30; + + emojiDb.some(x => { + if (x.name.startsWith(props.q || '') && !x.aliasOf && !matched.some(y => y.emoji == x.emoji)) matched.push(x); + return matched.length == max; + }); + + if (matched.length < max) { + emojiDb.some(x => { + if (x.name.startsWith(props.q || '') && !matched.some(y => y.emoji == x.emoji)) matched.push(x); + return matched.length == max; + }); + } + + if (matched.length < max) { + emojiDb.some(x => { + if (x.name.includes(props.q || '') && !matched.some(y => y.emoji == x.emoji)) matched.push(x); + return matched.length == max; + }); + } + + emojis.value = matched; + } else if (props.type === 'mfmTag') { + if (!props.q || props.q == '') { + mfmTags.value = MFM_TAGS; + return; + } + + mfmTags.value = MFM_TAGS.filter(tag => tag.startsWith(props.q || '')); + } +} + +function onMousedown(e: Event) { + if (!contains(rootEl.value, e.target) && (rootEl.value != e.target)) props.close(); +} + +function onKeydown(e: KeyboardEvent) { + const cancel = () => { + e.preventDefault(); + e.stopPropagation(); + }; + + switch (e.key) { + case 'Enter': + if (select.value !== -1) { + cancel(); + (items.value[select.value] as any).click(); + } else { + props.close(); + } + break; + + case 'Escape': + cancel(); + props.close(); + break; + + case 'ArrowUp': + if (select.value !== -1) { + cancel(); + selectPrev(); + } else { + props.close(); + } + break; + + case 'Tab': + case 'ArrowDown': + cancel(); + selectNext(); + break; + + default: + e.stopPropagation(); + props.textarea.focus(); + } +} + +function selectNext() { + if (++select.value >= items.value.length) select.value = 0; + if (items.value.length === 0) select.value = -1; + applySelect(); +} + +function selectPrev() { + if (--select.value < 0) select.value = items.value.length - 1; + applySelect(); +} + +function applySelect() { + for (const el of Array.from(items.value)) { + el.removeAttribute('data-selected'); + } + + if (select.value !== -1) { + items.value[select.value].setAttribute('data-selected', 'true'); + (items.value[select.value] as any).focus(); + } +} + +function chooseUser() { + props.close(); + os.selectUser().then(user => { + complete('user', user); + props.textarea.focus(); + }); +} + +onUpdated(() => { + setPosition(); + items.value = suggests.value?.children || []; +}); + +onMounted(() => { + setPosition(); + + props.textarea.addEventListener('keydown', onKeydown); + + for (const el of Array.from(document.querySelectorAll('body *'))) { + el.addEventListener('mousedown', onMousedown); + } + + nextTick(() => { + exec(); + + watch(() => props.q, () => { + nextTick(() => { + exec(); }); }); - }, + }); +}); - beforeUnmount() { - this.textarea.removeEventListener('keydown', this.onKeydown); +onBeforeUnmount(() => { + props.textarea.removeEventListener('keydown', onKeydown); - for (const el of Array.from(document.querySelectorAll('body *'))) { - el.removeEventListener('mousedown', this.onMousedown); - } - }, - - methods: { - complete(type, value) { - this.$emit('done', { type, value }); - this.$emit('closed'); - - if (type === 'emoji') { - let recents = this.$store.state.recentlyUsedEmojis; - recents = recents.filter((e: any) => e !== value); - recents.unshift(value); - this.$store.set('recentlyUsedEmojis', recents.splice(0, 32)); - } - }, - - setPosition() { - if (this.x + this.$el.offsetWidth > window.innerWidth) { - this.$el.style.left = (window.innerWidth - this.$el.offsetWidth) + 'px'; - } else { - this.$el.style.left = this.x + 'px'; - } - - if (this.y + this.$el.offsetHeight > window.innerHeight) { - this.$el.style.top = (this.y - this.$el.offsetHeight) + 'px'; - this.$el.style.marginTop = '0'; - } else { - this.$el.style.top = this.y + 'px'; - this.$el.style.marginTop = 'calc(1em + 8px)'; - } - }, - - exec() { - this.select = -1; - if (this.$refs.suggests) { - for (const el of Array.from(this.items)) { - el.removeAttribute('data-selected'); - } - } - - if (this.type === 'user') { - if (this.q == null) { - this.users = []; - this.fetching = false; - return; - } - - const cacheKey = `autocomplete:user:${this.q}`; - const cache = sessionStorage.getItem(cacheKey); - if (cache) { - const users = JSON.parse(cache); - this.users = users; - this.fetching = false; - } else { - os.api('users/search-by-username-and-host', { - username: this.q, - limit: 10, - detail: false - }).then(users => { - this.users = users; - this.fetching = false; - - // キャッシュ - sessionStorage.setItem(cacheKey, JSON.stringify(users)); - }); - } - } else if (this.type === 'hashtag') { - if (this.q == null || this.q == '') { - this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]'); - this.fetching = false; - } else { - const cacheKey = `autocomplete:hashtag:${this.q}`; - const cache = sessionStorage.getItem(cacheKey); - if (cache) { - const hashtags = JSON.parse(cache); - this.hashtags = hashtags; - this.fetching = false; - } else { - os.api('hashtags/search', { - query: this.q, - limit: 30 - }).then(hashtags => { - this.hashtags = hashtags; - this.fetching = false; - - // キャッシュ - sessionStorage.setItem(cacheKey, JSON.stringify(hashtags)); - }); - } - } - } else if (this.type === 'emoji') { - if (this.q == null || this.q == '') { - // 最近使った絵文字をサジェスト - this.emojis = this.$store.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x != null); - return; - } - - const matched = []; - const max = 30; - - emojiDb.some(x => { - if (x.name.startsWith(this.q) && !x.aliasOf && !matched.some(y => y.emoji == x.emoji)) matched.push(x); - return matched.length == max; - }); - if (matched.length < max) { - emojiDb.some(x => { - if (x.name.startsWith(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x); - return matched.length == max; - }); - } - if (matched.length < max) { - emojiDb.some(x => { - if (x.name.includes(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x); - return matched.length == max; - }); - } - - this.emojis = matched; - } else if (this.type === 'mfmTag') { - if (this.q == null || this.q == '') { - this.mfmTags = MFM_TAGS; - return; - } - - this.mfmTags = MFM_TAGS.filter(tag => tag.startsWith(this.q)); - } - }, - - onMousedown(e) { - if (!contains(this.$el, e.target) && (this.$el != e.target)) this.close(); - }, - - onKeydown(e) { - const cancel = () => { - e.preventDefault(); - e.stopPropagation(); - }; - - switch (e.which) { - case 10: // [ENTER] - case 13: // [ENTER] - if (this.select !== -1) { - cancel(); - (this.items[this.select] as any).click(); - } else { - this.close(); - } - break; - - case 27: // [ESC] - cancel(); - this.close(); - break; - - case 38: // [↑] - if (this.select !== -1) { - cancel(); - this.selectPrev(); - } else { - this.close(); - } - break; - - case 9: // [TAB] - case 40: // [↓] - cancel(); - this.selectNext(); - break; - - default: - e.stopPropagation(); - this.textarea.focus(); - } - }, - - selectNext() { - if (++this.select >= this.items.length) this.select = 0; - if (this.items.length === 0) this.select = -1; - this.applySelect(); - }, - - selectPrev() { - if (--this.select < 0) this.select = this.items.length - 1; - this.applySelect(); - }, - - applySelect() { - for (const el of Array.from(this.items)) { - el.removeAttribute('data-selected'); - } - - if (this.select !== -1) { - this.items[this.select].setAttribute('data-selected', 'true'); - (this.items[this.select] as any).focus(); - } - }, - - chooseUser() { - this.close(); - os.selectUser().then(user => { - this.complete('user', user); - this.textarea.focus(); - }); - }, - - acct + for (const el of Array.from(document.querySelectorAll('body *'))) { + el.removeEventListener('mousedown', onMousedown); } }); </script> diff --git a/packages/client/src/components/captcha.vue b/packages/client/src/components/captcha.vue index baa922506e..2a4181255f 100644 --- a/packages/client/src/components/captcha.vue +++ b/packages/client/src/components/captcha.vue @@ -1,12 +1,14 @@ <template> <div> - <span v-if="!available">{{ $ts.waiting }}<MkEllipsis/></span> - <div ref="captcha"></div> + <span v-if="!available">{{ i18n.locale.waiting }}<MkEllipsis/></span> + <div ref="captchaEl"></div> </div> </template> -<script lang="ts"> -import { defineComponent, PropType } from 'vue'; +<script lang="ts" setup> +import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'; +import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; type Captcha = { render(container: string | Node, options: { @@ -14,7 +16,7 @@ type Captcha = { }): string; remove(id: string): void; execute(id: string): void; - reset(id: string): void; + reset(id?: string): void; getResponse(id: string): string; }; @@ -29,95 +31,87 @@ declare global { } } -export default defineComponent({ - props: { - provider: { - type: String as PropType<CaptchaProvider>, - required: true, - }, - sitekey: { - type: String, - required: true, - }, - modelValue: { - type: String, - }, - }, +const props = defineProps<{ + provider: CaptchaProvider; + sitekey: string; + modelValue?: string | null; +}>(); - data() { - return { - available: false, - }; - }, +const emit = defineEmits<{ + (e: 'update:modelValue', v: string | null): void; +}>(); - computed: { - variable(): string { - switch (this.provider) { - case 'hcaptcha': return 'hcaptcha'; - case 'recaptcha': return 'grecaptcha'; - } - }, - loaded(): boolean { - return !!window[this.variable]; - }, - src(): string { - const endpoint = ({ - hcaptcha: 'https://hcaptcha.com/1', - recaptcha: 'https://www.recaptcha.net/recaptcha', - } as Record<CaptchaProvider, string>)[this.provider]; +const available = ref(false); - return `${typeof endpoint === 'string' ? endpoint : 'about:invalid'}/api.js?render=explicit`; - }, - captcha(): Captcha { - return window[this.variable] || {} as unknown as Captcha; - }, - }, +const captchaEl = ref<HTMLDivElement | undefined>(); - created() { - if (this.loaded) { - this.available = true; - } else { - (document.getElementById(this.provider) || document.head.appendChild(Object.assign(document.createElement('script'), { - async: true, - id: this.provider, - src: this.src, - }))) - .addEventListener('load', () => this.available = true); - } - }, - - mounted() { - if (this.available) { - this.requestRender(); - } else { - this.$watch('available', this.requestRender); - } - }, - - beforeUnmount() { - this.reset(); - }, - - methods: { - reset() { - if (this.captcha?.reset) this.captcha.reset(); - }, - requestRender() { - if (this.captcha.render && this.$refs.captcha instanceof Element) { - this.captcha.render(this.$refs.captcha, { - sitekey: this.sitekey, - theme: this.$store.state.darkMode ? 'dark' : 'light', - callback: this.callback, - 'expired-callback': this.callback, - 'error-callback': this.callback, - }); - } else { - setTimeout(this.requestRender.bind(this), 1); - } - }, - callback(response?: string) { - this.$emit('update:modelValue', typeof response == 'string' ? response : null); - }, - }, +const variable = computed(() => { + switch (props.provider) { + case 'hcaptcha': return 'hcaptcha'; + case 'recaptcha': return 'grecaptcha'; + } }); + +const loaded = computed(() => !!window[variable.value]); + +const src = computed(() => { + const endpoint = ({ + hcaptcha: 'https://hcaptcha.com/1', + recaptcha: 'https://www.recaptcha.net/recaptcha', + } as Record<CaptchaProvider, string>)[props.provider]; + + return `${typeof endpoint === 'string' ? endpoint : 'about:invalid'}/api.js?render=explicit`; +}); + +const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha); + +if (loaded.value) { + available.value = true; +} else { + (document.getElementById(props.provider) || document.head.appendChild(Object.assign(document.createElement('script'), { + async: true, + id: props.provider, + src: src.value, + }))) + .addEventListener('load', () => available.value = true); +} + +function reset() { + if (captcha.value?.reset) captcha.value.reset(); +} + +function requestRender() { + if (captcha.value.render && captchaEl.value instanceof Element) { + captcha.value.render(captchaEl.value, { + sitekey: props.sitekey, + theme: defaultStore.state.darkMode ? 'dark' : 'light', + callback: callback, + 'expired-callback': callback, + 'error-callback': callback, + }); + } else { + setTimeout(requestRender, 1); + } +} + +function callback(response?: string) { + emit('update:modelValue', typeof response == 'string' ? response : null); +} + +onMounted(() => { + if (available.value) { + requestRender(); + } else { + watch(available, requestRender); + } +}); + +onBeforeUnmount(() => { + reset(); +}); + +defineExpose({ + reset, +}); + </script> diff --git a/packages/client/src/components/channel-follow-button.vue b/packages/client/src/components/channel-follow-button.vue index abde7c8148..0ad5384cd5 100644 --- a/packages/client/src/components/channel-follow-button.vue +++ b/packages/client/src/components/channel-follow-button.vue @@ -6,66 +6,54 @@ > <template v-if="!wait"> <template v-if="isFollowing"> - <span v-if="full">{{ $ts.unfollow }}</span><i class="fas fa-minus"></i> + <span v-if="full">{{ i18n.locale.unfollow }}</span><i class="fas fa-minus"></i> </template> <template v-else> - <span v-if="full">{{ $ts.follow }}</span><i class="fas fa-plus"></i> + <span v-if="full">{{ i18n.locale.follow }}</span><i class="fas fa-plus"></i> </template> </template> <template v-else> - <span v-if="full">{{ $ts.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> + <span v-if="full">{{ i18n.locale.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> </template> </button> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - channel: { - type: Object, - required: true - }, - full: { - type: Boolean, - required: false, - default: false, - }, - }, - - data() { - return { - isFollowing: this.channel.isFollowing, - wait: false, - }; - }, - - methods: { - async onClick() { - this.wait = true; - - try { - if (this.isFollowing) { - await os.api('channels/unfollow', { - channelId: this.channel.id - }); - this.isFollowing = false; - } else { - await os.api('channels/follow', { - channelId: this.channel.id - }); - this.isFollowing = true; - } - } catch (e) { - console.error(e); - } finally { - this.wait = false; - } - } - } +const props = withDefaults(defineProps<{ + channel: Record<string, any>; + full?: boolean; +}>(), { + full: false, }); + +const isFollowing = ref<boolean>(props.channel.isFollowing); +const wait = ref(false); + +async function onClick() { + wait.value = true; + + try { + if (isFollowing.value) { + await os.api('channels/unfollow', { + channelId: props.channel.id + }); + isFollowing.value = false; + } else { + await os.api('channels/follow', { + channelId: props.channel.id + }); + isFollowing.value = true; + } + } catch (e) { + console.error(e); + } finally { + wait.value = false; + } +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/channel-preview.vue b/packages/client/src/components/channel-preview.vue index f2b6de97fd..8d135a192f 100644 --- a/packages/client/src/components/channel-preview.vue +++ b/packages/client/src/components/channel-preview.vue @@ -6,7 +6,7 @@ <div class="status"> <div> <i class="fas fa-users fa-fw"></i> - <I18n :src="$ts._channel.usersCount" tag="span" style="margin-left: 4px;"> + <I18n :src="i18n.locale._channel.usersCount" tag="span" style="margin-left: 4px;"> <template #n> <b>{{ channel.usersCount }}</b> </template> @@ -14,7 +14,7 @@ </div> <div> <i class="fas fa-pencil-alt fa-fw"></i> - <I18n :src="$ts._channel.notesCount" tag="span" style="margin-left: 4px;"> + <I18n :src="i18n.locale._channel.notesCount" tag="span" style="margin-left: 4px;"> <template #n> <b>{{ channel.notesCount }}</b> </template> @@ -27,37 +27,26 @@ </article> <footer> <span v-if="channel.lastNotedAt"> - {{ $ts.updatedAt }}: <MkTime :time="channel.lastNotedAt"/> + {{ i18n.locale.updatedAt }}: <MkTime :time="channel.lastNotedAt"/> </span> </footer> </MkA> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - channel: { - type: Object, - required: true - }, - }, +const props = defineProps<{ + channel: Record<string, any>; +}>(); - data() { - return { - }; - }, - - computed: { - bannerStyle() { - if (this.channel.bannerUrl) { - return { backgroundImage: `url(${this.channel.bannerUrl})` }; - } else { - return { backgroundColor: '#4c5e6d' }; - } - } - }, +const bannerStyle = computed(() => { + if (props.channel.bannerUrl) { + return { backgroundImage: `url(${props.channel.bannerUrl})` }; + } else { + return { backgroundColor: '#4c5e6d' }; + } }); </script> diff --git a/packages/client/src/components/code-core.vue b/packages/client/src/components/code-core.vue index b58484c2ac..45a38afe04 100644 --- a/packages/client/src/components/code-core.vue +++ b/packages/client/src/components/code-core.vue @@ -3,33 +3,17 @@ <pre v-else :class="`language-${prismLang}`"><code :class="`language-${prismLang}`" v-html="html"></code></pre> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import 'prismjs'; import 'prismjs/themes/prism-okaidia.css'; -export default defineComponent({ - props: { - code: { - type: String, - required: true - }, - lang: { - type: String, - required: false - }, - inline: { - type: Boolean, - required: false - } - }, - computed: { - prismLang() { - return Prism.languages[this.lang] ? this.lang : 'js'; - }, - html() { - return Prism.highlight(this.code, Prism.languages[this.prismLang], this.prismLang); - } - } -}); +const props = defineProps<{ + code: string; + lang?: string; + inline?: boolean; +}>(); + +const prismLang = computed(() => Prism.languages[props.lang] ? props.lang : 'js'); +const html = computed(() => Prism.highlight(props.code, Prism.languages[prismLang.value], prismLang.value)); </script> diff --git a/packages/client/src/components/code.vue b/packages/client/src/components/code.vue index f5d6c5673a..d6478fd2f8 100644 --- a/packages/client/src/components/code.vue +++ b/packages/client/src/components/code.vue @@ -2,26 +2,14 @@ <XCode :code="code" :lang="lang" :inline="inline"/> </template> -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent } from 'vue'; -export default defineComponent({ - components: { - XCode: defineAsyncComponent(() => import('./code-core.vue')) - }, - props: { - code: { - type: String, - required: true - }, - lang: { - type: String, - required: false - }, - inline: { - type: Boolean, - required: false - } - } -}); +defineProps<{ + code: string; + lang?: string; + inline?: boolean; +}>(); + +const XCode = defineAsyncComponent(() => import('./code-core.vue')); </script> diff --git a/packages/client/src/components/cw-button.vue b/packages/client/src/components/cw-button.vue index b0a9860de2..ccfd11462a 100644 --- a/packages/client/src/components/cw-button.vue +++ b/packages/client/src/components/cw-button.vue @@ -1,6 +1,6 @@ <template> <button class="nrvgflfu _button" @click="toggle"> - <b>{{ modelValue ? $ts._cw.hide : $ts._cw.show }}</b> + <b>{{ modelValue ? i18n.locale._cw.hide : i18n.locale._cw.show }}</b> <span v-if="!modelValue">{{ label }}</span> </button> </template> diff --git a/packages/client/src/components/date-separated-list.vue b/packages/client/src/components/date-separated-list.vue index aa84c6f60d..c85a0a6ffc 100644 --- a/packages/client/src/components/date-separated-list.vue +++ b/packages/client/src/components/date-separated-list.vue @@ -1,6 +1,8 @@ <script lang="ts"> import { defineComponent, h, PropType, TransitionGroup } from 'vue'; import MkAd from '@/components/global/ad.vue'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; export default defineComponent({ props: { @@ -30,29 +32,29 @@ export default defineComponent({ }, }, - methods: { - getDateText(time: string) { + setup(props, { slots, expose }) { + function getDateText(time: string) { const date = new Date(time).getDate(); const month = new Date(time).getMonth() + 1; - return this.$t('monthAndDay', { + return i18n.t('monthAndDay', { month: month.toString(), day: date.toString() }); } - }, - render() { - if (this.items.length === 0) return; + if (props.items.length === 0) return; - const renderChildren = () => this.items.map((item, i) => { - const el = this.$slots.default({ + const renderChildren = () => props.items.map((item, i) => { + if (!slots || !slots.default) return; + + const el = slots.default({ item: item })[0]; if (el.key == null && item.id) el.key = item.id; if ( - i != this.items.length - 1 && - new Date(item.createdAt).getDate() != new Date(this.items[i + 1].createdAt).getDate() + i != props.items.length - 1 && + new Date(item.createdAt).getDate() != new Date(props.items[i + 1].createdAt).getDate() ) { const separator = h('div', { class: 'separator', @@ -64,10 +66,10 @@ export default defineComponent({ h('i', { class: 'fas fa-angle-up icon', }), - this.getDateText(item.createdAt) + getDateText(item.createdAt) ]), h('span', [ - this.getDateText(this.items[i + 1].createdAt), + getDateText(props.items[i + 1].createdAt), h('i', { class: 'fas fa-angle-down icon', }) @@ -76,7 +78,7 @@ export default defineComponent({ return [el, separator]; } else { - if (this.ad && item._shouldInsertAd_) { + if (props.ad && item._shouldInsertAd_) { return [h(MkAd, { class: 'a', // advertiseの意(ブロッカー対策) key: item.id + ':ad', @@ -88,18 +90,19 @@ export default defineComponent({ } }); - return h(this.$store.state.animation ? TransitionGroup : 'div', this.$store.state.animation ? { - class: 'sqadhkmv' + (this.noGap ? ' noGap' : ''), - name: 'list', - tag: 'div', - 'data-direction': this.direction, - 'data-reversed': this.reversed ? 'true' : 'false', - } : { - class: 'sqadhkmv' + (this.noGap ? ' noGap' : ''), - }, { - default: renderChildren - }); - }, + return () => h( + defaultStore.state.animation ? TransitionGroup : 'div', + defaultStore.state.animation ? { + class: 'sqadhkmv' + (props.noGap ? ' noGap' : ''), + name: 'list', + tag: 'div', + 'data-direction': props.direction, + 'data-reversed': props.reversed ? 'true' : 'false', + } : { + class: 'sqadhkmv' + (props.noGap ? ' noGap' : ''), + }, + { default: renderChildren }); + } }); </script> diff --git a/packages/client/src/components/dialog.vue b/packages/client/src/components/dialog.vue index c2fa1b02b8..9cd5234684 100644 --- a/packages/client/src/components/dialog.vue +++ b/packages/client/src/components/dialog.vue @@ -14,7 +14,7 @@ </div> <header v-if="title"><Mfm :text="title"/></header> <div v-if="text" class="body"><Mfm :text="text"/></div> - <MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"> + <MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" @keydown="onInputKeydown"> <template v-if="input.type === 'password'" #prefix><i class="fas fa-lock"></i></template> </MkInput> <MkSelect v-if="select" v-model="selectedValue" autofocus> @@ -38,118 +38,107 @@ </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onBeforeUnmount, onMounted, ref } from 'vue'; import MkModal from '@/components/ui/modal.vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; -export default defineComponent({ - components: { - MkModal, - MkButton, - MkInput, - MkSelect, - }, +type Input = { + type: HTMLInputElement['type']; + placeholder?: string | null; + default: any | null; +}; - props: { - type: { - type: String, - required: false, - default: 'info' - }, - title: { - type: String, - required: false - }, - text: { - type: String, - required: false - }, - input: { - required: false - }, - select: { - required: false - }, - icon: { - required: false - }, - actions: { - required: false - }, - showOkButton: { - type: Boolean, - default: true - }, - showCancelButton: { - type: Boolean, - default: false - }, - cancelableByBgClick: { - type: Boolean, - default: true - }, - }, +type Select = { + items: { + value: string; + text: string; + }[]; + groupedItems: { + label: string; + items: { + value: string; + text: string; + }[]; + }[]; + default: string | null; +}; - emits: ['done', 'closed'], +const props = withDefaults(defineProps<{ + type?: 'success' | 'error' | 'warning' | 'info' | 'question' | 'waiting'; + title: string; + text?: string; + input?: Input; + select?: Select; + icon?: string; + actions?: { + text: string; + primary?: boolean, + callback: (...args: any[]) => void; + }[]; + showOkButton?: boolean; + showCancelButton?: boolean; + cancelableByBgClick?: boolean; +}>(), { + type: 'info', + showOkButton: true, + showCancelButton: false, + cancelableByBgClick: true, +}); - data() { - return { - inputValue: this.input && this.input.default ? this.input.default : null, - selectedValue: this.select ? this.select.default ? this.select.default : this.select.items ? this.select.items[0].value : this.select.groupedItems[0].items[0].value : null, - }; - }, +const emit = defineEmits<{ + (e: 'done', v: { canceled: boolean; result: any }): void; + (e: 'closed'): void; +}>(); - mounted() { - document.addEventListener('keydown', this.onKeydown); - }, +const modal = ref<InstanceType<typeof MkModal>>(); - beforeUnmount() { - document.removeEventListener('keydown', this.onKeydown); - }, +const inputValue = ref(props.input?.default || null); +const selectedValue = ref(props.select?.default || null); - methods: { - done(canceled, result?) { - this.$emit('done', { canceled, result }); - this.$refs.modal.close(); - }, +function done(canceled: boolean, result?) { + emit('done', { canceled, result }); + modal.value?.close(); +} - async ok() { - if (!this.showOkButton) return; +async function ok() { + if (!props.showOkButton) return; - const result = - this.input ? this.inputValue : - this.select ? this.selectedValue : - true; - this.done(false, result); - }, + const result = + props.input ? inputValue.value : + props.select ? selectedValue.value : + true; + done(false, result); +} - cancel() { - this.done(true); - }, +function cancel() { + done(true); +} +/* +function onBgClick() { + if (props.cancelableByBgClick) cancel(); +} +*/ +function onKeydown(e: KeyboardEvent) { + if (e.key === 'Escape') cancel(); +} - onBgClick() { - if (this.cancelableByBgClick) { - this.cancel(); - } - }, - - onKeydown(e) { - if (e.which === 27) { // ESC - this.cancel(); - } - }, - - onInputKeydown(e) { - if (e.which === 13) { // Enter - e.preventDefault(); - e.stopPropagation(); - this.ok(); - } - } +function onInputKeydown(e: KeyboardEvent) { + if (e.key === 'Enter') { + e.preventDefault(); + e.stopPropagation(); + ok(); } +} + +onMounted(() => { + document.addEventListener('keydown', onKeydown); +}); + +onBeforeUnmount(() => { + document.removeEventListener('keydown', onKeydown); }); </script> From 45211e14b33b458be54f8054d85918d96d9226db Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Wed, 12 Jan 2022 17:21:57 +0900 Subject: [PATCH 11/81] fix --- packages/client/src/pages/admin/files.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue index f2027fe8ac..6baa49f825 100644 --- a/packages/client/src/pages/admin/files.vue +++ b/packages/client/src/pages/admin/files.vue @@ -19,7 +19,7 @@ <option value="local">{{ $ts.local }}</option> <option value="remote">{{ $ts.remote }}</option> </MkSelect> - <MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params().origin === 'local'"> + <MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'"> <template #label>{{ $ts.host }}</template> </MkInput> </div> From b267a504ca4563e042929198319273e969939fc9 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Wed, 12 Jan 2022 17:34:53 +0900 Subject: [PATCH 12/81] bye reversi --- CHANGELOG.md | 2 + locales/ja-JP.yml | 38 - packages/backend/src/db/postgre.ts | 4 - packages/backend/src/games/reversi/core.ts | 263 ----- packages/backend/src/games/reversi/maps.ts | 896 ------------------ .../backend/src/games/reversi/package.json | 18 - .../backend/src/games/reversi/tsconfig.json | 21 - packages/backend/src/misc/schema.ts | 4 - .../src/models/entities/games/reversi/game.ts | 133 --- .../models/entities/games/reversi/matching.ts | 35 - packages/backend/src/models/index.ts | 4 - .../models/repositories/games/reversi/game.ts | 191 ---- .../repositories/games/reversi/matching.ts | 69 -- .../api/endpoints/games/reversi/games.ts | 157 --- .../api/endpoints/games/reversi/games/show.ts | 169 ---- .../games/reversi/games/surrender.ts | 68 -- .../endpoints/games/reversi/invitations.ts | 59 -- .../api/endpoints/games/reversi/match.ts | 109 --- .../endpoints/games/reversi/match/cancel.ts | 15 - .../src/server/api/endpoints/users/stats.ts | 8 +- .../api/stream/channels/games/reversi-game.ts | 372 -------- .../api/stream/channels/games/reversi.ts | 34 - .../src/server/api/stream/channels/index.ts | 4 - .../backend/src/server/api/stream/types.ts | 52 - packages/backend/src/server/web/index.ts | 3 - packages/backend/src/services/stream.ts | 13 - packages/client/src/menu.ts | 5 - .../client/src/pages/reversi/game.board.vue | 528 ----------- .../client/src/pages/reversi/game.setting.vue | 390 -------- packages/client/src/pages/reversi/game.vue | 77 -- packages/client/src/pages/reversi/index.vue | 280 ------ .../src/pages/settings/account-info.vue | 4 - packages/client/src/pages/settings/sounds.vue | 2 - packages/client/src/router.ts | 2 - .../client/src/scripts/games/reversi/core.ts | 263 ----- .../client/src/scripts/games/reversi/maps.ts | 896 ------------------ .../src/scripts/games/reversi/package.json | 18 - .../src/scripts/games/reversi/tsconfig.json | 21 - packages/client/src/store.ts | 2 - 39 files changed, 3 insertions(+), 5226 deletions(-) delete mode 100644 packages/backend/src/games/reversi/core.ts delete mode 100644 packages/backend/src/games/reversi/maps.ts delete mode 100644 packages/backend/src/games/reversi/package.json delete mode 100644 packages/backend/src/games/reversi/tsconfig.json delete mode 100644 packages/backend/src/models/entities/games/reversi/game.ts delete mode 100644 packages/backend/src/models/entities/games/reversi/matching.ts delete mode 100644 packages/backend/src/models/repositories/games/reversi/game.ts delete mode 100644 packages/backend/src/models/repositories/games/reversi/matching.ts delete mode 100644 packages/backend/src/server/api/endpoints/games/reversi/games.ts delete mode 100644 packages/backend/src/server/api/endpoints/games/reversi/games/show.ts delete mode 100644 packages/backend/src/server/api/endpoints/games/reversi/games/surrender.ts delete mode 100644 packages/backend/src/server/api/endpoints/games/reversi/invitations.ts delete mode 100644 packages/backend/src/server/api/endpoints/games/reversi/match.ts delete mode 100644 packages/backend/src/server/api/endpoints/games/reversi/match/cancel.ts delete mode 100644 packages/backend/src/server/api/stream/channels/games/reversi-game.ts delete mode 100644 packages/backend/src/server/api/stream/channels/games/reversi.ts delete mode 100644 packages/client/src/pages/reversi/game.board.vue delete mode 100644 packages/client/src/pages/reversi/game.setting.vue delete mode 100644 packages/client/src/pages/reversi/game.vue delete mode 100644 packages/client/src/pages/reversi/index.vue delete mode 100644 packages/client/src/scripts/games/reversi/core.ts delete mode 100644 packages/client/src/scripts/games/reversi/maps.ts delete mode 100644 packages/client/src/scripts/games/reversi/package.json delete mode 100644 packages/client/src/scripts/games/reversi/tsconfig.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 501e5cad98..ed5bdd3f1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ ### Changes - Room機能が削除されました - 後日別リポジトリとして復活予定です +- リバーシ機能が削除されました + - 後日別リポジトリとして復活予定です - Chat UIが削除されました ### Improvements diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 5ba09c4a81..dd853aa83a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -242,7 +242,6 @@ uploadFromUrlDescription: "アップロードしたいファイルのURL" uploadFromUrlRequested: "アップロードをリクエストしました" uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がかかる場合があります。" explore: "みつける" -games: "Misskey Games" messageRead: "既読" noMoreHistory: "これより過去の履歴はありません" startMessaging: "チャットを開始" @@ -669,7 +668,6 @@ emailVerified: "メールアドレスが確認されました" noteFavoritesCount: "お気に入りノートの数" pageLikesCount: "Pageにいいねした数" pageLikedCount: "Pageにいいねされた数" -reversiCount: "リバーシの対局数" contact: "連絡先" useSystemFont: "システムのデフォルトのフォントを使う" clips: "クリップ" @@ -957,40 +955,6 @@ _mfm: rotate: "回転" rotateDescription: "指定した角度で回転させます。" -_reversi: - reversi: "リバーシ" - gameSettings: "対局の設定" - chooseBoard: "ボードを選択" - blackOrWhite: "先行/後攻" - blackIs: "{name}が黒(先行)" - rules: "ルール" - botSettings: "Botのオプション" - thisGameIsStartedSoon: "対局は数秒後に開始されます" - waitingForOther: "相手の準備が完了するのを待っています" - waitingForMe: "あなたの準備が完了するのを待っています" - waitingBoth: "準備してください" - ready: "準備完了" - cancelReady: "準備を再開" - opponentTurn: "相手のターンです" - myTurn: "あなたのターンです" - turnOf: "{name}のターンです" - pastTurnOf: "{name}のターン" - surrender: "投了" - surrendered: "投了により" - drawn: "引き分け" - won: "{name}の勝ち" - black: "黒" - white: "白" - total: "合計" - turnCount: "{count}ターン目" - myGames: "自分の対局" - allGames: "みんなの対局" - ended: "終了" - playing: "対局中" - isLlotheo: "石の少ない方が勝ち(ロセオ)" - loopedMap: "ループマップ" - canPutEverywhere: "どこでも置けるモード" - _instanceTicker: none: "表示しない" remote: "リモートユーザーに表示" @@ -1118,8 +1082,6 @@ _sfx: chatBg: "チャット(バックグラウンド)" antenna: "アンテナ受信" channel: "チャンネル通知" - reversiPutBlack: "リバーシ: 黒が打ったとき" - reversiPutWhite: "リバーシ: 白が打ったとき" _ago: unknown: "謎" diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index b5f228d919..1692b26219 100644 --- a/packages/backend/src/db/postgre.ts +++ b/packages/backend/src/db/postgre.ts @@ -40,8 +40,6 @@ import { Signin } from '@/models/entities/signin'; import { AuthSession } from '@/models/entities/auth-session'; import { FollowRequest } from '@/models/entities/follow-request'; import { Emoji } from '@/models/entities/emoji'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; -import { ReversiMatching } from '@/models/entities/games/reversi/matching'; import { UserNotePining } from '@/models/entities/user-note-pining'; import { Poll } from '@/models/entities/poll'; import { UserKeypair } from '@/models/entities/user-keypair'; @@ -166,8 +164,6 @@ export const entities = [ AntennaNote, PromoNote, PromoRead, - ReversiGame, - ReversiMatching, Relay, MutedNote, Channel, diff --git a/packages/backend/src/games/reversi/core.ts b/packages/backend/src/games/reversi/core.ts deleted file mode 100644 index 0cf7714543..0000000000 --- a/packages/backend/src/games/reversi/core.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { count, concat } from '@/prelude/array'; - -// MISSKEY REVERSI ENGINE - -/** - * true ... 黒 - * false ... 白 - */ -export type Color = boolean; -const BLACK = true; -const WHITE = false; - -export type MapPixel = 'null' | 'empty'; - -export type Options = { - isLlotheo: boolean; - canPutEverywhere: boolean; - loopedBoard: boolean; -}; - -export type Undo = { - /** - * 色 - */ - color: Color; - - /** - * どこに打ったか - */ - pos: number; - - /** - * 反転した石の位置の配列 - */ - effects: number[]; - - /** - * ターン - */ - turn: Color | null; -}; - -/** - * リバーシエンジン - */ -export default class Reversi { - public map: MapPixel[]; - public mapWidth: number; - public mapHeight: number; - public board: (Color | null | undefined)[]; - public turn: Color | null = BLACK; - public opts: Options; - - public prevPos = -1; - public prevColor: Color | null = null; - - private logs: Undo[] = []; - - /** - * ゲームを初期化します - */ - constructor(map: string[], opts: Options) { - //#region binds - this.put = this.put.bind(this); - //#endregion - - //#region Options - this.opts = opts; - if (this.opts.isLlotheo == null) this.opts.isLlotheo = false; - if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false; - if (this.opts.loopedBoard == null) this.opts.loopedBoard = false; - //#endregion - - //#region Parse map data - this.mapWidth = map[0].length; - this.mapHeight = map.length; - const mapData = map.join(''); - - this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined); - - this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null'); - //#endregion - - // ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある - if (!this.canPutSomewhere(BLACK)) this.turn = this.canPutSomewhere(WHITE) ? WHITE : null; - } - - /** - * 黒石の数 - */ - public get blackCount() { - return count(BLACK, this.board); - } - - /** - * 白石の数 - */ - public get whiteCount() { - return count(WHITE, this.board); - } - - public transformPosToXy(pos: number): number[] { - const x = pos % this.mapWidth; - const y = Math.floor(pos / this.mapWidth); - return [x, y]; - } - - public transformXyToPos(x: number, y: number): number { - return x + (y * this.mapWidth); - } - - /** - * 指定のマスに石を打ちます - * @param color 石の色 - * @param pos 位置 - */ - public put(color: Color, pos: number) { - this.prevPos = pos; - this.prevColor = color; - - this.board[pos] = color; - - // 反転させられる石を取得 - const effects = this.effects(color, pos); - - // 反転させる - for (const pos of effects) { - this.board[pos] = color; - } - - const turn = this.turn; - - this.logs.push({ - color, - pos, - effects, - turn, - }); - - this.calcTurn(); - } - - private calcTurn() { - // ターン計算 - this.turn = - this.canPutSomewhere(!this.prevColor) ? !this.prevColor : - this.canPutSomewhere(this.prevColor!) ? this.prevColor : - null; - } - - public undo() { - const undo = this.logs.pop()!; - this.prevColor = undo.color; - this.prevPos = undo.pos; - this.board[undo.pos] = null; - for (const pos of undo.effects) { - const color = this.board[pos]; - this.board[pos] = !color; - } - this.turn = undo.turn; - } - - /** - * 指定した位置のマップデータのマスを取得します - * @param pos 位置 - */ - public mapDataGet(pos: number): MapPixel { - const [x, y] = this.transformPosToXy(pos); - return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos]; - } - - /** - * 打つことができる場所を取得します - */ - public puttablePlaces(color: Color): number[] { - return Array.from(this.board.keys()).filter(i => this.canPut(color, i)); - } - - /** - * 打つことができる場所があるかどうかを取得します - */ - public canPutSomewhere(color: Color): boolean { - return this.puttablePlaces(color).length > 0; - } - - /** - * 指定のマスに石を打つことができるかどうかを取得します - * @param color 自分の色 - * @param pos 位置 - */ - public canPut(color: Color, pos: number): boolean { - return ( - this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない - this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んでなくても置けるモード - this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか - } - - /** - * 指定のマスに石を置いた時の、反転させられる石を取得します - * @param color 自分の色 - * @param initPos 位置 - */ - public effects(color: Color, initPos: number): number[] { - const enemyColor = !color; - - const diffVectors: [number, number][] = [ - [ 0, -1], // 上 - [ +1, -1], // 右上 - [ +1, 0], // 右 - [ +1, +1], // 右下 - [ 0, +1], // 下 - [ -1, +1], // 左下 - [ -1, 0], // 左 - [ -1, -1], // 左上 - ]; - - const effectsInLine = ([dx, dy]: [number, number]): number[] => { - const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy]; - - const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列 - let [x, y] = this.transformPosToXy(initPos); - while (true) { - [x, y] = nextPos(x, y); - - // 座標が指し示す位置がボード外に出たとき - if (this.opts.loopedBoard && this.transformXyToPos( - (x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth), - (y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) { - // 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ) - return found; - } else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) { - return []; // 挟めないことが確定 (盤面外に到達) - } - - const pos = this.transformXyToPos(x, y); - if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達) - const stone = this.board[pos]; - if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達) - if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見) - if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見) - } - }; - - return concat(diffVectors.map(effectsInLine)); - } - - /** - * ゲームが終了したか否か - */ - public get isEnded(): boolean { - return this.turn === null; - } - - /** - * ゲームの勝者 (null = 引き分け) - */ - public get winner(): Color | null { - return this.isEnded ? - this.blackCount == this.whiteCount ? null : - this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : - undefined as never; - } -} diff --git a/packages/backend/src/games/reversi/maps.ts b/packages/backend/src/games/reversi/maps.ts deleted file mode 100644 index 8442c6d741..0000000000 --- a/packages/backend/src/games/reversi/maps.ts +++ /dev/null @@ -1,896 +0,0 @@ -/** - * 組み込みマップ定義 - * - * データ値: - * (スペース) ... マス無し - * - ... マス - * b ... 初期配置される黒石 - * w ... 初期配置される白石 - */ - -export type Map = { - name?: string; - category?: string; - author?: string; - data: string[]; -}; - -export const fourfour: Map = { - name: '4x4', - category: '4x4', - data: [ - '----', - '-wb-', - '-bw-', - '----', - ], -}; - -export const sixsix: Map = { - name: '6x6', - category: '6x6', - data: [ - '------', - '------', - '--wb--', - '--bw--', - '------', - '------', - ], -}; - -export const roundedSixsix: Map = { - name: '6x6 rounded', - category: '6x6', - author: 'syuilo', - data: [ - ' ---- ', - '------', - '--wb--', - '--bw--', - '------', - ' ---- ', - ], -}; - -export const roundedSixsix2: Map = { - name: '6x6 rounded 2', - category: '6x6', - author: 'syuilo', - data: [ - ' -- ', - ' ---- ', - '--wb--', - '--bw--', - ' ---- ', - ' -- ', - ], -}; - -export const eighteight: Map = { - name: '8x8', - category: '8x8', - data: [ - '--------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------', - ], -}; - -export const eighteightH1: Map = { - name: '8x8 handicap 1', - category: '8x8', - data: [ - 'b-------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------', - ], -}; - -export const eighteightH2: Map = { - name: '8x8 handicap 2', - category: '8x8', - data: [ - 'b-------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '-------b', - ], -}; - -export const eighteightH3: Map = { - name: '8x8 handicap 3', - category: '8x8', - data: [ - 'b------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '-------b', - ], -}; - -export const eighteightH4: Map = { - name: '8x8 handicap 4', - category: '8x8', - data: [ - 'b------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - 'b------b', - ], -}; - -export const eighteightH28: Map = { - name: '8x8 handicap 28', - category: '8x8', - data: [ - 'bbbbbbbb', - 'b------b', - 'b------b', - 'b--wb--b', - 'b--bw--b', - 'b------b', - 'b------b', - 'bbbbbbbb', - ], -}; - -export const roundedEighteight: Map = { - name: '8x8 rounded', - category: '8x8', - author: 'syuilo', - data: [ - ' ------ ', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - ' ------ ', - ], -}; - -export const roundedEighteight2: Map = { - name: '8x8 rounded 2', - category: '8x8', - author: 'syuilo', - data: [ - ' ---- ', - ' ------ ', - '--------', - '---wb---', - '---bw---', - '--------', - ' ------ ', - ' ---- ', - ], -}; - -export const roundedEighteight3: Map = { - name: '8x8 rounded 3', - category: '8x8', - author: 'syuilo', - data: [ - ' -- ', - ' ---- ', - ' ------ ', - '---wb---', - '---bw---', - ' ------ ', - ' ---- ', - ' -- ', - ], -}; - -export const eighteightWithNotch: Map = { - name: '8x8 with notch', - category: '8x8', - author: 'syuilo', - data: [ - '--- ---', - '--------', - '--------', - ' --wb-- ', - ' --bw-- ', - '--------', - '--------', - '--- ---', - ], -}; - -export const eighteightWithSomeHoles: Map = { - name: '8x8 with some holes', - category: '8x8', - author: 'syuilo', - data: [ - '--- ----', - '----- --', - '-- -----', - '---wb---', - '---bw- -', - ' -------', - '--- ----', - '--------', - ], -}; - -export const circle: Map = { - name: 'Circle', - category: '8x8', - author: 'syuilo', - data: [ - ' -- ', - ' ------ ', - ' ------ ', - '---wb---', - '---bw---', - ' ------ ', - ' ------ ', - ' -- ', - ], -}; - -export const smile: Map = { - name: 'Smile', - category: '8x8', - author: 'syuilo', - data: [ - ' ------ ', - '--------', - '-- -- --', - '---wb---', - '-- bw --', - '--- ---', - '--------', - ' ------ ', - ], -}; - -export const window: Map = { - name: 'Window', - category: '8x8', - author: 'syuilo', - data: [ - '--------', - '- -- -', - '- -- -', - '---wb---', - '---bw---', - '- -- -', - '- -- -', - '--------', - ], -}; - -export const reserved: Map = { - name: 'Reserved', - category: '8x8', - author: 'Aya', - data: [ - 'w------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - 'b------w', - ], -}; - -export const x: Map = { - name: 'X', - category: '8x8', - author: 'Aya', - data: [ - 'w------b', - '-w----b-', - '--w--b--', - '---wb---', - '---bw---', - '--b--w--', - '-b----w-', - 'b------w', - ], -}; - -export const parallel: Map = { - name: 'Parallel', - category: '8x8', - author: 'Aya', - data: [ - '--------', - '--------', - '--------', - '---bb---', - '---ww---', - '--------', - '--------', - '--------', - ], -}; - -export const lackOfBlack: Map = { - name: 'Lack of Black', - category: '8x8', - data: [ - '--------', - '--------', - '--------', - '---w----', - '---bw---', - '--------', - '--------', - '--------', - ], -}; - -export const squareParty: Map = { - name: 'Square Party', - category: '8x8', - author: 'syuilo', - data: [ - '--------', - '-wwwbbb-', - '-w-wb-b-', - '-wwwbbb-', - '-bbbwww-', - '-b-bw-w-', - '-bbbwww-', - '--------', - ], -}; - -export const minesweeper: Map = { - name: 'Minesweeper', - category: '8x8', - author: 'syuilo', - data: [ - 'b-b--w-w', - '-w-wb-b-', - 'w-b--w-b', - '-b-wb-w-', - '-w-bw-b-', - 'b-w--b-w', - '-b-bw-w-', - 'w-w--b-b', - ], -}; - -export const tenthtenth: Map = { - name: '10x10', - category: '10x10', - data: [ - '----------', - '----------', - '----------', - '----------', - '----wb----', - '----bw----', - '----------', - '----------', - '----------', - '----------', - ], -}; - -export const hole: Map = { - name: 'The Hole', - category: '10x10', - author: 'syuilo', - data: [ - '----------', - '----------', - '--wb--wb--', - '--bw--bw--', - '---- ----', - '---- ----', - '--wb--wb--', - '--bw--bw--', - '----------', - '----------', - ], -}; - -export const grid: Map = { - name: 'Grid', - category: '10x10', - author: 'syuilo', - data: [ - '----------', - '- - -- - -', - '----------', - '- - -- - -', - '----wb----', - '----bw----', - '- - -- - -', - '----------', - '- - -- - -', - '----------', - ], -}; - -export const cross: Map = { - name: 'Cross', - category: '10x10', - author: 'Aya', - data: [ - ' ---- ', - ' ---- ', - ' ---- ', - '----------', - '----wb----', - '----bw----', - '----------', - ' ---- ', - ' ---- ', - ' ---- ', - ], -}; - -export const charX: Map = { - name: 'Char X', - category: '10x10', - author: 'syuilo', - data: [ - '--- ---', - '---- ----', - '----------', - ' -------- ', - ' --wb-- ', - ' --bw-- ', - ' -------- ', - '----------', - '---- ----', - '--- ---', - ], -}; - -export const charY: Map = { - name: 'Char Y', - category: '10x10', - author: 'syuilo', - data: [ - '--- ---', - '---- ----', - '----------', - ' -------- ', - ' --wb-- ', - ' --bw-- ', - ' ------ ', - ' ------ ', - ' ------ ', - ' ------ ', - ], -}; - -export const walls: Map = { - name: 'Walls', - category: '10x10', - author: 'Aya', - data: [ - ' bbbbbbbb ', - 'w--------w', - 'w--------w', - 'w--------w', - 'w---wb---w', - 'w---bw---w', - 'w--------w', - 'w--------w', - 'w--------w', - ' bbbbbbbb ', - ], -}; - -export const cpu: Map = { - name: 'CPU', - category: '10x10', - author: 'syuilo', - data: [ - ' b b b b ', - 'w--------w', - ' -------- ', - 'w--------w', - ' ---wb--- ', - ' ---bw--- ', - 'w--------w', - ' -------- ', - 'w--------w', - ' b b b b ', - ], -}; - -export const checker: Map = { - name: 'Checker', - category: '10x10', - author: 'Aya', - data: [ - '----------', - '----------', - '----------', - '---wbwb---', - '---bwbw---', - '---wbwb---', - '---bwbw---', - '----------', - '----------', - '----------', - ], -}; - -export const japaneseCurry: Map = { - name: 'Japanese curry', - category: '10x10', - author: 'syuilo', - data: [ - 'w-b-b-b-b-', - '-w-b-b-b-b', - 'w-w-b-b-b-', - '-w-w-b-b-b', - 'w-w-wwb-b-', - '-w-wbb-b-b', - 'w-w-w-b-b-', - '-w-w-w-b-b', - 'w-w-w-w-b-', - '-w-w-w-w-b', - ], -}; - -export const mosaic: Map = { - name: 'Mosaic', - category: '10x10', - author: 'syuilo', - data: [ - '- - - - - ', - ' - - - - -', - '- - - - - ', - ' - w w - -', - '- - b b - ', - ' - w w - -', - '- - b b - ', - ' - - - - -', - '- - - - - ', - ' - - - - -', - ], -}; - -export const arena: Map = { - name: 'Arena', - category: '10x10', - author: 'syuilo', - data: [ - '- - -- - -', - ' - - - - ', - '- ------ -', - ' -------- ', - '- --wb-- -', - '- --bw-- -', - ' -------- ', - '- ------ -', - ' - - - - ', - '- - -- - -', - ], -}; - -export const reactor: Map = { - name: 'Reactor', - category: '10x10', - author: 'syuilo', - data: [ - '-w------b-', - 'b- - - -w', - '- --wb-- -', - '---b w---', - '- b wb w -', - '- w bw b -', - '---w b---', - '- --bw-- -', - 'w- - - -b', - '-b------w-', - ], -}; - -export const sixeight: Map = { - name: '6x8', - category: 'Special', - data: [ - '------', - '------', - '------', - '--wb--', - '--bw--', - '------', - '------', - '------', - ], -}; - -export const spark: Map = { - name: 'Spark', - category: 'Special', - author: 'syuilo', - data: [ - ' - - ', - '----------', - ' -------- ', - ' -------- ', - ' ---wb--- ', - ' ---bw--- ', - ' -------- ', - ' -------- ', - '----------', - ' - - ', - ], -}; - -export const islands: Map = { - name: 'Islands', - category: 'Special', - author: 'syuilo', - data: [ - '-------- ', - '---wb--- ', - '---bw--- ', - '-------- ', - ' - - ', - ' - - ', - ' --------', - ' --------', - ' --------', - ' --------', - ], -}; - -export const galaxy: Map = { - name: 'Galaxy', - category: 'Special', - author: 'syuilo', - data: [ - ' ------ ', - ' --www--- ', - ' ------w--- ', - '---bbb--w---', - '--b---b-w-b-', - '-b--wwb-w-b-', - '-b-w-bww--b-', - '-b-w-b---b--', - '---w--bbb---', - ' ---w------ ', - ' ---www-- ', - ' ------ ', - ], -}; - -export const triangle: Map = { - name: 'Triangle', - category: 'Special', - author: 'syuilo', - data: [ - ' -- ', - ' -- ', - ' ---- ', - ' ---- ', - ' --wb-- ', - ' --bw-- ', - ' -------- ', - ' -------- ', - '----------', - '----------', - ], -}; - -export const iphonex: Map = { - name: 'iPhone X', - category: 'Special', - author: 'syuilo', - data: [ - ' -- -- ', - '--------', - '--------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------', - '--------', - ' ------ ', - ], -}; - -export const dealWithIt: Map = { - name: 'Deal with it!', - category: 'Special', - author: 'syuilo', - data: [ - '------------', - '--w-b-------', - ' --b-w------', - ' --w-b---- ', - ' ------- ', - ], -}; - -export const experiment: Map = { - name: 'Let\'s experiment', - category: 'Special', - author: 'syuilo', - data: [ - ' ------------ ', - '------wb------', - '------bw------', - '--------------', - ' - - ', - '------ ------', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'wwwwww bbbbbb', - ], -}; - -export const bigBoard: Map = { - name: 'Big board', - category: 'Special', - data: [ - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '-------wb-------', - '-------bw-------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - ], -}; - -export const twoBoard: Map = { - name: 'Two board', - category: 'Special', - author: 'Aya', - data: [ - '-------- --------', - '-------- --------', - '-------- --------', - '---wb--- ---wb---', - '---bw--- ---bw---', - '-------- --------', - '-------- --------', - '-------- --------', - ], -}; - -export const test1: Map = { - name: 'Test1', - category: 'Test', - data: [ - '--------', - '---wb---', - '---bw---', - '--------', - ], -}; - -export const test2: Map = { - name: 'Test2', - category: 'Test', - data: [ - '------', - '------', - '-b--w-', - '-w--b-', - '-w--b-', - ], -}; - -export const test3: Map = { - name: 'Test3', - category: 'Test', - data: [ - '-w-', - '--w', - 'w--', - '-w-', - '--w', - 'w--', - '-w-', - '--w', - 'w--', - '-w-', - '---', - 'b--', - ], -}; - -export const test4: Map = { - name: 'Test4', - category: 'Test', - data: [ - '-w--b-', - '-w--b-', - '------', - '-w--b-', - '-w--b-', - ], -}; - -// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)A1に打ってしまう -export const test6: Map = { - name: 'Test6', - category: 'Test', - data: [ - '--wwwww-', - 'wwwwwwww', - 'wbbbwbwb', - 'wbbbbwbb', - 'wbwbbwbb', - 'wwbwbbbb', - '--wbbbbb', - '-wwwww--', - ], -}; - -// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)G7に打ってしまう -export const test7: Map = { - name: 'Test7', - category: 'Test', - data: [ - 'b--w----', - 'b-wwww--', - 'bwbwwwbb', - 'wbwwwwb-', - 'wwwwwww-', - '-wwbbwwb', - '--wwww--', - '--wwww--', - ], -}; - -// 検証用: この盤面で藍(lv5)が黒で始めると何故か(?)A1に打ってしまう -export const test8: Map = { - name: 'Test8', - category: 'Test', - data: [ - '--------', - '-----w--', - 'w--www--', - 'wwwwww--', - 'bbbbwww-', - 'wwwwww--', - '--www---', - '--ww----', - ], -}; diff --git a/packages/backend/src/games/reversi/package.json b/packages/backend/src/games/reversi/package.json deleted file mode 100644 index a4415ad141..0000000000 --- a/packages/backend/src/games/reversi/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "misskey-reversi", - "version": "0.0.5", - "description": "Misskey reversi engine", - "keywords": [ - "misskey" - ], - "author": "syuilo <i@syuilo.com>", - "license": "MIT", - "repository": "https://github.com/misskey-dev/misskey.git", - "bugs": "https://github.com/misskey-dev/misskey/issues", - "main": "./built/core.js", - "types": "./built/core.d.ts", - "scripts": { - "build": "tsc" - }, - "dependencies": {} -} diff --git a/packages/backend/src/games/reversi/tsconfig.json b/packages/backend/src/games/reversi/tsconfig.json deleted file mode 100644 index 851fb6b7e4..0000000000 --- a/packages/backend/src/games/reversi/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "noEmitOnError": false, - "noImplicitAny": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "experimentalDecorators": true, - "declaration": true, - "sourceMap": false, - "target": "es2017", - "module": "commonjs", - "removeComments": false, - "noLib": false, - "outDir": "./built", - "rootDir": "./" - }, - "compileOnSave": false, - "include": [ - "./core.ts" - ] -} diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts index 4131875ef7..ae6f1bac86 100644 --- a/packages/backend/src/misc/schema.ts +++ b/packages/backend/src/misc/schema.ts @@ -22,8 +22,6 @@ import { packedFederationInstanceSchema } from '@/models/repositories/federation import { packedQueueCountSchema } from '@/models/repositories/queue'; import { packedGalleryPostSchema } from '@/models/repositories/gallery-post'; import { packedEmojiSchema } from '@/models/repositories/emoji'; -import { packedReversiGameSchema } from '@/models/repositories/games/reversi/game'; -import { packedReversiMatchingSchema } from '@/models/repositories/games/reversi/matching'; export const refs = { User: packedUserSchema, @@ -49,8 +47,6 @@ export const refs = { FederationInstance: packedFederationInstanceSchema, GalleryPost: packedGalleryPostSchema, Emoji: packedEmojiSchema, - ReversiGame: packedReversiGameSchema, - ReversiMatching: packedReversiMatchingSchema, }; export type Packed<x extends keyof typeof refs> = ObjType<(typeof refs[x])['properties']>; diff --git a/packages/backend/src/models/entities/games/reversi/game.ts b/packages/backend/src/models/entities/games/reversi/game.ts deleted file mode 100644 index fe9b8a5ba5..0000000000 --- a/packages/backend/src/models/entities/games/reversi/game.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from '../../user'; -import { id } from '../../../id'; - -@Entity() -export class ReversiGame { - @PrimaryColumn(id()) - public id: string; - - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the ReversiGame.', - }) - public createdAt: Date; - - @Column('timestamp with time zone', { - nullable: true, - comment: 'The started date of the ReversiGame.', - }) - public startedAt: Date | null; - - @Column(id()) - public user1Id: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public user1: User | null; - - @Column(id()) - public user2Id: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public user2: User | null; - - @Column('boolean', { - default: false, - }) - public user1Accepted: boolean; - - @Column('boolean', { - default: false, - }) - public user2Accepted: boolean; - - /** - * どちらのプレイヤーが先行(黒)か - * 1 ... user1 - * 2 ... user2 - */ - @Column('integer', { - nullable: true, - }) - public black: number | null; - - @Column('boolean', { - default: false, - }) - public isStarted: boolean; - - @Column('boolean', { - default: false, - }) - public isEnded: boolean; - - @Column({ - ...id(), - nullable: true, - }) - public winnerId: User['id'] | null; - - @Column({ - ...id(), - nullable: true, - }) - public surrendered: User['id'] | null; - - @Column('jsonb', { - default: [], - }) - public logs: { - at: Date; - color: boolean; - pos: number; - }[]; - - @Column('varchar', { - array: true, length: 64, - }) - public map: string[]; - - @Column('varchar', { - length: 32, - }) - public bw: string; - - @Column('boolean', { - default: false, - }) - public isLlotheo: boolean; - - @Column('boolean', { - default: false, - }) - public canPutEverywhere: boolean; - - @Column('boolean', { - default: false, - }) - public loopedBoard: boolean; - - @Column('jsonb', { - nullable: true, default: null, - }) - public form1: any | null; - - @Column('jsonb', { - nullable: true, default: null, - }) - public form2: any | null; - - /** - * ログのposを文字列としてすべて連結したもののCRC32値 - */ - @Column('varchar', { - length: 32, nullable: true, - }) - public crc32: string | null; -} diff --git a/packages/backend/src/models/entities/games/reversi/matching.ts b/packages/backend/src/models/entities/games/reversi/matching.ts deleted file mode 100644 index 70bb555e2f..0000000000 --- a/packages/backend/src/models/entities/games/reversi/matching.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from '../../user'; -import { id } from '../../../id'; - -@Entity() -export class ReversiMatching { - @PrimaryColumn(id()) - public id: string; - - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the ReversiMatching.', - }) - public createdAt: Date; - - @Index() - @Column(id()) - public parentId: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public parent: User | null; - - @Index() - @Column(id()) - public childId: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public child: User | null; -} diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index 7154cca550..67da347395 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -18,7 +18,6 @@ import { AccessToken } from './entities/access-token'; import { UserNotePining } from './entities/user-note-pining'; import { SigninRepository } from './repositories/signin'; import { MessagingMessageRepository } from './repositories/messaging-message'; -import { ReversiGameRepository } from './repositories/games/reversi/game'; import { UserListRepository } from './repositories/user-list'; import { UserListJoining } from './entities/user-list-joining'; import { UserGroupRepository } from './repositories/user-group'; @@ -30,7 +29,6 @@ import { BlockingRepository } from './repositories/blocking'; import { NoteReactionRepository } from './repositories/note-reaction'; import { NotificationRepository } from './repositories/notification'; import { NoteFavoriteRepository } from './repositories/note-favorite'; -import { ReversiMatchingRepository } from './repositories/games/reversi/matching'; import { UserPublickey } from './entities/user-publickey'; import { UserKeypair } from './entities/user-keypair'; import { AppRepository } from './repositories/app'; @@ -107,8 +105,6 @@ export const AuthSessions = getCustomRepository(AuthSessionRepository); export const AccessTokens = getRepository(AccessToken); export const Signins = getCustomRepository(SigninRepository); export const MessagingMessages = getCustomRepository(MessagingMessageRepository); -export const ReversiGames = getCustomRepository(ReversiGameRepository); -export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository); export const Pages = getCustomRepository(PageRepository); export const PageLikes = getCustomRepository(PageLikeRepository); export const GalleryPosts = getCustomRepository(GalleryPostRepository); diff --git a/packages/backend/src/models/repositories/games/reversi/game.ts b/packages/backend/src/models/repositories/games/reversi/game.ts deleted file mode 100644 index a9e0496760..0000000000 --- a/packages/backend/src/models/repositories/games/reversi/game.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { User } from '@/models/entities/user'; -import { EntityRepository, Repository } from 'typeorm'; -import { Users } from '../../../index'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; -import { Packed } from '@/misc/schema'; - -@EntityRepository(ReversiGame) -export class ReversiGameRepository extends Repository<ReversiGame> { - public async pack( - src: ReversiGame['id'] | ReversiGame, - me?: { id: User['id'] } | null | undefined, - options?: { - detail?: boolean - } - ): Promise<Packed<'ReversiGame'>> { - const opts = Object.assign({ - detail: true, - }, options); - - const game = typeof src === 'object' ? src : await this.findOneOrFail(src); - - return { - id: game.id, - createdAt: game.createdAt.toISOString(), - startedAt: game.startedAt && game.startedAt.toISOString(), - isStarted: game.isStarted, - isEnded: game.isEnded, - form1: game.form1, - form2: game.form2, - user1Accepted: game.user1Accepted, - user2Accepted: game.user2Accepted, - user1Id: game.user1Id, - user2Id: game.user2Id, - user1: await Users.pack(game.user1Id, me), - user2: await Users.pack(game.user2Id, me), - winnerId: game.winnerId, - winner: game.winnerId ? await Users.pack(game.winnerId, me) : null, - surrendered: game.surrendered, - black: game.black, - bw: game.bw, - isLlotheo: game.isLlotheo, - canPutEverywhere: game.canPutEverywhere, - loopedBoard: game.loopedBoard, - ...(opts.detail ? { - logs: game.logs.map(log => ({ - at: log.at.toISOString(), - color: log.color, - pos: log.pos, - })), - map: game.map, - } : {}), - }; - } -} - -export const packedReversiGameSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - startedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'date-time', - }, - isStarted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - isEnded: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - form1: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - form2: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - user1Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - user2Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - user1Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - user2Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - user1: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - user2: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - winnerId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - winner: { - type: 'object' as const, - optional: false as const, nullable: true as const, - ref: 'User' as const, - }, - surrendered: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - black: { - type: 'number' as const, - optional: false as const, nullable: true as const, - }, - bw: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - isLlotheo: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - canPutEverywhere: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - loopedBoard: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - logs: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'object' as const, - optional: true as const, nullable: false as const, - properties: { - at: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - color: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - pos: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - }, - map: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/games/reversi/matching.ts b/packages/backend/src/models/repositories/games/reversi/matching.ts deleted file mode 100644 index b55f598068..0000000000 --- a/packages/backend/src/models/repositories/games/reversi/matching.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { EntityRepository, Repository } from 'typeorm'; -import { ReversiMatching } from '@/models/entities/games/reversi/matching'; -import { Users } from '../../../index'; -import { awaitAll } from '@/prelude/await-all'; -import { User } from '@/models/entities/user'; -import { Packed } from '@/misc/schema'; - -@EntityRepository(ReversiMatching) -export class ReversiMatchingRepository extends Repository<ReversiMatching> { - public async pack( - src: ReversiMatching['id'] | ReversiMatching, - me: { id: User['id'] } - ): Promise<Packed<'ReversiMatching'>> { - const matching = typeof src === 'object' ? src : await this.findOneOrFail(src); - - return await awaitAll({ - id: matching.id, - createdAt: matching.createdAt.toISOString(), - parentId: matching.parentId, - parent: Users.pack(matching.parentId, me, { - detail: true, - }), - childId: matching.childId, - child: Users.pack(matching.childId, me, { - detail: true, - }), - }); - } -} - -export const packedReversiMatchingSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - parentId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - parent: { - type: 'object' as const, - optional: false as const, nullable: true as const, - ref: 'User' as const, - }, - childId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - child: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/server/api/endpoints/games/reversi/games.ts b/packages/backend/src/server/api/endpoints/games/reversi/games.ts deleted file mode 100644 index 8b0e812ca9..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/games.ts +++ /dev/null @@ -1,157 +0,0 @@ -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id'; -import define from '../../../define'; -import { ReversiGames } from '@/models/index'; -import { makePaginationQuery } from '../../../common/make-pagination-query'; -import { Brackets } from 'typeorm'; - -export const meta = { - tags: ['games'], - - params: { - limit: { - validator: $.optional.num.range(1, 100), - default: 10, - }, - - sinceId: { - validator: $.optional.type(ID), - }, - - untilId: { - validator: $.optional.type(ID), - }, - - my: { - validator: $.optional.bool, - default: false, - }, - }, - - res: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - startedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - isStarted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - isEnded: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - form1: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - form2: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - user1Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - user2Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - user1Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user2Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user1: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - user2: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - winnerId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - winner: { - type: 'object' as const, - optional: false as const, nullable: true as const, - ref: 'User', - }, - surrendered: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - black: { - type: 'number' as const, - optional: false as const, nullable: true as const, - }, - bw: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - isLlotheo: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - canPutEverywhere: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - loopedBoard: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - }, -}; - -// eslint-disable-next-line import/no-default-export -export default define(meta, async (ps, user) => { - const query = makePaginationQuery(ReversiGames.createQueryBuilder('game'), ps.sinceId, ps.untilId) - .andWhere('game.isStarted = TRUE'); - - if (ps.my && user) { - query.andWhere(new Brackets(qb => { qb - .where('game.user1Id = :userId', { userId: user.id }) - .orWhere('game.user2Id = :userId', { userId: user.id }); - })); - } - - // Fetch games - const games = await query.take(ps.limit!).getMany(); - - return await Promise.all(games.map((g) => ReversiGames.pack(g, user, { - detail: false, - }))); -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/games/show.ts b/packages/backend/src/server/api/endpoints/games/reversi/games/show.ts deleted file mode 100644 index 020e9e6fc2..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/games/show.ts +++ /dev/null @@ -1,169 +0,0 @@ -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id'; -import Reversi from '../../../../../../games/reversi/core'; -import define from '../../../../define'; -import { ApiError } from '../../../../error'; -import { ReversiGames } from '@/models/index'; - -export const meta = { - tags: ['games'], - - params: { - gameId: { - validator: $.type(ID), - }, - }, - - errors: { - noSuchGame: { - message: 'No such game.', - code: 'NO_SUCH_GAME', - id: 'f13a03db-fae1-46c9-87f3-43c8165419e1', - }, - }, - - res: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - startedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - isStarted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - isEnded: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - form1: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - form2: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - user1Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - user2Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - user1Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user2Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user1: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - user2: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - winnerId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - winner: { - type: 'object' as const, - optional: false as const, nullable: true as const, - ref: 'User', - }, - surrendered: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - black: { - type: 'number' as const, - optional: false as const, nullable: true as const, - }, - bw: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - isLlotheo: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - canPutEverywhere: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - loopedBoard: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - board: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'any' as const, - optional: false as const, nullable: false as const, - }, - }, - turn: { - type: 'any' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - }, -}; - -// eslint-disable-next-line import/no-default-export -export default define(meta, async (ps, user) => { - const game = await ReversiGames.findOne(ps.gameId); - - if (game == null) { - throw new ApiError(meta.errors.noSuchGame); - } - - const o = new Reversi(game.map, { - isLlotheo: game.isLlotheo, - canPutEverywhere: game.canPutEverywhere, - loopedBoard: game.loopedBoard, - }); - - for (const log of game.logs) { - o.put(log.color, log.pos); - } - - const packed = await ReversiGames.pack(game, user); - - return Object.assign({ - board: o.board, - turn: o.turn, - }, packed); -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/games/surrender.ts b/packages/backend/src/server/api/endpoints/games/reversi/games/surrender.ts deleted file mode 100644 index a0eb4705ba..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/games/surrender.ts +++ /dev/null @@ -1,68 +0,0 @@ -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id'; -import { publishReversiGameStream } from '@/services/stream'; -import define from '../../../../define'; -import { ApiError } from '../../../../error'; -import { ReversiGames } from '@/models/index'; - -export const meta = { - tags: ['games'], - - requireCredential: true as const, - - params: { - gameId: { - validator: $.type(ID), - }, - }, - - errors: { - noSuchGame: { - message: 'No such game.', - code: 'NO_SUCH_GAME', - id: 'ace0b11f-e0a6-4076-a30d-e8284c81b2df', - }, - - alreadyEnded: { - message: 'That game has already ended.', - code: 'ALREADY_ENDED', - id: '6c2ad4a6-cbf1-4a5b-b187-b772826cfc6d', - }, - - accessDenied: { - message: 'Access denied.', - code: 'ACCESS_DENIED', - id: '6e04164b-a992-4c93-8489-2123069973e1', - }, - }, -}; - -// eslint-disable-next-line import/no-default-export -export default define(meta, async (ps, user) => { - const game = await ReversiGames.findOne(ps.gameId); - - if (game == null) { - throw new ApiError(meta.errors.noSuchGame); - } - - if (game.isEnded) { - throw new ApiError(meta.errors.alreadyEnded); - } - - if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) { - throw new ApiError(meta.errors.accessDenied); - } - - const winnerId = game.user1Id === user.id ? game.user2Id : game.user1Id; - - await ReversiGames.update(game.id, { - surrendered: user.id, - isEnded: true, - winnerId: winnerId, - }); - - publishReversiGameStream(game.id, 'ended', { - winnerId: winnerId, - game: await ReversiGames.pack(game.id, user), - }); -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/invitations.ts b/packages/backend/src/server/api/endpoints/games/reversi/invitations.ts deleted file mode 100644 index 0285a2f63b..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/invitations.ts +++ /dev/null @@ -1,59 +0,0 @@ -import define from '../../../define'; -import { ReversiMatchings } from '@/models/index'; - -export const meta = { - tags: ['games'], - - requireCredential: true as const, - - res: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - parentId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - parent: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - childId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - child: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - }, - }, - }, -}; - -// eslint-disable-next-line import/no-default-export -export default define(meta, async (ps, user) => { - // Find session - const invitations = await ReversiMatchings.find({ - childId: user.id, - }); - - return await Promise.all(invitations.map((i) => ReversiMatchings.pack(i, user))); -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/match.ts b/packages/backend/src/server/api/endpoints/games/reversi/match.ts deleted file mode 100644 index b1d958306d..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/match.ts +++ /dev/null @@ -1,109 +0,0 @@ -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id'; -import { publishMainStream, publishReversiStream } from '@/services/stream'; -import { eighteight } from '../../../../../games/reversi/maps'; -import define from '../../../define'; -import { ApiError } from '../../../error'; -import { getUser } from '../../../common/getters'; -import { genId } from '@/misc/gen-id'; -import { ReversiMatchings, ReversiGames } from '@/models/index'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; -import { ReversiMatching } from '@/models/entities/games/reversi/matching'; - -export const meta = { - tags: ['games'], - - requireCredential: true as const, - - params: { - userId: { - validator: $.type(ID), - }, - }, - - errors: { - noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '0b4f0559-b484-4e31-9581-3f73cee89b28', - }, - - isYourself: { - message: 'Target user is yourself.', - code: 'TARGET_IS_YOURSELF', - id: '96fd7bd6-d2bc-426c-a865-d055dcd2828e', - }, - }, -}; - -// eslint-disable-next-line import/no-default-export -export default define(meta, async (ps, user) => { - // Myself - if (ps.userId === user.id) { - throw new ApiError(meta.errors.isYourself); - } - - // Find session - const exist = await ReversiMatchings.findOne({ - parentId: ps.userId, - childId: user.id, - }); - - if (exist) { - // Destroy session - ReversiMatchings.delete(exist.id); - - // Create game - const game = await ReversiGames.save({ - id: genId(), - createdAt: new Date(), - user1Id: exist.parentId, - user2Id: user.id, - user1Accepted: false, - user2Accepted: false, - isStarted: false, - isEnded: false, - logs: [], - map: eighteight.data, - bw: 'random', - isLlotheo: false, - } as Partial<ReversiGame>); - - publishReversiStream(exist.parentId, 'matched', await ReversiGames.pack(game, { id: exist.parentId })); - - const other = await ReversiMatchings.count({ - childId: user.id, - }); - - if (other == 0) { - publishMainStream(user.id, 'reversiNoInvites'); - } - - return await ReversiGames.pack(game, user); - } else { - // Fetch child - const child = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // 以前のセッションはすべて削除しておく - await ReversiMatchings.delete({ - parentId: user.id, - }); - - // セッションを作成 - const matching = await ReversiMatchings.save({ - id: genId(), - createdAt: new Date(), - parentId: user.id, - childId: child.id, - } as ReversiMatching); - - const packed = await ReversiMatchings.pack(matching, child); - publishReversiStream(child.id, 'invited', packed); - publishMainStream(child.id, 'reversiInvited', packed); - - return; - } -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/match/cancel.ts b/packages/backend/src/server/api/endpoints/games/reversi/match/cancel.ts deleted file mode 100644 index 3ef753bcd0..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/match/cancel.ts +++ /dev/null @@ -1,15 +0,0 @@ -import define from '../../../../define'; -import { ReversiMatchings } from '@/models/index'; - -export const meta = { - tags: ['games'], - - requireCredential: true as const, -}; - -// eslint-disable-next-line import/no-default-export -export default define(meta, async (ps, user) => { - await ReversiMatchings.delete({ - parentId: user.id, - }); -}); diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index bd62e7fb21..84bbd16f0b 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import define from '../../define'; import { ApiError } from '../../error'; import { ID } from '@/misc/cafy-id'; -import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, ReversiGames, Users } from '@/models/index'; +import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index'; export const meta = { tags: ['users'], @@ -50,7 +50,6 @@ export default define(meta, async (ps, me) => { pageLikedCount, driveFilesCount, driveUsage, - reversiCount, ] = await Promise.all([ Notes.createQueryBuilder('note') .where('note.userId = :userId', { userId: user.id }) @@ -113,10 +112,6 @@ export default define(meta, async (ps, me) => { .where('file.userId = :userId', { userId: user.id }) .getCount(), DriveFiles.calcDriveUsageOf(user), - ReversiGames.createQueryBuilder('game') - .where('game.user1Id = :userId', { userId: user.id }) - .orWhere('game.user2Id = :userId', { userId: user.id }) - .getCount(), ]); return { @@ -140,6 +135,5 @@ export default define(meta, async (ps, me) => { pageLikedCount, driveFilesCount, driveUsage, - reversiCount, }; }); diff --git a/packages/backend/src/server/api/stream/channels/games/reversi-game.ts b/packages/backend/src/server/api/stream/channels/games/reversi-game.ts deleted file mode 100644 index 314db48b5e..0000000000 --- a/packages/backend/src/server/api/stream/channels/games/reversi-game.ts +++ /dev/null @@ -1,372 +0,0 @@ -import autobind from 'autobind-decorator'; -import * as CRC32 from 'crc-32'; -import { publishReversiGameStream } from '@/services/stream'; -import Reversi from '../../../../../games/reversi/core'; -import * as maps from '../../../../../games/reversi/maps'; -import Channel from '../../channel'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; -import { ReversiGames, Users } from '@/models/index'; -import { User } from '@/models/entities/user'; - -export default class extends Channel { - public readonly chName = 'gamesReversiGame'; - public static shouldShare = false; - public static requireCredential = false; - - private gameId: ReversiGame['id'] | null = null; - private watchers: Record<User['id'], Date> = {}; - private emitWatchersIntervalId: ReturnType<typeof setInterval>; - - @autobind - public async init(params: any) { - this.gameId = params.gameId; - - // Subscribe game stream - this.subscriber.on(`reversiGameStream:${this.gameId}`, this.onEvent); - this.emitWatchersIntervalId = setInterval(this.emitWatchers, 5000); - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - // 観戦者イベント - this.watch(game); - } - - @autobind - private onEvent(data: any) { - if (data.type === 'watching') { - const id = data.body; - this.watchers[id] = new Date(); - } else { - this.send(data); - } - } - - @autobind - private async emitWatchers() { - const now = new Date(); - - // Remove not watching users - for (const [userId, date] of Object.entries(this.watchers)) { - if (now.getTime() - date.getTime() > 5000) delete this.watchers[userId]; - } - - const users = await Users.packMany(Object.keys(this.watchers), null, { detail: false }); - - this.send({ - type: 'watchers', - body: users, - }); - } - - @autobind - public dispose() { - // Unsubscribe events - this.subscriber.off(`reversiGameStream:${this.gameId}`, this.onEvent); - clearInterval(this.emitWatchersIntervalId); - } - - @autobind - public onMessage(type: string, body: any) { - switch (type) { - case 'accept': this.accept(true); break; - case 'cancelAccept': this.accept(false); break; - case 'updateSettings': this.updateSettings(body.key, body.value); break; - case 'initForm': this.initForm(body); break; - case 'updateForm': this.updateForm(body.id, body.value); break; - case 'message': this.message(body); break; - case 'set': this.set(body.pos); break; - case 'check': this.check(body.crc32); break; - } - } - - @autobind - private async updateSettings(key: string, value: any) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (game.isStarted) return; - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - if ((game.user1Id === this.user.id) && game.user1Accepted) return; - if ((game.user2Id === this.user.id) && game.user2Accepted) return; - - if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard'].includes(key)) return; - - await ReversiGames.update(this.gameId!, { - [key]: value, - }); - - publishReversiGameStream(this.gameId!, 'updateSettings', { - key: key, - value: value, - }); - } - - @autobind - private async initForm(form: any) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (game.isStarted) return; - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - - const set = game.user1Id === this.user.id ? { - form1: form, - } : { - form2: form, - }; - - await ReversiGames.update(this.gameId!, set); - - publishReversiGameStream(this.gameId!, 'initForm', { - userId: this.user.id, - form, - }); - } - - @autobind - private async updateForm(id: string, value: any) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (game.isStarted) return; - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - - const form = game.user1Id === this.user.id ? game.form2 : game.form1; - - const item = form.find((i: any) => i.id == id); - - if (item == null) return; - - item.value = value; - - const set = game.user1Id === this.user.id ? { - form2: form, - } : { - form1: form, - }; - - await ReversiGames.update(this.gameId!, set); - - publishReversiGameStream(this.gameId!, 'updateForm', { - userId: this.user.id, - id, - value, - }); - } - - @autobind - private async message(message: any) { - if (this.user == null) return; - - message.id = Math.random(); - publishReversiGameStream(this.gameId!, 'message', { - userId: this.user.id, - message, - }); - } - - @autobind - private async accept(accept: boolean) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (game.isStarted) return; - - let bothAccepted = false; - - if (game.user1Id === this.user.id) { - await ReversiGames.update(this.gameId!, { - user1Accepted: accept, - }); - - publishReversiGameStream(this.gameId!, 'changeAccepts', { - user1: accept, - user2: game.user2Accepted, - }); - - if (accept && game.user2Accepted) bothAccepted = true; - } else if (game.user2Id === this.user.id) { - await ReversiGames.update(this.gameId!, { - user2Accepted: accept, - }); - - publishReversiGameStream(this.gameId!, 'changeAccepts', { - user1: game.user1Accepted, - user2: accept, - }); - - if (accept && game.user1Accepted) bothAccepted = true; - } else { - return; - } - - if (bothAccepted) { - // 3秒後、まだacceptされていたらゲーム開始 - setTimeout(async () => { - const freshGame = await ReversiGames.findOne(this.gameId!); - if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return; - if (!freshGame.user1Accepted || !freshGame.user2Accepted) return; - - let bw: number; - if (freshGame.bw == 'random') { - bw = Math.random() > 0.5 ? 1 : 2; - } else { - bw = parseInt(freshGame.bw, 10); - } - - function getRandomMap() { - const mapCount = Object.entries(maps).length; - const rnd = Math.floor(Math.random() * mapCount); - return Object.values(maps)[rnd].data; - } - - const map = freshGame.map != null ? freshGame.map : getRandomMap(); - - await ReversiGames.update(this.gameId!, { - startedAt: new Date(), - isStarted: true, - black: bw, - map: map, - }); - - //#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理 - const o = new Reversi(map, { - isLlotheo: freshGame.isLlotheo, - canPutEverywhere: freshGame.canPutEverywhere, - loopedBoard: freshGame.loopedBoard, - }); - - if (o.isEnded) { - let winner; - if (o.winner === true) { - winner = freshGame.black == 1 ? freshGame.user1Id : freshGame.user2Id; - } else if (o.winner === false) { - winner = freshGame.black == 1 ? freshGame.user2Id : freshGame.user1Id; - } else { - winner = null; - } - - await ReversiGames.update(this.gameId!, { - isEnded: true, - winnerId: winner, - }); - - publishReversiGameStream(this.gameId!, 'ended', { - winnerId: winner, - game: await ReversiGames.pack(this.gameId!, this.user), - }); - } - //#endregion - - publishReversiGameStream(this.gameId!, 'started', - await ReversiGames.pack(this.gameId!, this.user)); - }, 3000); - } - } - - // 石を打つ - @autobind - private async set(pos: number) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (!game.isStarted) return; - if (game.isEnded) return; - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - - const myColor = - ((game.user1Id === this.user.id) && game.black == 1) || ((game.user2Id === this.user.id) && game.black == 2) - ? true - : false; - - const o = new Reversi(game.map, { - isLlotheo: game.isLlotheo, - canPutEverywhere: game.canPutEverywhere, - loopedBoard: game.loopedBoard, - }); - - // 盤面の状態を再生 - for (const log of game.logs) { - o.put(log.color, log.pos); - } - - if (o.turn !== myColor) return; - - if (!o.canPut(myColor, pos)) return; - o.put(myColor, pos); - - let winner; - if (o.isEnded) { - if (o.winner === true) { - winner = game.black == 1 ? game.user1Id : game.user2Id; - } else if (o.winner === false) { - winner = game.black == 1 ? game.user2Id : game.user1Id; - } else { - winner = null; - } - } - - const log = { - at: new Date(), - color: myColor, - pos, - }; - - const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()).toString(); - - game.logs.push(log); - - await ReversiGames.update(this.gameId!, { - crc32, - isEnded: o.isEnded, - winnerId: winner, - logs: game.logs, - }); - - publishReversiGameStream(this.gameId!, 'set', Object.assign(log, { - next: o.turn, - })); - - if (o.isEnded) { - publishReversiGameStream(this.gameId!, 'ended', { - winnerId: winner, - game: await ReversiGames.pack(this.gameId!, this.user), - }); - } - } - - @autobind - private async check(crc32: string | number) { - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (!game.isStarted) return; - - if (crc32.toString() !== game.crc32) { - this.send('rescue', await ReversiGames.pack(game, this.user)); - } - - // ついでに観戦者イベントを発行 - this.watch(game); - } - - @autobind - private watch(game: ReversiGame) { - if (this.user != null) { - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) { - publishReversiGameStream(this.gameId!, 'watching', this.user.id); - } - } - } -} diff --git a/packages/backend/src/server/api/stream/channels/games/reversi.ts b/packages/backend/src/server/api/stream/channels/games/reversi.ts deleted file mode 100644 index 121560ff87..0000000000 --- a/packages/backend/src/server/api/stream/channels/games/reversi.ts +++ /dev/null @@ -1,34 +0,0 @@ -import autobind from 'autobind-decorator'; -import { publishMainStream } from '@/services/stream'; -import Channel from '../../channel'; -import { ReversiMatchings } from '@/models/index'; - -export default class extends Channel { - public readonly chName = 'gamesReversi'; - public static shouldShare = true; - public static requireCredential = true; - - @autobind - public async init(params: any) { - // Subscribe reversi stream - this.subscriber.on(`reversiStream:${this.user!.id}`, data => { - this.send(data); - }); - } - - @autobind - public async onMessage(type: string, body: any) { - switch (type) { - case 'ping': { - if (body.id == null) return; - const matching = await ReversiMatchings.findOne({ - parentId: this.user!.id, - childId: body.id, - }); - if (matching == null) return; - publishMainStream(matching.childId, 'reversiInvited', await ReversiMatchings.pack(matching, { id: matching.childId })); - break; - } - } - } -} diff --git a/packages/backend/src/server/api/stream/channels/index.ts b/packages/backend/src/server/api/stream/channels/index.ts index 89d93f2da3..f3826c4cf7 100644 --- a/packages/backend/src/server/api/stream/channels/index.ts +++ b/packages/backend/src/server/api/stream/channels/index.ts @@ -13,8 +13,6 @@ import drive from './drive'; import hashtag from './hashtag'; import channel from './channel'; import admin from './admin'; -import gamesReversi from './games/reversi'; -import gamesReversiGame from './games/reversi-game'; export default { main, @@ -32,6 +30,4 @@ export default { hashtag, channel, admin, - gamesReversi, - gamesReversiGame, }; diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index f4302f64a0..168f1b3448 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -11,7 +11,6 @@ import { Emoji } from '@/models/entities/emoji'; import { UserList } from '@/models/entities/user-list'; import { MessagingMessage } from '@/models/entities/messaging-message'; import { UserGroup } from '@/models/entities/user-group'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; import { AbuseUserReport } from '@/models/entities/abuse-user-report'; import { Signin } from '@/models/entities/signin'; import { Page } from '@/models/entities/page'; @@ -77,8 +76,6 @@ export interface MainStreamTypes { readAllChannels: undefined; unreadChannel: Note['id']; myTokenRegenerated: undefined; - reversiNoInvites: undefined; - reversiInvited: Packed<'ReversiMatching'>; signin: Signin; registryUpdated: { scope?: string[]; @@ -158,47 +155,6 @@ export interface MessagingIndexStreamTypes { message: Packed<'MessagingMessage'>; } -export interface ReversiStreamTypes { - matched: Packed<'ReversiGame'>; - invited: Packed<'ReversiMatching'>; -} - -export interface ReversiGameStreamTypes { - started: Packed<'ReversiGame'>; - ended: { - winnerId?: User['id'] | null, - game: Packed<'ReversiGame'>; - }; - updateSettings: { - key: string; - value: FIXME; - }; - initForm: { - userId: User['id']; - form: FIXME; - }; - updateForm: { - userId: User['id']; - id: string; - value: FIXME; - }; - message: { - userId: User['id']; - message: FIXME; - }; - changeAccepts: { - user1: boolean; - user2: boolean; - }; - set: { - at: Date; - color: boolean; - pos: number; - next: boolean; - }; - watching: User['id']; -} - export interface AdminStreamTypes { newAbuseUserReport: { id: AbuseUserReport['id']; @@ -268,14 +224,6 @@ export type StreamMessages = { name: `messagingIndexStream:${User['id']}`; payload: EventUnionFromDictionary<MessagingIndexStreamTypes>; }; - reversi: { - name: `reversiStream:${User['id']}`; - payload: EventUnionFromDictionary<ReversiStreamTypes>; - }; - reversiGame: { - name: `reversiGameStream:${ReversiGame['id']}`; - payload: EventUnionFromDictionary<ReversiGameStreamTypes>; - }; admin: { name: `adminStream:${User['id']}`; payload: EventUnionFromDictionary<AdminStreamTypes>; diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index 5623f7db83..e95a115aec 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -390,9 +390,6 @@ router.get('/cli', async ctx => { const override = (source: string, target: string, depth: number = 0) => [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); -router.get('/othello', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games/reversi', 1))); -router.get('/reversi', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games'))); - router.get('/flush', async ctx => { await ctx.render('flush'); }); diff --git a/packages/backend/src/services/stream.ts b/packages/backend/src/services/stream.ts index 7d11ff3f45..c0cefe9af4 100644 --- a/packages/backend/src/services/stream.ts +++ b/packages/backend/src/services/stream.ts @@ -2,7 +2,6 @@ import { redisClient } from '../db/redis'; import { User } from '@/models/entities/user'; import { Note } from '@/models/entities/note'; import { UserList } from '@/models/entities/user-list'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; import { UserGroup } from '@/models/entities/user-group'; import config from '@/config/index'; import { Antenna } from '@/models/entities/antenna'; @@ -20,8 +19,6 @@ import { MessagingIndexStreamTypes, MessagingStreamTypes, NoteStreamTypes, - ReversiGameStreamTypes, - ReversiStreamTypes, UserListStreamTypes, UserStreamTypes, } from '@/server/api/stream/types'; @@ -90,14 +87,6 @@ class Publisher { this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value); }; - public publishReversiStream = <K extends keyof ReversiStreamTypes>(userId: User['id'], type: K, value?: ReversiStreamTypes[K]): void => { - this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value); - }; - - public publishReversiGameStream = <K extends keyof ReversiGameStreamTypes>(gameId: ReversiGame['id'], type: K, value?: ReversiGameStreamTypes[K]): void => { - this.publish(`reversiGameStream:${gameId}`, type, typeof value === 'undefined' ? null : value); - }; - public publishNotesStream = (note: Packed<'Note'>): void => { this.publish('notesStream', null, note); }; @@ -124,6 +113,4 @@ export const publishAntennaStream = publisher.publishAntennaStream; export const publishMessagingStream = publisher.publishMessagingStream; export const publishGroupMessagingStream = publisher.publishGroupMessagingStream; export const publishMessagingIndexStream = publisher.publishMessagingIndexStream; -export const publishReversiStream = publisher.publishReversiStream; -export const publishReversiGameStream = publisher.publishReversiGameStream; export const publishAdminStream = publisher.publishAdminStream; diff --git a/packages/client/src/menu.ts b/packages/client/src/menu.ts index 98a892d569..184779f21f 100644 --- a/packages/client/src/menu.ts +++ b/packages/client/src/menu.ts @@ -163,11 +163,6 @@ export const menuDef = reactive({ icon: 'fas fa-laugh', to: '/emojis', }, - games: { - title: 'games', - icon: 'fas fa-gamepad', - to: '/games/reversi', - }, scratchpad: { title: 'scratchpad', icon: 'fas fa-terminal', diff --git a/packages/client/src/pages/reversi/game.board.vue b/packages/client/src/pages/reversi/game.board.vue deleted file mode 100644 index eb6fef2799..0000000000 --- a/packages/client/src/pages/reversi/game.board.vue +++ /dev/null @@ -1,528 +0,0 @@ -<template> -<div class="xqnhankfuuilcwvhgsopeqncafzsquya"> - <header><b><MkA :to="userPage(blackUser)"><MkUserName :user="blackUser"/></MkA></b>({{ $ts._reversi.black }}) vs <b><MkA :to="userPage(whiteUser)"><MkUserName :user="whiteUser"/></MkA></b>({{ $ts._reversi.white }})</header> - - <div style="overflow: hidden; line-height: 28px;"> - <p v-if="!iAmPlayer && !game.isEnded" class="turn"> - <Mfm :key="'turn:' + turnUser().name" :text="$t('_reversi.turnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/> - <MkEllipsis/> - </p> - <p v-if="logPos != logs.length" class="turn"> - <Mfm :key="'past-turn-of:' + turnUser().name" :text="$t('_reversi.pastTurnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/> - </p> - <p v-if="iAmPlayer && !game.isEnded && !isMyTurn()" class="turn1">{{ $ts._reversi.opponentTurn }}<MkEllipsis/></p> - <p v-if="iAmPlayer && !game.isEnded && isMyTurn()" class="turn2" style="animation: tada 1s linear infinite both;">{{ $ts._reversi.myTurn }}</p> - <p v-if="game.isEnded && logPos == logs.length" class="result"> - <template v-if="game.winner"> - <Mfm :key="'won'" :text="$t('_reversi.won', { name: game.winner.name })" :plain="true" :custom-emojis="game.winner.emojis"/> - <span v-if="game.surrendered != null"> ({{ $ts._reversi.surrendered }})</span> - </template> - <template v-else>{{ $ts._reversi.drawn }}</template> - </p> - </div> - - <div class="board"> - <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-x"> - <span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span> - </div> - <div class="flex"> - <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-y"> - <div v-for="i in game.map.length">{{ i }}</div> - </div> - <div class="cells" :style="cellsStyle"> - <div v-for="(stone, i) in o.board" - :class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn(), can: turnUser() ? o.canPut(turnUser().id == blackUser.id, i) : null, prev: o.prevPos == i }" - :title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`" - @click="set(i)" - > - <template v-if="$store.state.gamesReversiUseAvatarStones || true"> - <img v-if="stone === true" :src="blackUser.avatarUrl" alt="black"> - <img v-if="stone === false" :src="whiteUser.avatarUrl" alt="white"> - </template> - <template v-else> - <i v-if="stone === true" class="fas fa-circle"></i> - <i v-if="stone === false" class="far fa-circle"></i> - </template> - </div> - </div> - <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-y"> - <div v-for="i in game.map.length">{{ i }}</div> - </div> - </div> - <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-x"> - <span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span> - </div> - </div> - - <p class="status"><b>{{ $t('_reversi.turnCount', { count: logPos }) }}</b> {{ $ts._reversi.black }}:{{ o.blackCount }} {{ $ts._reversi.white }}:{{ o.whiteCount }} {{ $ts._reversi.total }}:{{ o.blackCount + o.whiteCount }}</p> - - <div v-if="!game.isEnded && iAmPlayer" class="actions"> - <MkButton inline @click="surrender">{{ $ts._reversi.surrender }}</MkButton> - </div> - - <div v-if="game.isEnded" class="player"> - <span>{{ logPos }} / {{ logs.length }}</span> - <div v-if="!autoplaying" class="buttons"> - <MkButton inline :disabled="logPos == 0" @click="logPos = 0"><i class="fas fa-angle-double-left"></i></MkButton> - <MkButton inline :disabled="logPos == 0" @click="logPos--"><i class="fas fa-angle-left"></i></MkButton> - <MkButton inline :disabled="logPos == logs.length" @click="logPos++"><i class="fas fa-angle-right"></i></MkButton> - <MkButton inline :disabled="logPos == logs.length" @click="logPos = logs.length"><i class="fas fa-angle-double-right"></i></MkButton> - </div> - <MkButton :disabled="autoplaying" style="margin: var(--margin) auto 0 auto;" @click="autoplay()"><i class="fas fa-play"></i></MkButton> - </div> - - <div class="info"> - <p v-if="game.isLlotheo">{{ $ts._reversi.isLlotheo }}</p> - <p v-if="game.loopedBoard">{{ $ts._reversi.loopedMap }}</p> - <p v-if="game.canPutEverywhere">{{ $ts._reversi.canPutEverywhere }}</p> - </div> - - <div class="watchers"> - <MkAvatar v-for="user in watchers" :key="user.id" :user="user" class="avatar"/> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as CRC32 from 'crc-32'; -import Reversi, { Color } from '@/scripts/games/reversi/core'; -import { url } from '@/config'; -import MkButton from '@/components/ui/button.vue'; -import { userPage } from '@/filters/user'; -import * as os from '@/os'; -import * as sound from '@/scripts/sound'; - -export default defineComponent({ - components: { - MkButton - }, - - props: { - initGame: { - type: Object, - require: true - }, - connection: { - type: Object, - require: true - }, - }, - - data() { - return { - game: JSON.parse(JSON.stringify(this.initGame)), - o: null as Reversi, - logs: [], - logPos: 0, - watchers: [], - pollingClock: null, - }; - }, - - computed: { - iAmPlayer(): boolean { - if (!this.$i) return false; - return this.game.user1Id == this.$i.id || this.game.user2Id == this.$i.id; - }, - - myColor(): Color { - if (!this.iAmPlayer) return null; - if (this.game.user1Id == this.$i.id && this.game.black == 1) return true; - if (this.game.user2Id == this.$i.id && this.game.black == 2) return true; - return false; - }, - - opColor(): Color { - if (!this.iAmPlayer) return null; - return this.myColor === true ? false : true; - }, - - blackUser(): any { - return this.game.black == 1 ? this.game.user1 : this.game.user2; - }, - - whiteUser(): any { - return this.game.black == 1 ? this.game.user2 : this.game.user1; - }, - - cellsStyle(): any { - return { - 'grid-template-rows': `repeat(${this.game.map.length}, 1fr)`, - 'grid-template-columns': `repeat(${this.game.map[0].length}, 1fr)` - }; - } - }, - - watch: { - logPos(v) { - if (!this.game.isEnded) return; - const o = new Reversi(this.game.map, { - isLlotheo: this.game.isLlotheo, - canPutEverywhere: this.game.canPutEverywhere, - loopedBoard: this.game.loopedBoard - }); - for (const log of this.logs.slice(0, v)) { - o.put(log.color, log.pos); - } - this.o = o; - //this.$forceUpdate(); - } - }, - - created() { - this.o = new Reversi(this.game.map, { - isLlotheo: this.game.isLlotheo, - canPutEverywhere: this.game.canPutEverywhere, - loopedBoard: this.game.loopedBoard - }); - - for (const log of this.game.logs) { - this.o.put(log.color, log.pos); - } - - this.logs = this.game.logs; - this.logPos = this.logs.length; - - // 通信を取りこぼしてもいいように定期的にポーリングさせる - if (this.game.isStarted && !this.game.isEnded) { - this.pollingClock = setInterval(() => { - if (this.game.isEnded) return; - const crc32 = CRC32.str(this.logs.map(x => x.pos.toString()).join('')); - this.connection.send('check', { - crc32: crc32 - }); - }, 3000); - } - }, - - mounted() { - this.connection.on('set', this.onSet); - this.connection.on('rescue', this.onRescue); - this.connection.on('ended', this.onEnded); - this.connection.on('watchers', this.onWatchers); - }, - - beforeUnmount() { - this.connection.off('set', this.onSet); - this.connection.off('rescue', this.onRescue); - this.connection.off('ended', this.onEnded); - this.connection.off('watchers', this.onWatchers); - - clearInterval(this.pollingClock); - }, - - methods: { - userPage, - - // this.o がリアクティブになった折にはcomputedにできる - turnUser(): any { - if (this.o.turn === true) { - return this.game.black == 1 ? this.game.user1 : this.game.user2; - } else if (this.o.turn === false) { - return this.game.black == 1 ? this.game.user2 : this.game.user1; - } else { - return null; - } - }, - - // this.o がリアクティブになった折にはcomputedにできる - isMyTurn(): boolean { - if (!this.iAmPlayer) return false; - if (this.turnUser() == null) return false; - return this.turnUser().id == this.$i.id; - }, - - set(pos) { - if (this.game.isEnded) return; - if (!this.iAmPlayer) return; - if (!this.isMyTurn()) return; - if (!this.o.canPut(this.myColor, pos)) return; - - this.o.put(this.myColor, pos); - - // サウンドを再生する - sound.play(this.myColor ? 'reversiPutBlack' : 'reversiPutWhite'); - - this.connection.send('set', { - pos: pos - }); - - this.checkEnd(); - - this.$forceUpdate(); - }, - - onSet(x) { - this.logs.push(x); - this.logPos++; - this.o.put(x.color, x.pos); - this.checkEnd(); - this.$forceUpdate(); - - // サウンドを再生する - if (x.color !== this.myColor) { - sound.play(x.color ? 'reversiPutBlack' : 'reversiPutWhite'); - } - }, - - onEnded(x) { - this.game = JSON.parse(JSON.stringify(x.game)); - }, - - checkEnd() { - this.game.isEnded = this.o.isEnded; - if (this.game.isEnded) { - if (this.o.winner === true) { - this.game.winnerId = this.game.black == 1 ? this.game.user1Id : this.game.user2Id; - this.game.winner = this.game.black == 1 ? this.game.user1 : this.game.user2; - } else if (this.o.winner === false) { - this.game.winnerId = this.game.black == 1 ? this.game.user2Id : this.game.user1Id; - this.game.winner = this.game.black == 1 ? this.game.user2 : this.game.user1; - } else { - this.game.winnerId = null; - this.game.winner = null; - } - } - }, - - // 正しいゲーム情報が送られてきたとき - onRescue(game) { - this.game = JSON.parse(JSON.stringify(game)); - - this.o = new Reversi(this.game.map, { - isLlotheo: this.game.isLlotheo, - canPutEverywhere: this.game.canPutEverywhere, - loopedBoard: this.game.loopedBoard - }); - - for (const log of this.game.logs) { - this.o.put(log.color, log.pos, true); - } - - this.logs = this.game.logs; - this.logPos = this.logs.length; - - this.checkEnd(); - this.$forceUpdate(); - }, - - onWatchers(users) { - this.watchers = users; - }, - - surrender() { - os.api('games/reversi/games/surrender', { - gameId: this.game.id - }); - }, - - autoplay() { - this.autoplaying = true; - this.logPos = 0; - - setTimeout(() => { - this.logPos = 1; - - let i = 1; - let previousLog = this.game.logs[0]; - const tick = () => { - const log = this.game.logs[i]; - const time = new Date(log.at).getTime() - new Date(previousLog.at).getTime() - setTimeout(() => { - i++; - this.logPos++; - previousLog = log; - - if (i < this.game.logs.length) { - tick(); - } else { - this.autoplaying = false; - } - }, time); - }; - - tick(); - }, 1000); - } - } -}); -</script> - -<style lang="scss" scoped> - -@use "sass:math"; - -.xqnhankfuuilcwvhgsopeqncafzsquya { - text-align: center; - - > .go-index { - position: absolute; - top: 0; - left: 0; - z-index: 1; - width: 42px; - height :42px; - } - - > header { - padding: 8px; - border-bottom: dashed 1px var(--divider); - } - - > .board { - width: calc(100% - 16px); - max-width: 500px; - margin: 0 auto; - - $label-size: 16px; - $gap: 4px; - - > .labels-x { - height: $label-size; - padding: 0 $label-size; - display: flex; - - > * { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - font-size: 0.8em; - - &:first-child { - margin-left: -(math.div($gap, 2)); - } - - &:last-child { - margin-right: -(math.div($gap, 2)); - } - } - } - - > .flex { - display: flex; - - > .labels-y { - width: $label-size; - display: flex; - flex-direction: column; - - > * { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - font-size: 12px; - - &:first-child { - margin-top: -(math.div($gap, 2)); - } - - &:last-child { - margin-bottom: -(math.div($gap, 2)); - } - } - } - - > .cells { - flex: 1; - display: grid; - grid-gap: $gap; - - > div { - background: transparent; - border-radius: 6px; - overflow: hidden; - - * { - pointer-events: none; - user-select: none; - } - - &.empty { - border: solid 2px var(--divider); - } - - &.empty.can { - border-color: var(--accent); - } - - &.empty.myTurn { - border-color: var(--divider); - - &.can { - border-color: var(--accent); - cursor: pointer; - - &:hover { - background: var(--accent); - } - } - } - - &.prev { - box-shadow: 0 0 0 4px var(--accent); - } - - &.isEnded { - border-color: var(--divider); - } - - &.none { - border-color: transparent !important; - } - - > svg, > img { - display: block; - width: 100%; - height: 100%; - } - } - } - } - } - - > .status { - margin: 0; - padding: 16px 0; - } - - > .actions { - padding-bottom: 16px; - } - - > .player { - padding: 0 16px 32px 16px; - margin: 0 auto; - max-width: 500px; - - > span { - display: inline-block; - margin: 0 8px; - min-width: 70px; - } - - > .buttons { - display: flex; - - > * { - flex: 1; - } - } - } - - > .watchers { - padding: 0 0 16px 0; - - &:empty { - display: none; - } - - > .avatar { - width: 32px; - height: 32px; - } - } -} -</style> diff --git a/packages/client/src/pages/reversi/game.setting.vue b/packages/client/src/pages/reversi/game.setting.vue deleted file mode 100644 index 28bc598cfd..0000000000 --- a/packages/client/src/pages/reversi/game.setting.vue +++ /dev/null @@ -1,390 +0,0 @@ -<template> -<div class="urbixznjwwuukfsckrwzwsqzsxornqij"> - <header><b><MkUserName :user="game.user1"/></b> vs <b><MkUserName :user="game.user2"/></b></header> - - <div> - <p>{{ $ts._reversi.gameSettings }}</p> - - <div class="card map _panel"> - <header> - <select v-model="mapName" :placeholder="$ts._reversi.chooseBoard" @change="onMapChange"> - <option v-if="mapName == '-Custom-'" label="-Custom-" :value="mapName"/> - <option :label="$ts.random" :value="null"/> - <optgroup v-for="c in mapCategories" :key="c" :label="c"> - <option v-for="m in Object.values(maps).filter(m => m.category == c)" :key="m.name" :label="m.name" :value="m.name">{{ m.name }}</option> - </optgroup> - </select> - </header> - - <div> - <div v-if="game.map == null" class="random"><i class="fas fa-dice"></i></div> - <div v-else class="board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }"> - <div v-for="(x, i) in game.map.join('')" :class="{ none: x == ' ' }" @click="onPixelClick(i, x)"> - <i v-if="x === 'b'" class="fas fa-circle"></i> - <i v-if="x === 'w'" class="far fa-circle"></i> - </div> - </div> - </div> - </div> - - <div class="card _panel"> - <header> - <span>{{ $ts._reversi.blackOrWhite }}</span> - </header> - - <div> - <MkRadio v-model="game.bw" value="random" @update:modelValue="updateSettings('bw')">{{ $ts.random }}</MkRadio> - <MkRadio v-model="game.bw" :value="'1'" @update:modelValue="updateSettings('bw')"> - <I18n :src="$ts._reversi.blackIs" tag="span"> - <template #name> - <b><MkUserName :user="game.user1"/></b> - </template> - </I18n> - </MkRadio> - <MkRadio v-model="game.bw" :value="'2'" @update:modelValue="updateSettings('bw')"> - <I18n :src="$ts._reversi.blackIs" tag="span"> - <template #name> - <b><MkUserName :user="game.user2"/></b> - </template> - </I18n> - </MkRadio> - </div> - </div> - - <div class="card _panel"> - <header> - <span>{{ $ts._reversi.rules }}</span> - </header> - - <div> - <MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ $ts._reversi.isLlotheo }}</MkSwitch> - <MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ $ts._reversi.loopedMap }}</MkSwitch> - <MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ $ts._reversi.canPutEverywhere }}</MkSwitch> - </div> - </div> - - <div v-if="form" class="card form _panel"> - <header> - <span>{{ $ts._reversi.botSettings }}</span> - </header> - - <div> - <template v-for="item in form"> - <MkSwitch v-if="item.type == 'switch'" :key="item.id" v-model="item.value" @change="onChangeForm(item)">{{ item.label || item.desc || '' }}</MkSwitch> - - <div v-if="item.type == 'radio'" :key="item.id" class="card"> - <header> - <span>{{ item.label }}</span> - </header> - - <div> - <MkRadio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :value="r.value" @update:modelValue="onChangeForm(item)">{{ r.label }}</MkRadio> - </div> - </div> - - <div v-if="item.type == 'slider'" :key="item.id" class="card"> - <header> - <span>{{ item.label }}</span> - </header> - - <div> - <input v-model="item.value" type="range" :min="item.min" :max="item.max" :step="item.step || 1" @change="onChangeForm(item)"/> - </div> - </div> - - <div v-if="item.type == 'textbox'" :key="item.id" class="card"> - <header> - <span>{{ item.label }}</span> - </header> - - <div> - <input v-model="item.value" @change="onChangeForm(item)"/> - </div> - </div> - </template> - </div> - </div> - </div> - - <footer class="_acrylic"> - <p class="status"> - <template v-if="isAccepted && isOpAccepted">{{ $ts._reversi.thisGameIsStartedSoon }}<MkEllipsis/></template> - <template v-if="isAccepted && !isOpAccepted">{{ $ts._reversi.waitingForOther }}<MkEllipsis/></template> - <template v-if="!isAccepted && isOpAccepted">{{ $ts._reversi.waitingForMe }}</template> - <template v-if="!isAccepted && !isOpAccepted">{{ $ts._reversi.waitingBoth }}<MkEllipsis/></template> - </p> - - <div class="actions"> - <MkButton inline @click="exit">{{ $ts.cancel }}</MkButton> - <MkButton v-if="!isAccepted" inline primary @click="accept">{{ $ts._reversi.ready }}</MkButton> - <MkButton v-if="isAccepted" inline primary @click="cancel">{{ $ts._reversi.cancelReady }}</MkButton> - </div> - </footer> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as maps from '@/scripts/games/reversi/maps'; -import MkButton from '@/components/ui/button.vue'; -import MkSwitch from '@/components/form/switch.vue'; -import MkRadio from '@/components/form/radio.vue'; - -export default defineComponent({ - components: { - MkButton, - MkSwitch, - MkRadio, - }, - - props: { - initGame: { - type: Object, - require: true - }, - connection: { - type: Object, - require: true - }, - }, - - data() { - return { - game: this.initGame, - o: null, - isLlotheo: false, - mapName: maps.eighteight.name, - maps: maps, - form: null, - messages: [], - }; - }, - - computed: { - mapCategories(): string[] { - const categories = Object.values(maps).map(x => x.category); - return categories.filter((item, pos) => categories.indexOf(item) == pos); - }, - isAccepted(): boolean { - if (this.game.user1Id == this.$i.id && this.game.user1Accepted) return true; - if (this.game.user2Id == this.$i.id && this.game.user2Accepted) return true; - return false; - }, - isOpAccepted(): boolean { - if (this.game.user1Id != this.$i.id && this.game.user1Accepted) return true; - if (this.game.user2Id != this.$i.id && this.game.user2Accepted) return true; - return false; - } - }, - - created() { - this.connection.on('changeAccepts', this.onChangeAccepts); - this.connection.on('updateSettings', this.onUpdateSettings); - this.connection.on('initForm', this.onInitForm); - this.connection.on('message', this.onMessage); - - if (this.game.user1Id != this.$i.id && this.game.form1) this.form = this.game.form1; - if (this.game.user2Id != this.$i.id && this.game.form2) this.form = this.game.form2; - }, - - beforeUnmount() { - this.connection.off('changeAccepts', this.onChangeAccepts); - this.connection.off('updateSettings', this.onUpdateSettings); - this.connection.off('initForm', this.onInitForm); - this.connection.off('message', this.onMessage); - }, - - methods: { - exit() { - - }, - - accept() { - this.connection.send('accept', {}); - }, - - cancel() { - this.connection.send('cancelAccept', {}); - }, - - onChangeAccepts(accepts) { - this.game.user1Accepted = accepts.user1; - this.game.user2Accepted = accepts.user2; - }, - - updateSettings(key: string) { - this.connection.send('updateSettings', { - key: key, - value: this.game[key] - }); - }, - - onUpdateSettings({ key, value }) { - this.game[key] = value; - if (this.game.map == null) { - this.mapName = null; - } else { - const found = Object.values(maps).find(x => x.data.join('') == this.game.map.join('')); - this.mapName = found ? found.name : '-Custom-'; - } - }, - - onInitForm(x) { - if (x.userId == this.$i.id) return; - this.form = x.form; - }, - - onMessage(x) { - if (x.userId == this.$i.id) return; - this.messages.unshift(x.message); - }, - - onChangeForm(item) { - this.connection.send('updateForm', { - id: item.id, - value: item.value - }); - }, - - onMapChange() { - if (this.mapName == null) { - this.game.map = null; - } else { - this.game.map = Object.values(maps).find(x => x.name == this.mapName).data; - } - this.updateSettings('map'); - }, - - onPixelClick(pos, pixel) { - const x = pos % this.game.map[0].length; - const y = Math.floor(pos / this.game.map[0].length); - const newPixel = - pixel == ' ' ? '-' : - pixel == '-' ? 'b' : - pixel == 'b' ? 'w' : - ' '; - const line = this.game.map[y].split(''); - line[x] = newPixel; - this.game.map[y] = line.join(''); - this.updateSettings('map'); - } - } -}); -</script> - -<style lang="scss" scoped> -.urbixznjwwuukfsckrwzwsqzsxornqij { - text-align: center; - background: var(--bg); - - > header { - padding: 8px; - border-bottom: dashed 1px #c4cdd4; - } - - > div { - padding: 0 16px; - - > .card { - margin: 0 auto 16px auto; - - &.map { - > header { - > select { - width: 100%; - padding: 12px 14px; - background: var(--face); - border: 1px solid var(--inputBorder); - border-radius: 4px; - color: var(--fg); - cursor: pointer; - transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - - &:focus-visible, - &:active { - border-color: var(--accent); - } - } - } - - > div { - > .random { - padding: 32px 0; - font-size: 64px; - color: var(--fg); - opacity: 0.7; - } - - > .board { - display: grid; - grid-gap: 4px; - width: 300px; - height: 300px; - margin: 0 auto; - color: var(--fg); - - > div { - background: transparent; - border: solid 2px var(--divider); - border-radius: 6px; - overflow: hidden; - cursor: pointer; - - * { - pointer-events: none; - user-select: none; - width: 100%; - height: 100%; - } - - &.none { - border-color: transparent; - } - } - } - } - } - - &.form { - > div { - > .card + .card { - margin-top: 16px; - } - - input[type='range'] { - width: 100%; - } - } - } - } - - .card { - max-width: 400px; - - > header { - padding: 18px 20px; - border-bottom: 1px solid var(--divider); - } - - > div { - padding: 20px; - color: var(--fg); - } - } - } - - > footer { - position: sticky; - bottom: 0; - padding: 16px; - border-top: solid 1px var(--divider); - - > .status { - margin: 0 0 16px 0; - } - } -} -</style> diff --git a/packages/client/src/pages/reversi/game.vue b/packages/client/src/pages/reversi/game.vue deleted file mode 100644 index 697d2898b9..0000000000 --- a/packages/client/src/pages/reversi/game.vue +++ /dev/null @@ -1,77 +0,0 @@ -<template> -<div v-if="game == null"><MkLoading/></div> -<GameSetting v-else-if="!game.isStarted" :init-game="game" :connection="connection"/> -<GameBoard v-else :init-game="game" :connection="connection"/> -</template> - -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import GameSetting from './game.setting.vue'; -import GameBoard from './game.board.vue'; -import * as os from '@/os'; -import { stream } from '@/stream'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - GameSetting, - GameBoard, - }, - - props: { - gameId: { - type: String, - required: true - }, - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._reversi.reversi, - icon: 'fas fa-gamepad' - }, - game: null, - connection: null, - }; - }, - - watch: { - gameId() { - this.fetch(); - } - }, - - mounted() { - this.fetch(); - }, - - beforeUnmount() { - if (this.connection) { - this.connection.dispose(); - } - }, - - methods: { - fetch() { - os.api('games/reversi/games/show', { - gameId: this.gameId - }).then(game => { - this.game = game; - - if (this.connection) { - this.connection.dispose(); - } - this.connection = markRaw(stream.useChannel('gamesReversiGame', { - gameId: this.game.id - })); - this.connection.on('started', this.onStarted); - }); - }, - - onStarted(game) { - Object.assign(this.game, game); - }, - } -}); -</script> diff --git a/packages/client/src/pages/reversi/index.vue b/packages/client/src/pages/reversi/index.vue deleted file mode 100644 index 93c22c02f3..0000000000 --- a/packages/client/src/pages/reversi/index.vue +++ /dev/null @@ -1,280 +0,0 @@ -<template> -<div v-if="!matching" class="bgvwxkhb"> - <h1>Misskey {{ $ts._reversi.reversi }}</h1> - - <div class="play"> - <MkButton primary round style="margin: var(--margin) auto 0 auto;" @click="match">{{ $ts.invite }}</MkButton> - </div> - - <div class="_section"> - <MkFolder v-if="invitations.length > 0"> - <template #header>{{ $ts.invitations }}</template> - <div class="nfcacttm"> - <button v-for="invitation in invitations" class="invitation _panel _button" tabindex="-1" @click="accept(invitation)"> - <MkAvatar class="avatar" :user="invitation.parent" :show-indicator="true"/> - <span class="name"><b><MkUserName :user="invitation.parent"/></b></span> - <span class="username">@{{ invitation.parent.username }}</span> - <MkTime :time="invitation.createdAt" class="time"/> - </button> - </div> - </MkFolder> - - <MkFolder v-if="myGames.length > 0"> - <template #header>{{ $ts._reversi.myGames }}</template> - <div class="knextgwz"> - <MkA v-for="g in myGames" :key="g.id" class="game _panel" tabindex="-1" :to="`/games/reversi/${g.id}`"> - <div class="players"> - <MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/> - </div> - <footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $ts._reversi.ended : $ts._reversi.playing }}</span><MkTime class="time" :time="g.createdAt"/></footer> - </MkA> - </div> - </MkFolder> - - <MkFolder v-if="games.length > 0"> - <template #header>{{ $ts._reversi.allGames }}</template> - <div class="knextgwz"> - <MkA v-for="g in games" :key="g.id" class="game _panel" tabindex="-1" :to="`/games/reversi/${g.id}`"> - <div class="players"> - <MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/> - </div> - <footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $ts._reversi.ended : $ts._reversi.playing }}</span><MkTime class="time" :time="g.createdAt"/></footer> - </MkA> - </div> - </MkFolder> - </div> -</div> -<div v-else class="sazhgisb"> - <h1> - <I18n :src="$ts.waitingFor" tag="span"> - <template #x> - <b><MkUserName :user="matching"/></b> - </template> - </I18n> - <MkEllipsis/> - </h1> - <div class="cancel"> - <MkButton inline round @click="cancel">{{ $ts.cancel }}</MkButton> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import * as os from '@/os'; -import { stream } from '@/stream'; -import MkButton from '@/components/ui/button.vue'; -import MkFolder from '@/components/ui/folder.vue'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - MkButton, MkFolder, - }, - - inject: ['navHook'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._reversi.reversi, - icon: 'fas fa-gamepad' - }, - games: [], - gamesFetching: true, - gamesMoreFetching: false, - myGames: [], - matching: null, - invitations: [], - connection: null, - pingClock: null, - }; - }, - - mounted() { - if (this.$i) { - this.connection = markRaw(stream.useChannel('gamesReversi')); - - this.connection.on('invited', this.onInvited); - - this.connection.on('matched', this.onMatched); - - this.pingClock = setInterval(() => { - if (this.matching) { - this.connection.send('ping', { - id: this.matching.id - }); - } - }, 3000); - - os.api('games/reversi/games', { - my: true - }).then(games => { - this.myGames = games; - }); - - os.api('games/reversi/invitations').then(invitations => { - this.invitations = this.invitations.concat(invitations); - }); - } - - os.api('games/reversi/games').then(games => { - this.games = games; - this.gamesFetching = false; - }); - }, - - beforeUnmount() { - if (this.connection) { - this.connection.dispose(); - clearInterval(this.pingClock); - } - }, - - methods: { - go(game) { - const url = '/games/reversi/' + game.id; - if (this.navHook) { - this.navHook(url); - } else { - this.$router.push(url); - } - }, - - async match() { - const user = await os.selectUser({ local: true }); - if (user == null) return; - os.api('games/reversi/match', { - userId: user.id - }).then(res => { - if (res == null) { - this.matching = user; - } else { - this.go(res); - } - }); - }, - - cancel() { - this.matching = null; - os.api('games/reversi/match/cancel'); - }, - - accept(invitation) { - os.api('games/reversi/match', { - userId: invitation.parent.id - }).then(game => { - if (game) { - this.go(game); - } - }); - }, - - onMatched(game) { - this.go(game); - }, - - onInvited(invite) { - this.invitations.unshift(invite); - } - } -}); -</script> - -<style lang="scss" scoped> -.bgvwxkhb { - > h1 { - margin: 0; - padding: 24px; - text-align: center; - font-size: 1.5em; - background: linear-gradient(0deg, #43c583, #438881); - color: #fff; - } - - > .play { - text-align: center; - } -} - -.sazhgisb { - text-align: center; -} - -.nfcacttm { - > .invitation { - display: flex; - box-sizing: border-box; - width: 100%; - padding: 16px; - line-height: 32px; - text-align: left; - - > .avatar { - width: 32px; - height: 32px; - margin-right: 8px; - } - - > .name { - margin-right: 8px; - } - - > .username { - margin-right: 8px; - opacity: 0.7; - } - - > .time { - margin-left: auto; - opacity: 0.7; - } - } -} - -.knextgwz { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - grid-gap: var(--margin); - - > .game { - > .players { - text-align: center; - padding: 16px; - line-height: 32px; - - > .avatar { - width: 32px; - height: 32px; - - &:first-child { - margin-right: 8px; - } - - &:last-child { - margin-left: 8px; - } - } - } - - > footer { - display: flex; - align-items: baseline; - border-top: solid 0.5px var(--divider); - padding: 6px 8px; - font-size: 0.9em; - - > .state { - &.playing { - color: var(--accent); - } - } - - > .time { - margin-left: auto; - opacity: 0.7; - } - } - } -} -</style> diff --git a/packages/client/src/pages/settings/account-info.vue b/packages/client/src/pages/settings/account-info.vue index 1d6afd9d58..ceff587302 100644 --- a/packages/client/src/pages/settings/account-info.vue +++ b/packages/client/src/pages/settings/account-info.vue @@ -94,10 +94,6 @@ <template #key>{{ $ts.driveUsage }}</template> <template #value>{{ bytes(stats.driveUsage) }}</template> </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.reversiCount }}</template> - <template #value>{{ number(stats.reversiCount) }}</template> - </MkKeyValue> </FormSection> <FormSection> diff --git a/packages/client/src/pages/settings/sounds.vue b/packages/client/src/pages/settings/sounds.vue index 0977dd8322..b538bf7cf5 100644 --- a/packages/client/src/pages/settings/sounds.vue +++ b/packages/client/src/pages/settings/sounds.vue @@ -94,8 +94,6 @@ export default defineComponent({ this.sounds.chatBg = ColdDeviceStorage.get('sound_chatBg'); this.sounds.antenna = ColdDeviceStorage.get('sound_antenna'); this.sounds.channel = ColdDeviceStorage.get('sound_channel'); - this.sounds.reversiPutBlack = ColdDeviceStorage.get('sound_reversiPutBlack'); - this.sounds.reversiPutWhite = ColdDeviceStorage.get('sound_reversiPutWhite'); }, mounted() { diff --git a/packages/client/src/router.ts b/packages/client/src/router.ts index a5ae1177e8..312374cdb9 100644 --- a/packages/client/src/router.ts +++ b/packages/client/src/router.ts @@ -73,8 +73,6 @@ const defaultRoutes = [ { path: '/tags/:tag', component: page('tag'), props: route => ({ tag: route.params.tag }) }, { path: '/user-info/:user', component: page('user-info'), props: route => ({ userId: route.params.user }) }, { path: '/instance-info/:host', component: page('instance-info'), props: route => ({ host: route.params.host }) }, - { path: '/games/reversi', component: page('reversi/index') }, - { path: '/games/reversi/:gameId', component: page('reversi/game'), props: route => ({ gameId: route.params.gameId }) }, { path: '/mfm-cheat-sheet', component: page('mfm-cheat-sheet') }, { path: '/api-console', component: page('api-console') }, { path: '/preview', component: page('preview') }, diff --git a/packages/client/src/scripts/games/reversi/core.ts b/packages/client/src/scripts/games/reversi/core.ts deleted file mode 100644 index 0cb8922e19..0000000000 --- a/packages/client/src/scripts/games/reversi/core.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { count, concat } from '@/scripts/array'; - -// MISSKEY REVERSI ENGINE - -/** - * true ... 黒 - * false ... 白 - */ -export type Color = boolean; -const BLACK = true; -const WHITE = false; - -export type MapPixel = 'null' | 'empty'; - -export type Options = { - isLlotheo: boolean; - canPutEverywhere: boolean; - loopedBoard: boolean; -}; - -export type Undo = { - /** - * 色 - */ - color: Color; - - /** - * どこに打ったか - */ - pos: number; - - /** - * 反転した石の位置の配列 - */ - effects: number[]; - - /** - * ターン - */ - turn: Color | null; -}; - -/** - * リバーシエンジン - */ -export default class Reversi { - public map: MapPixel[]; - public mapWidth: number; - public mapHeight: number; - public board: (Color | null | undefined)[]; - public turn: Color | null = BLACK; - public opts: Options; - - public prevPos = -1; - public prevColor: Color | null = null; - - private logs: Undo[] = []; - - /** - * ゲームを初期化します - */ - constructor(map: string[], opts: Options) { - //#region binds - this.put = this.put.bind(this); - //#endregion - - //#region Options - this.opts = opts; - if (this.opts.isLlotheo == null) this.opts.isLlotheo = false; - if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false; - if (this.opts.loopedBoard == null) this.opts.loopedBoard = false; - //#endregion - - //#region Parse map data - this.mapWidth = map[0].length; - this.mapHeight = map.length; - const mapData = map.join(''); - - this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined); - - this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null'); - //#endregion - - // ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある - if (!this.canPutSomewhere(BLACK)) - this.turn = this.canPutSomewhere(WHITE) ? WHITE : null; - } - - /** - * 黒石の数 - */ - public get blackCount() { - return count(BLACK, this.board); - } - - /** - * 白石の数 - */ - public get whiteCount() { - return count(WHITE, this.board); - } - - public transformPosToXy(pos: number): number[] { - const x = pos % this.mapWidth; - const y = Math.floor(pos / this.mapWidth); - return [x, y]; - } - - public transformXyToPos(x: number, y: number): number { - return x + (y * this.mapWidth); - } - - /** - * 指定のマスに石を打ちます - * @param color 石の色 - * @param pos 位置 - */ - public put(color: Color, pos: number) { - this.prevPos = pos; - this.prevColor = color; - - this.board[pos] = color; - - // 反転させられる石を取得 - const effects = this.effects(color, pos); - - // 反転させる - for (const pos of effects) { - this.board[pos] = color; - } - - const turn = this.turn; - - this.logs.push({ - color, - pos, - effects, - turn - }); - - this.calcTurn(); - } - - private calcTurn() { - // ターン計算 - this.turn = - this.canPutSomewhere(!this.prevColor) ? !this.prevColor : - this.canPutSomewhere(this.prevColor!) ? this.prevColor : - null; - } - - public undo() { - const undo = this.logs.pop()!; - this.prevColor = undo.color; - this.prevPos = undo.pos; - this.board[undo.pos] = null; - for (const pos of undo.effects) { - const color = this.board[pos]; - this.board[pos] = !color; - } - this.turn = undo.turn; - } - - /** - * 指定した位置のマップデータのマスを取得します - * @param pos 位置 - */ - public mapDataGet(pos: number): MapPixel { - const [x, y] = this.transformPosToXy(pos); - return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos]; - } - - /** - * 打つことができる場所を取得します - */ - public puttablePlaces(color: Color): number[] { - return Array.from(this.board.keys()).filter(i => this.canPut(color, i)); - } - - /** - * 打つことができる場所があるかどうかを取得します - */ - public canPutSomewhere(color: Color): boolean { - return this.puttablePlaces(color).length > 0; - } - - /** - * 指定のマスに石を打つことができるかどうかを取得します - * @param color 自分の色 - * @param pos 位置 - */ - public canPut(color: Color, pos: number): boolean { - return ( - this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない - this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んでなくても置けるモード - this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか - } - - /** - * 指定のマスに石を置いた時の、反転させられる石を取得します - * @param color 自分の色 - * @param initPos 位置 - */ - public effects(color: Color, initPos: number): number[] { - const enemyColor = !color; - - const diffVectors: [number, number][] = [ - [ 0, -1], // 上 - [ +1, -1], // 右上 - [ +1, 0], // 右 - [ +1, +1], // 右下 - [ 0, +1], // 下 - [ -1, +1], // 左下 - [ -1, 0], // 左 - [ -1, -1] // 左上 - ]; - - const effectsInLine = ([dx, dy]: [number, number]): number[] => { - const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy]; - - const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列 - let [x, y] = this.transformPosToXy(initPos); - while (true) { - [x, y] = nextPos(x, y); - - // 座標が指し示す位置がボード外に出たとき - if (this.opts.loopedBoard && this.transformXyToPos( - (x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth), - (y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) - // 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ) - return found; - else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) - return []; // 挟めないことが確定 (盤面外に到達) - - const pos = this.transformXyToPos(x, y); - if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達) - const stone = this.board[pos]; - if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達) - if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見) - if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見) - } - }; - - return concat(diffVectors.map(effectsInLine)); - } - - /** - * ゲームが終了したか否か - */ - public get isEnded(): boolean { - return this.turn === null; - } - - /** - * ゲームの勝者 (null = 引き分け) - */ - public get winner(): Color | null { - return this.isEnded ? - this.blackCount == this.whiteCount ? null : - this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : - undefined as never; - } -} diff --git a/packages/client/src/scripts/games/reversi/maps.ts b/packages/client/src/scripts/games/reversi/maps.ts deleted file mode 100644 index dc0d1bf9d0..0000000000 --- a/packages/client/src/scripts/games/reversi/maps.ts +++ /dev/null @@ -1,896 +0,0 @@ -/** - * 組み込みマップ定義 - * - * データ値: - * (スペース) ... マス無し - * - ... マス - * b ... 初期配置される黒石 - * w ... 初期配置される白石 - */ - -export type Map = { - name?: string; - category?: string; - author?: string; - data: string[]; -}; - -export const fourfour: Map = { - name: '4x4', - category: '4x4', - data: [ - '----', - '-wb-', - '-bw-', - '----' - ] -}; - -export const sixsix: Map = { - name: '6x6', - category: '6x6', - data: [ - '------', - '------', - '--wb--', - '--bw--', - '------', - '------' - ] -}; - -export const roundedSixsix: Map = { - name: '6x6 rounded', - category: '6x6', - author: 'syuilo', - data: [ - ' ---- ', - '------', - '--wb--', - '--bw--', - '------', - ' ---- ' - ] -}; - -export const roundedSixsix2: Map = { - name: '6x6 rounded 2', - category: '6x6', - author: 'syuilo', - data: [ - ' -- ', - ' ---- ', - '--wb--', - '--bw--', - ' ---- ', - ' -- ' - ] -}; - -export const eighteight: Map = { - name: '8x8', - category: '8x8', - data: [ - '--------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------' - ] -}; - -export const eighteightH1: Map = { - name: '8x8 handicap 1', - category: '8x8', - data: [ - 'b-------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------' - ] -}; - -export const eighteightH2: Map = { - name: '8x8 handicap 2', - category: '8x8', - data: [ - 'b-------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '-------b' - ] -}; - -export const eighteightH3: Map = { - name: '8x8 handicap 3', - category: '8x8', - data: [ - 'b------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '-------b' - ] -}; - -export const eighteightH4: Map = { - name: '8x8 handicap 4', - category: '8x8', - data: [ - 'b------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - 'b------b' - ] -}; - -export const eighteightH28: Map = { - name: '8x8 handicap 28', - category: '8x8', - data: [ - 'bbbbbbbb', - 'b------b', - 'b------b', - 'b--wb--b', - 'b--bw--b', - 'b------b', - 'b------b', - 'bbbbbbbb' - ] -}; - -export const roundedEighteight: Map = { - name: '8x8 rounded', - category: '8x8', - author: 'syuilo', - data: [ - ' ------ ', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - ' ------ ' - ] -}; - -export const roundedEighteight2: Map = { - name: '8x8 rounded 2', - category: '8x8', - author: 'syuilo', - data: [ - ' ---- ', - ' ------ ', - '--------', - '---wb---', - '---bw---', - '--------', - ' ------ ', - ' ---- ' - ] -}; - -export const roundedEighteight3: Map = { - name: '8x8 rounded 3', - category: '8x8', - author: 'syuilo', - data: [ - ' -- ', - ' ---- ', - ' ------ ', - '---wb---', - '---bw---', - ' ------ ', - ' ---- ', - ' -- ' - ] -}; - -export const eighteightWithNotch: Map = { - name: '8x8 with notch', - category: '8x8', - author: 'syuilo', - data: [ - '--- ---', - '--------', - '--------', - ' --wb-- ', - ' --bw-- ', - '--------', - '--------', - '--- ---' - ] -}; - -export const eighteightWithSomeHoles: Map = { - name: '8x8 with some holes', - category: '8x8', - author: 'syuilo', - data: [ - '--- ----', - '----- --', - '-- -----', - '---wb---', - '---bw- -', - ' -------', - '--- ----', - '--------' - ] -}; - -export const circle: Map = { - name: 'Circle', - category: '8x8', - author: 'syuilo', - data: [ - ' -- ', - ' ------ ', - ' ------ ', - '---wb---', - '---bw---', - ' ------ ', - ' ------ ', - ' -- ' - ] -}; - -export const smile: Map = { - name: 'Smile', - category: '8x8', - author: 'syuilo', - data: [ - ' ------ ', - '--------', - '-- -- --', - '---wb---', - '-- bw --', - '--- ---', - '--------', - ' ------ ' - ] -}; - -export const window: Map = { - name: 'Window', - category: '8x8', - author: 'syuilo', - data: [ - '--------', - '- -- -', - '- -- -', - '---wb---', - '---bw---', - '- -- -', - '- -- -', - '--------' - ] -}; - -export const reserved: Map = { - name: 'Reserved', - category: '8x8', - author: 'Aya', - data: [ - 'w------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - 'b------w' - ] -}; - -export const x: Map = { - name: 'X', - category: '8x8', - author: 'Aya', - data: [ - 'w------b', - '-w----b-', - '--w--b--', - '---wb---', - '---bw---', - '--b--w--', - '-b----w-', - 'b------w' - ] -}; - -export const parallel: Map = { - name: 'Parallel', - category: '8x8', - author: 'Aya', - data: [ - '--------', - '--------', - '--------', - '---bb---', - '---ww---', - '--------', - '--------', - '--------' - ] -}; - -export const lackOfBlack: Map = { - name: 'Lack of Black', - category: '8x8', - data: [ - '--------', - '--------', - '--------', - '---w----', - '---bw---', - '--------', - '--------', - '--------' - ] -}; - -export const squareParty: Map = { - name: 'Square Party', - category: '8x8', - author: 'syuilo', - data: [ - '--------', - '-wwwbbb-', - '-w-wb-b-', - '-wwwbbb-', - '-bbbwww-', - '-b-bw-w-', - '-bbbwww-', - '--------' - ] -}; - -export const minesweeper: Map = { - name: 'Minesweeper', - category: '8x8', - author: 'syuilo', - data: [ - 'b-b--w-w', - '-w-wb-b-', - 'w-b--w-b', - '-b-wb-w-', - '-w-bw-b-', - 'b-w--b-w', - '-b-bw-w-', - 'w-w--b-b' - ] -}; - -export const tenthtenth: Map = { - name: '10x10', - category: '10x10', - data: [ - '----------', - '----------', - '----------', - '----------', - '----wb----', - '----bw----', - '----------', - '----------', - '----------', - '----------' - ] -}; - -export const hole: Map = { - name: 'The Hole', - category: '10x10', - author: 'syuilo', - data: [ - '----------', - '----------', - '--wb--wb--', - '--bw--bw--', - '---- ----', - '---- ----', - '--wb--wb--', - '--bw--bw--', - '----------', - '----------' - ] -}; - -export const grid: Map = { - name: 'Grid', - category: '10x10', - author: 'syuilo', - data: [ - '----------', - '- - -- - -', - '----------', - '- - -- - -', - '----wb----', - '----bw----', - '- - -- - -', - '----------', - '- - -- - -', - '----------' - ] -}; - -export const cross: Map = { - name: 'Cross', - category: '10x10', - author: 'Aya', - data: [ - ' ---- ', - ' ---- ', - ' ---- ', - '----------', - '----wb----', - '----bw----', - '----------', - ' ---- ', - ' ---- ', - ' ---- ' - ] -}; - -export const charX: Map = { - name: 'Char X', - category: '10x10', - author: 'syuilo', - data: [ - '--- ---', - '---- ----', - '----------', - ' -------- ', - ' --wb-- ', - ' --bw-- ', - ' -------- ', - '----------', - '---- ----', - '--- ---' - ] -}; - -export const charY: Map = { - name: 'Char Y', - category: '10x10', - author: 'syuilo', - data: [ - '--- ---', - '---- ----', - '----------', - ' -------- ', - ' --wb-- ', - ' --bw-- ', - ' ------ ', - ' ------ ', - ' ------ ', - ' ------ ' - ] -}; - -export const walls: Map = { - name: 'Walls', - category: '10x10', - author: 'Aya', - data: [ - ' bbbbbbbb ', - 'w--------w', - 'w--------w', - 'w--------w', - 'w---wb---w', - 'w---bw---w', - 'w--------w', - 'w--------w', - 'w--------w', - ' bbbbbbbb ' - ] -}; - -export const cpu: Map = { - name: 'CPU', - category: '10x10', - author: 'syuilo', - data: [ - ' b b b b ', - 'w--------w', - ' -------- ', - 'w--------w', - ' ---wb--- ', - ' ---bw--- ', - 'w--------w', - ' -------- ', - 'w--------w', - ' b b b b ' - ] -}; - -export const checker: Map = { - name: 'Checker', - category: '10x10', - author: 'Aya', - data: [ - '----------', - '----------', - '----------', - '---wbwb---', - '---bwbw---', - '---wbwb---', - '---bwbw---', - '----------', - '----------', - '----------' - ] -}; - -export const japaneseCurry: Map = { - name: 'Japanese curry', - category: '10x10', - author: 'syuilo', - data: [ - 'w-b-b-b-b-', - '-w-b-b-b-b', - 'w-w-b-b-b-', - '-w-w-b-b-b', - 'w-w-wwb-b-', - '-w-wbb-b-b', - 'w-w-w-b-b-', - '-w-w-w-b-b', - 'w-w-w-w-b-', - '-w-w-w-w-b' - ] -}; - -export const mosaic: Map = { - name: 'Mosaic', - category: '10x10', - author: 'syuilo', - data: [ - '- - - - - ', - ' - - - - -', - '- - - - - ', - ' - w w - -', - '- - b b - ', - ' - w w - -', - '- - b b - ', - ' - - - - -', - '- - - - - ', - ' - - - - -', - ] -}; - -export const arena: Map = { - name: 'Arena', - category: '10x10', - author: 'syuilo', - data: [ - '- - -- - -', - ' - - - - ', - '- ------ -', - ' -------- ', - '- --wb-- -', - '- --bw-- -', - ' -------- ', - '- ------ -', - ' - - - - ', - '- - -- - -' - ] -}; - -export const reactor: Map = { - name: 'Reactor', - category: '10x10', - author: 'syuilo', - data: [ - '-w------b-', - 'b- - - -w', - '- --wb-- -', - '---b w---', - '- b wb w -', - '- w bw b -', - '---w b---', - '- --bw-- -', - 'w- - - -b', - '-b------w-' - ] -}; - -export const sixeight: Map = { - name: '6x8', - category: 'Special', - data: [ - '------', - '------', - '------', - '--wb--', - '--bw--', - '------', - '------', - '------' - ] -}; - -export const spark: Map = { - name: 'Spark', - category: 'Special', - author: 'syuilo', - data: [ - ' - - ', - '----------', - ' -------- ', - ' -------- ', - ' ---wb--- ', - ' ---bw--- ', - ' -------- ', - ' -------- ', - '----------', - ' - - ' - ] -}; - -export const islands: Map = { - name: 'Islands', - category: 'Special', - author: 'syuilo', - data: [ - '-------- ', - '---wb--- ', - '---bw--- ', - '-------- ', - ' - - ', - ' - - ', - ' --------', - ' --------', - ' --------', - ' --------' - ] -}; - -export const galaxy: Map = { - name: 'Galaxy', - category: 'Special', - author: 'syuilo', - data: [ - ' ------ ', - ' --www--- ', - ' ------w--- ', - '---bbb--w---', - '--b---b-w-b-', - '-b--wwb-w-b-', - '-b-w-bww--b-', - '-b-w-b---b--', - '---w--bbb---', - ' ---w------ ', - ' ---www-- ', - ' ------ ' - ] -}; - -export const triangle: Map = { - name: 'Triangle', - category: 'Special', - author: 'syuilo', - data: [ - ' -- ', - ' -- ', - ' ---- ', - ' ---- ', - ' --wb-- ', - ' --bw-- ', - ' -------- ', - ' -------- ', - '----------', - '----------' - ] -}; - -export const iphonex: Map = { - name: 'iPhone X', - category: 'Special', - author: 'syuilo', - data: [ - ' -- -- ', - '--------', - '--------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------', - '--------', - ' ------ ' - ] -}; - -export const dealWithIt: Map = { - name: 'Deal with it!', - category: 'Special', - author: 'syuilo', - data: [ - '------------', - '--w-b-------', - ' --b-w------', - ' --w-b---- ', - ' ------- ' - ] -}; - -export const experiment: Map = { - name: 'Let\'s experiment', - category: 'Special', - author: 'syuilo', - data: [ - ' ------------ ', - '------wb------', - '------bw------', - '--------------', - ' - - ', - '------ ------', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'wwwwww bbbbbb' - ] -}; - -export const bigBoard: Map = { - name: 'Big board', - category: 'Special', - data: [ - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '-------wb-------', - '-------bw-------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------' - ] -}; - -export const twoBoard: Map = { - name: 'Two board', - category: 'Special', - author: 'Aya', - data: [ - '-------- --------', - '-------- --------', - '-------- --------', - '---wb--- ---wb---', - '---bw--- ---bw---', - '-------- --------', - '-------- --------', - '-------- --------' - ] -}; - -export const test1: Map = { - name: 'Test1', - category: 'Test', - data: [ - '--------', - '---wb---', - '---bw---', - '--------' - ] -}; - -export const test2: Map = { - name: 'Test2', - category: 'Test', - data: [ - '------', - '------', - '-b--w-', - '-w--b-', - '-w--b-' - ] -}; - -export const test3: Map = { - name: 'Test3', - category: 'Test', - data: [ - '-w-', - '--w', - 'w--', - '-w-', - '--w', - 'w--', - '-w-', - '--w', - 'w--', - '-w-', - '---', - 'b--', - ] -}; - -export const test4: Map = { - name: 'Test4', - category: 'Test', - data: [ - '-w--b-', - '-w--b-', - '------', - '-w--b-', - '-w--b-' - ] -}; - -// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)A1に打ってしまう -export const test6: Map = { - name: 'Test6', - category: 'Test', - data: [ - '--wwwww-', - 'wwwwwwww', - 'wbbbwbwb', - 'wbbbbwbb', - 'wbwbbwbb', - 'wwbwbbbb', - '--wbbbbb', - '-wwwww--', - ] -}; - -// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)G7に打ってしまう -export const test7: Map = { - name: 'Test7', - category: 'Test', - data: [ - 'b--w----', - 'b-wwww--', - 'bwbwwwbb', - 'wbwwwwb-', - 'wwwwwww-', - '-wwbbwwb', - '--wwww--', - '--wwww--', - ] -}; - -// 検証用: この盤面で藍(lv5)が黒で始めると何故か(?)A1に打ってしまう -export const test8: Map = { - name: 'Test8', - category: 'Test', - data: [ - '--------', - '-----w--', - 'w--www--', - 'wwwwww--', - 'bbbbwww-', - 'wwwwww--', - '--www---', - '--ww----', - ] -}; diff --git a/packages/client/src/scripts/games/reversi/package.json b/packages/client/src/scripts/games/reversi/package.json deleted file mode 100644 index a4415ad141..0000000000 --- a/packages/client/src/scripts/games/reversi/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "misskey-reversi", - "version": "0.0.5", - "description": "Misskey reversi engine", - "keywords": [ - "misskey" - ], - "author": "syuilo <i@syuilo.com>", - "license": "MIT", - "repository": "https://github.com/misskey-dev/misskey.git", - "bugs": "https://github.com/misskey-dev/misskey/issues", - "main": "./built/core.js", - "types": "./built/core.d.ts", - "scripts": { - "build": "tsc" - }, - "dependencies": {} -} diff --git a/packages/client/src/scripts/games/reversi/tsconfig.json b/packages/client/src/scripts/games/reversi/tsconfig.json deleted file mode 100644 index 851fb6b7e4..0000000000 --- a/packages/client/src/scripts/games/reversi/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "noEmitOnError": false, - "noImplicitAny": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "experimentalDecorators": true, - "declaration": true, - "sourceMap": false, - "target": "es2017", - "module": "commonjs", - "removeComments": false, - "noLib": false, - "outDir": "./built", - "rootDir": "./" - }, - "compileOnSave": false, - "include": [ - "./core.ts" - ] -} diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index f2732c57d3..745d323100 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -255,8 +255,6 @@ export class ColdDeviceStorage { sound_chatBg: { type: 'syuilo/waon', volume: 1 }, sound_antenna: { type: 'syuilo/triple', volume: 1 }, sound_channel: { type: 'syuilo/square-pico', volume: 1 }, - sound_reversiPutBlack: { type: 'syuilo/kick', volume: 0.3 }, - sound_reversiPutWhite: { type: 'syuilo/snare', volume: 0.3 }, }; public static watchers = []; From b17726c9da4ab33f949904dcd47ecbd1ba26fede Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Wed, 12 Jan 2022 17:50:36 +0900 Subject: [PATCH 13/81] Fix The unauthenticated git protocol on port 9418 is no longer supported. (#8139) --- packages/client/package.json | 2 +- packages/client/yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index 167ab2e816..e790998c36 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -75,7 +75,7 @@ "ms": "2.1.3", "nested-property": "4.0.0", "parse5": "6.0.1", - "photoswipe": "git://github.com/dimsemenov/photoswipe#v5-beta", + "photoswipe": "git+https://github.com/dimsemenov/photoswipe#v5-beta", "portscanner": "2.2.0", "postcss": "8.4.5", "postcss-loader": "6.2.1", diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index 80cf1fc982..c18910edfb 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -4537,9 +4537,9 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -"photoswipe@git://github.com/dimsemenov/photoswipe#v5-beta": +"photoswipe@git+https://github.com/dimsemenov/photoswipe#v5-beta": version "5.1.7" - resolved "git://github.com/dimsemenov/photoswipe#60040164333bd257409669e715e4327afdb3aec7" + resolved "git+https://github.com/dimsemenov/photoswipe#60040164333bd257409669e715e4327afdb3aec7" picocolors@^1.0.0: version "1.0.0" From 1f2dab0a839f9ee6d754243881d949988af7df55 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 13 Jan 2022 00:47:05 +0900 Subject: [PATCH 14/81] feat: multiple emojis editing --- .../endpoints/admin/emoji/add-aliases-bulk.ts | 39 ++ .../api/endpoints/admin/emoji/delete-bulk.ts | 37 ++ .../admin/emoji/{remove.ts => delete.ts} | 2 +- .../api/endpoints/admin/emoji/import-zip.ts | 21 ++ .../admin/emoji/remove-aliases-bulk.ts | 39 ++ .../endpoints/admin/emoji/set-aliases-bulk.ts | 35 ++ .../admin/emoji/set-category-bulk.ts | 35 ++ .../src/pages/admin/emoji-edit-dialog.vue | 2 +- packages/client/src/pages/admin/emojis.vue | 349 +++++++++++------- packages/client/src/pages/admin/index.vue | 8 +- 10 files changed, 428 insertions(+), 139 deletions(-) create mode 100644 packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts rename packages/backend/src/server/api/endpoints/admin/emoji/{remove.ts => delete.ts} (95%) create mode 100644 packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts new file mode 100644 index 0000000000..ef0f315022 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts @@ -0,0 +1,39 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true as const, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + aliases: { + validator: $.arr($.str), + }, + }, +}; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + const emojis = await Emojis.find({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await Emojis.update(emoji.id, { + updatedAt: new Date(), + aliases: [...new Set(emoji.aliases.concat(ps.aliases))], + }); + } + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts new file mode 100644 index 0000000000..a99cd3c978 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -0,0 +1,37 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { insertModerationLog } from '@/services/insert-moderation-log'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true as const, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + }, +}; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps, me) => { + const emojis = await Emojis.find({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await Emojis.delete(emoji.id); + + await getConnection().queryResultCache!.remove(['meta_emojis']); + + insertModerationLog(me, 'deleteEmoji', { + emoji: emoji, + }); + } +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts similarity index 95% rename from packages/backend/src/server/api/endpoints/admin/emoji/remove.ts rename to packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index 440c1008c7..870245ac92 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -37,7 +37,7 @@ export default define(meta, async (ps, me) => { await getConnection().queryResultCache!.remove(['meta_emojis']); - insertModerationLog(me, 'removeEmoji', { + insertModerationLog(me, 'deleteEmoji', { emoji: emoji, }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts new file mode 100644 index 0000000000..04895b8f20 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts @@ -0,0 +1,21 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { createImportCustomEmojisJob } from '@/queue/index'; +import ms from 'ms'; +import { ID } from '@/misc/cafy-id'; + +export const meta = { + secure: true, + requireCredential: true as const, + requireModerator: true, + params: { + fileId: { + validator: $.type(ID), + }, + }, +}; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps, user) => { + createImportCustomEmojisJob(user, ps.fileId); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts new file mode 100644 index 0000000000..4c771b4e42 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts @@ -0,0 +1,39 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true as const, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + aliases: { + validator: $.arr($.str), + }, + }, +}; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + const emojis = await Emojis.find({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await Emojis.update(emoji.id, { + updatedAt: new Date(), + aliases: emoji.aliases.filter(x => !ps.aliases.includes(x)), + }); + } + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts new file mode 100644 index 0000000000..33dccbc642 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts @@ -0,0 +1,35 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true as const, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + aliases: { + validator: $.arr($.str), + }, + }, +}; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + await Emojis.update({ + id: In(ps.ids), + }, { + updatedAt: new Date(), + aliases: ps.aliases, + }); + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts new file mode 100644 index 0000000000..d40ed52da7 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts @@ -0,0 +1,35 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true as const, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + category: { + validator: $.optional.nullable.str, + }, + }, +}; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + await Emojis.update({ + id: In(ps.ids), + }, { + updatedAt: new Date(), + category: ps.category, + }); + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/client/src/pages/admin/emoji-edit-dialog.vue b/packages/client/src/pages/admin/emoji-edit-dialog.vue index a45d92fa16..2e3903426e 100644 --- a/packages/client/src/pages/admin/emoji-edit-dialog.vue +++ b/packages/client/src/pages/admin/emoji-edit-dialog.vue @@ -95,7 +95,7 @@ export default defineComponent({ }); if (canceled) return; - os.api('admin/emoji/remove', { + os.api('admin/emoji/delete', { id: this.emoji.id }).then(() => { this.$emit('done', { diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue index df5d234d6f..f995460f11 100644 --- a/packages/client/src/pages/admin/emojis.vue +++ b/packages/client/src/pages/admin/emojis.vue @@ -6,11 +6,22 @@ <template #prefix><i class="fas fa-search"></i></template> <template #label>{{ $ts.search }}</template> </MkInput> - <MkPagination ref="emojis" :pagination="pagination"> + <MkSwitch v-model="selectMode" style="margin: 8px 0;"> + <template #label>Select mode</template> + </MkSwitch> + <div v-if="selectMode" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <MkButton inline @click="selectAll">Select all</MkButton> + <MkButton inline @click="setCategoryBulk">Set category</MkButton> + <MkButton inline @click="addTagBulk">Add tag</MkButton> + <MkButton inline @click="removeTagBulk">Remove tag</MkButton> + <MkButton inline @click="setTagBulk">Set tag</MkButton> + <MkButton inline danger @click="delBulk">Delete</MkButton> + </div> + <MkPagination ref="emojisPaginationComponent" :pagination="pagination"> <template #empty><span>{{ $ts.noCustomEmojis }}</span></template> <template v-slot="{items}"> <div class="ldhfsamy"> - <button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="edit(emoji)"> + <button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)"> <img :src="emoji.url" class="img" :alt="emoji.name"/> <div class="body"> <div class="name _monospace">{{ emoji.name }}</div> @@ -32,7 +43,7 @@ <template #label>{{ $ts.host }}</template> </MkInput> </FormSplit> - <MkPagination ref="remoteEmojis" :pagination="remotePagination"> + <MkPagination :pagination="remotePagination"> <template #empty><span>{{ $ts.noCustomEmojis }}</span></template> <template v-slot="{items}"> <div class="ldhfsamy"> @@ -51,148 +62,213 @@ </MkSpacer> </template> -<script lang="ts"> -import { computed, defineComponent, toRef } from 'vue'; +<script lang="ts" setup> +import { computed, defineComponent, ref, toRef } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkTab from '@/components/tab.vue'; +import MkSwitch from '@/components/form/switch.vue'; import FormSplit from '@/components/form/split.vue'; -import { selectFiles } from '@/scripts/select-file'; +import { selectFile, selectFiles } from '@/scripts/select-file'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkTab, - MkButton, - MkInput, - MkPagination, - FormSplit, - }, +const emojisPaginationComponent = ref<InstanceType<typeof MkPagination>>(); - emits: ['info'], +const tab = ref('local'); +const query = ref(null); +const queryRemote = ref(null); +const host = ref(null); +const selectMode = ref(false); +const selectedEmojis = ref<string[]>([]); - data() { - return { - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.customEmojis, - icon: 'fas fa-laugh', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-plus', - text: this.$ts.addEmoji, - handler: this.add, - }, { - icon: 'fas fa-ellipsis-h', - handler: this.menu, - }], - tabs: [{ - active: this.tab === 'local', - title: this.$ts.local, - onClick: () => { this.tab = 'local'; }, - }, { - active: this.tab === 'remote', - title: this.$ts.remote, - onClick: () => { this.tab = 'remote'; }, - },] - })), - tab: 'local', - query: null, - queryRemote: null, - host: '', - pagination: { - endpoint: 'admin/emoji/list', - limit: 30, - params: computed(() => ({ - query: (this.query && this.query !== '') ? this.query : null - })) - }, - remotePagination: { - endpoint: 'admin/emoji/list-remote', - limit: 30, - params: computed(() => ({ - query: (this.queryRemote && this.queryRemote !== '') ? this.queryRemote : null, - host: (this.host && this.host !== '') ? this.host : null - })) - }, - } - }, +const pagination = { + endpoint: 'admin/emoji/list', + limit: 30, + params: computed(() => ({ + query: (query.value && query.value !== '') ? query.value : null, + })), +}; - async mounted() { - this.$emit('info', toRef(this, symbols.PAGE_INFO)); - }, +const remotePagination = { + endpoint: 'admin/emoji/list-remote', + limit: 30, + params: computed(() => ({ + query: (queryRemote.value && queryRemote.value !== '') ? queryRemote.value : null, + host: (host.value && host.value !== '') ? host.value : null, + })), +}; - methods: { - async add(e) { - const files = await selectFiles(e.currentTarget || e.target, null); - - const promise = Promise.all(files.map(file => os.api('admin/emoji/add', { - fileId: file.id, - }))); - promise.then(() => { - this.$refs.emojis.reload(); - }); - os.promiseDialog(promise); - }, - - edit(emoji) { - os.popup(import('./emoji-edit-dialog.vue'), { - emoji: emoji - }, { - done: result => { - if (result.updated) { - this.$refs.emojis.replaceItem(item => item.id === emoji.id, { - ...emoji, - ...result.updated - }); - } else if (result.deleted) { - this.$refs.emojis.removeItem(item => item.id === emoji.id); - } - }, - }, 'closed'); - }, - - im(emoji) { - os.apiWithDialog('admin/emoji/copy', { - emojiId: emoji.id, - }); - }, - - remoteMenu(emoji, ev) { - os.popupMenu([{ - type: 'label', - text: ':' + emoji.name + ':', - }, { - text: this.$ts.import, - icon: 'fas fa-plus', - action: () => { this.im(emoji) } - }], ev.currentTarget || ev.target); - }, - - menu(ev) { - os.popupMenu([{ - icon: 'fas fa-download', - text: this.$ts.export, - action: async () => { - os.api('export-custom-emojis', { - }) - .then(() => { - os.alert({ - type: 'info', - text: this.$ts.exportRequested, - }); - }).catch((e) => { - os.alert({ - type: 'error', - text: e.message, - }); - }); - } - }], ev.currentTarget || ev.target); - } +const selectAll = () => { + if (selectedEmojis.value.length > 0) { + selectedEmojis.value = []; + } else { + selectedEmojis.value = emojisPaginationComponent.value.items.map(item => item.id); } +}; + +const toggleSelect = (emoji) => { + if (selectedEmojis.value.includes(emoji.id)) { + selectedEmojis.value = selectedEmojis.value.filter(x => x !== emoji.id); + } else { + selectedEmojis.value.push(emoji.id); + } +}; + +const add = async (ev: MouseEvent) => { + const files = await selectFiles(ev.currentTarget || ev.target, null); + + const promise = Promise.all(files.map(file => os.api('admin/emoji/add', { + fileId: file.id, + }))); + promise.then(() => { + emojisPaginationComponent.value.reload(); + }); + os.promiseDialog(promise); +}; + +const edit = (emoji) => { + os.popup(import('./emoji-edit-dialog.vue'), { + emoji: emoji + }, { + done: result => { + if (result.updated) { + emojisPaginationComponent.value.replaceItem(item => item.id === emoji.id, { + ...emoji, + ...result.updated + }); + } else if (result.deleted) { + emojisPaginationComponent.value.removeItem(item => item.id === emoji.id); + } + }, + }, 'closed'); +}; + +const im = (emoji) => { + os.apiWithDialog('admin/emoji/copy', { + emojiId: emoji.id, + }); +}; + +const remoteMenu = (emoji, ev: MouseEvent) => { + os.popupMenu([{ + type: 'label', + text: ':' + emoji.name + ':', + }, { + text: i18n.locale.import, + icon: 'fas fa-plus', + action: () => { im(emoji) } + }], ev.currentTarget || ev.target); +}; + +const menu = (ev: MouseEvent) => { + os.popupMenu([{ + icon: 'fas fa-download', + text: i18n.locale.export, + action: async () => { + os.api('export-custom-emojis', { + }) + .then(() => { + os.alert({ + type: 'info', + text: i18n.locale.exportRequested, + }); + }).catch((e) => { + os.alert({ + type: 'error', + text: e.message, + }); + }); + } + }], ev.currentTarget || ev.target); +}; + +const setCategoryBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Category', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/set-category-bulk', { + ids: selectedEmojis.value, + category: result, + }); + emojisPaginationComponent.value.reload(); +}; + +const addTagBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Tag', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/add-aliases-bulk', { + ids: selectedEmojis.value, + aliases: result.split(' '), + }); + emojisPaginationComponent.value.reload(); +}; + +const removeTagBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Tag', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/remove-aliases-bulk', { + ids: selectedEmojis.value, + aliases: result.split(' '), + }); + emojisPaginationComponent.value.reload(); +}; + +const setTagBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Tag', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/set-aliases-bulk', { + ids: selectedEmojis.value, + aliases: result.split(' '), + }); + emojisPaginationComponent.value.reload(); +}; + +const delBulk = async () => { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.locale.deleteConfirm, + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/delete-bulk', { + ids: selectedEmojis.value, + }); + emojisPaginationComponent.value.reload(); +}; + +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.customEmojis, + icon: 'fas fa-laugh', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: i18n.locale.addEmoji, + handler: add, + }, { + icon: 'fas fa-ellipsis-h', + handler: menu, + }], + tabs: [{ + active: tab.value === 'local', + title: i18n.locale.local, + onClick: () => { tab.value = 'local'; }, + }, { + active: tab.value === 'remote', + title: i18n.locale.remote, + onClick: () => { tab.value = 'remote'; }, + },] + })), }); </script> @@ -212,11 +288,16 @@ export default defineComponent({ > .emoji { display: flex; align-items: center; - padding: 12px; + padding: 11px; text-align: left; + border: solid 1px var(--panel); &:hover { - color: var(--accent); + border-color: var(--inputBorderHover); + } + + &.selected { + border-color: var(--accent); } > .img { diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue index b7160de11d..350e7defc6 100644 --- a/packages/client/src/pages/admin/index.vue +++ b/packages/client/src/pages/admin/index.vue @@ -19,7 +19,7 @@ <div class="main"> <MkStickyContainer> <template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template> - <component :is="component" :key="page" v-bind="pageProps" @info="onInfo"/> + <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> </MkStickyContainer> </div> </div> @@ -66,7 +66,9 @@ export default defineComponent({ const narrow = ref(false); const view = ref(null); const el = ref(null); - const onInfo = (viewInfo) => { + const pageChanged = (page) => { + if (page == null) return; + const viewInfo = page[symbols.PAGE_INFO]; if (isRef(viewInfo)) { watch(viewInfo, () => { childInfo.value = viewInfo.value; @@ -311,7 +313,7 @@ export default defineComponent({ narrow, view, el, - onInfo, + pageChanged, childInfo, pageProps, component, From 7e5e763584acd80b1baef6af2d20621c89fa0af9 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 13 Jan 2022 00:47:40 +0900 Subject: [PATCH 15/81] feat: emojis import --- packages/backend/package.json | 1 + .../processors/db/export-custom-emojis.ts | 12 ++- .../processors/db/import-custom-emojis.ts | 84 ++++++++++++++++ .../backend/src/queue/processors/db/index.ts | 2 + packages/backend/yarn.lock | 95 ++++++++++++++++++- packages/client/src/pages/admin/emojis.vue | 20 ++++ 6 files changed, 205 insertions(+), 9 deletions(-) create mode 100644 packages/backend/src/queue/processors/db/import-custom-emojis.ts diff --git a/packages/backend/package.json b/packages/backend/package.json index 65da382e2d..c940e98301 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -180,6 +180,7 @@ "typeorm": "0.2.39", "typescript": "4.4.4", "ulid": "2.3.0", + "unzipper": "0.10.11", "uuid": "8.3.2", "web-push": "3.4.5", "websocket": "1.0.34", diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index 3930b9d6d4..a420866dcf 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -52,7 +52,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi }); }; - await writeMeta(`{"metaVersion":1,"emojis":[`); + await writeMeta(`{"metaVersion":2,"emojis":[`); const customEmojis = await Emojis.find({ where: { @@ -64,9 +64,9 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi }); for (const emoji of customEmojis) { - const exportId = ulid().toLowerCase(); const ext = mime.extension(emoji.type); - const emojiPath = path + '/' + exportId + (ext ? '.' + ext : ''); + const fileName = emoji.name + (ext ? '.' + ext : ''); + const emojiPath = path + '/' + fileName; fs.writeFileSync(emojiPath, '', 'binary'); let downloaded = false; @@ -77,8 +77,12 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi logger.error(e); } + if (!downloaded) { + fs.unlinkSync(emojiPath); + } + const content = JSON.stringify({ - id: exportId, + fileName: fileName, downloaded: downloaded, emoji: emoji, }); diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts new file mode 100644 index 0000000000..eb386bbb42 --- /dev/null +++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts @@ -0,0 +1,84 @@ +import * as Bull from 'bull'; +import * as tmp from 'tmp'; +import * as fs from 'fs'; +const unzipper = require('unzipper'); +import { getConnection } from 'typeorm'; + +import { queueLogger } from '../../logger'; +import { downloadUrl } from '@/misc/download-url'; +import { DriveFiles, Emojis } from '@/models/index'; +import { DbUserImportJobData } from '@/queue/types'; +import addFile from '@/services/drive/add-file'; +import { genId } from '@/misc/gen-id'; + +const logger = queueLogger.createSubLogger('import-custom-emojis'); + +// TODO: 名前衝突時の動作を選べるようにする +export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> { + logger.info(`Importing custom emojis ...`); + + const file = await DriveFiles.findOne({ + id: job.data.fileId, + }); + if (file == null) { + done(); + return; + } + + // Create temp dir + const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => { + tmp.dir((e, path, cleanup) => { + if (e) return rej(e); + res([path, cleanup]); + }); + }); + + logger.info(`Temp dir is ${path}`); + + const destPath = path + '/emojis.zip'; + + try { + fs.writeFileSync(destPath, '', 'binary'); + await downloadUrl(file.url, destPath); + } catch (e) { // TODO: 何度か再試行 + logger.error(e); + throw e; + } + + const outputPath = path + '/emojis'; + const unzipStream = fs.createReadStream(destPath); + const extractor = unzipper.Extract({ path: outputPath }); + extractor.on('close', async () => { + const metaRaw = fs.readFileSync(outputPath + '/meta.json', 'utf-8'); + const meta = JSON.parse(metaRaw); + + for (const record of meta.emojis) { + if (!record.downloaded) continue; + const emojiInfo = record.emoji; + const emojiPath = outputPath + '/' + record.fileName; + await Emojis.delete({ + name: emojiInfo.name, + }); + const driveFile = await addFile(null, emojiPath, record.fileName, null, null, true); + const emoji = await Emojis.insert({ + id: genId(), + updatedAt: new Date(), + name: emojiInfo.name, + category: emojiInfo.category, + host: null, + aliases: emojiInfo.aliases, + url: driveFile.url, + type: driveFile.type, + }).then(x => Emojis.findOneOrFail(x.identifiers[0])); + } + + await getConnection().queryResultCache!.remove(['meta_emojis']); + + cleanup(); + + logger.succ('Imported'); + done(); + }); + unzipStream.pipe(extractor); + logger.succ(`Unzipping to ${outputPath}`); +} diff --git a/packages/backend/src/queue/processors/db/index.ts b/packages/backend/src/queue/processors/db/index.ts index 1542f401ef..5fffa378f5 100644 --- a/packages/backend/src/queue/processors/db/index.ts +++ b/packages/backend/src/queue/processors/db/index.ts @@ -12,6 +12,7 @@ import { importUserLists } from './import-user-lists'; import { deleteAccount } from './delete-account'; import { importMuting } from './import-muting'; import { importBlocking } from './import-blocking'; +import { importCustomEmojis } from './import-custom-emojis'; const jobs = { deleteDriveFiles, @@ -25,6 +26,7 @@ const jobs = { importMuting, importBlocking, importUserLists, + importCustomEmojis, deleteAccount, } as Record<string, Bull.ProcessCallbackFunction<DbJobData> | Bull.ProcessPromiseFunction<DbJobData>>; diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index 16144b6d57..9e21fb29e3 100644 --- a/packages/backend/yarn.lock +++ b/packages/backend/yarn.lock @@ -1522,6 +1522,11 @@ big-integer@^1.6.16: resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== +big-integer@^1.6.17: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -1532,6 +1537,14 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + bl@^4.0.1, bl@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" @@ -1546,6 +1559,11 @@ bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= + blurhash@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.1.4.tgz#a7010ceb3019cd2c9809b17c910ebf6175d29244" @@ -1677,6 +1695,11 @@ buffer-from@^1.0.0, buffer-from@^1.1.1: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer-indexof-polyfill@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== + buffer-writer@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" @@ -1707,6 +1730,11 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= + bufferutil@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7" @@ -1875,6 +1903,13 @@ cbor@8.1.0: dependencies: nofilter "^3.1.0" +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= + dependencies: + traverse ">=0.3.0 <0.4" + chalk@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72" @@ -2789,6 +2824,13 @@ dotenv@^8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -3480,6 +3522,16 @@ fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -3690,7 +3742,7 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -graceful-fs@^4.2.0: +graceful-fs@^4.2.0, graceful-fs@^4.2.2: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== @@ -4007,7 +4059,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4800,6 +4852,11 @@ lilconfig@^2.0.3: resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd" integrity sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg== +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= + loader-runner@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" @@ -5204,7 +5261,7 @@ mkdirp-classic@^0.5.3: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -mkdirp@0.x, mkdirp@^0.5.4: +mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.4: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -6598,7 +6655,7 @@ readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2: +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -6780,6 +6837,13 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rimraf@2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -6914,7 +6978,7 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -setimmediate@^1.0.5: +setimmediate@^1.0.5, setimmediate@~1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -7584,6 +7648,11 @@ trace-redirect@1.0.6: resolved "https://registry.yarnpkg.com/trace-redirect/-/trace-redirect-1.0.6.tgz#ac629b5bf8247d30dde5a35fe9811b811075b504" integrity sha512-UUfa1DjjU5flcjMdaFIiIEGDTyu2y/IiMjOX4uGXa7meKBS4vD4f2Uy/tken9Qkd4Jsm4sRsfZcIIPqrRVF3Mg== +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= + ts-jest@^25.2.1: version "25.5.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.5.1.tgz#2913afd08f28385d54f2f4e828be4d261f4337c7" @@ -7827,6 +7896,22 @@ unpipe@1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= +unzipper@0.10.11: + version "0.10.11" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.11.tgz#0b4991446472cbdb92ee7403909f26c2419c782e" + integrity sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "^1.0.12" + graceful-fs "^4.2.2" + listenercount "~1.0.1" + readable-stream "~2.3.6" + setimmediate "~1.0.4" + uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue index f995460f11..38182e5be7 100644 --- a/packages/client/src/pages/admin/emojis.vue +++ b/packages/client/src/pages/admin/emojis.vue @@ -182,6 +182,26 @@ const menu = (ev: MouseEvent) => { }); }); } + }, { + icon: 'fas fa-upload', + text: i18n.locale.import, + action: async () => { + const file = await selectFile(ev.currentTarget || ev.target); + os.api('admin/emoji/import-zip', { + fileId: file.id, + }) + .then(() => { + os.alert({ + type: 'info', + text: i18n.locale.importRequested, + }); + }).catch((e) => { + os.alert({ + type: 'error', + text: e.message, + }); + }); + } }], ev.currentTarget || ev.target); }; From 53fb48537d39b0315493dec28388c57fee1848ab Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 13 Jan 2022 00:48:58 +0900 Subject: [PATCH 16/81] =?UTF-8?q?git=20add=E5=BF=98=E3=82=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/queue/index.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 2fbc1b1c01..f9994c3b59 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -213,6 +213,16 @@ export function createImportUserListsJob(user: ThinUser, fileId: DriveFile['id'] }); } +export function createImportCustomEmojisJob(user: ThinUser, fileId: DriveFile['id']) { + return dbQueue.add('importCustomEmojis', { + user: user, + fileId: fileId, + }, { + removeOnComplete: true, + removeOnFail: true, + }); +} + export function createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; } = {}) { return dbQueue.add('deleteAccount', { user: user, From 7782d9e46cb73c817e25919af450749f0560240e Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 13 Jan 2022 00:51:07 +0900 Subject: [PATCH 17/81] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5bdd3f1c..88e8055daa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - Chat UIが削除されました ### Improvements +- カスタム絵文字一括編集機能 +- カスタム絵文字一括インポート ### Bugfixes From d2623a2ef2cd2fb85671aa7d4b61edb5e5028f33 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 13 Jan 2022 01:46:23 +0900 Subject: [PATCH 18/81] clean up --- packages/client/src/pages/admin/abuses.vue | 4 ---- packages/client/src/pages/admin/ads.vue | 4 ---- packages/client/src/pages/admin/announcements.vue | 4 ---- packages/client/src/pages/admin/bot-protection.vue | 4 ---- packages/client/src/pages/admin/database.vue | 4 ---- packages/client/src/pages/admin/email-settings.vue | 4 ---- packages/client/src/pages/admin/files.vue | 4 ---- packages/client/src/pages/admin/instance-block.vue | 4 ---- packages/client/src/pages/admin/integrations.discord.vue | 4 ---- packages/client/src/pages/admin/integrations.github.vue | 4 ---- packages/client/src/pages/admin/integrations.twitter.vue | 4 ---- packages/client/src/pages/admin/integrations.vue | 4 ---- packages/client/src/pages/admin/object-storage.vue | 4 ---- packages/client/src/pages/admin/other-settings.vue | 4 ---- packages/client/src/pages/admin/overview.vue | 2 -- packages/client/src/pages/admin/proxy-account.vue | 4 ---- packages/client/src/pages/admin/queue.vue | 2 -- packages/client/src/pages/admin/relays.vue | 4 ---- packages/client/src/pages/admin/security.vue | 4 ---- packages/client/src/pages/admin/settings.vue | 4 ---- packages/client/src/pages/admin/users.vue | 4 ---- packages/client/src/pages/federation.vue | 4 ---- 22 files changed, 84 deletions(-) diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue index 04257f86ce..0f19f8dbe9 100644 --- a/packages/client/src/pages/admin/abuses.vue +++ b/packages/client/src/pages/admin/abuses.vue @@ -106,10 +106,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { acct, diff --git a/packages/client/src/pages/admin/ads.vue b/packages/client/src/pages/admin/ads.vue index 0396dae10c..8f164caa99 100644 --- a/packages/client/src/pages/admin/ads.vue +++ b/packages/client/src/pages/admin/ads.vue @@ -87,10 +87,6 @@ export default defineComponent({ }); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { add() { this.ads.unshift({ diff --git a/packages/client/src/pages/admin/announcements.vue b/packages/client/src/pages/admin/announcements.vue index 3614cb1441..a0d720bb29 100644 --- a/packages/client/src/pages/admin/announcements.vue +++ b/packages/client/src/pages/admin/announcements.vue @@ -61,10 +61,6 @@ export default defineComponent({ }); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { add() { this.announcements.unshift({ diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue index 81b09fb4d9..82ab155317 100644 --- a/packages/client/src/pages/admin/bot-protection.vue +++ b/packages/client/src/pages/admin/bot-protection.vue @@ -82,10 +82,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/database.vue b/packages/client/src/pages/admin/database.vue index c1088afd77..3a835eeafa 100644 --- a/packages/client/src/pages/admin/database.vue +++ b/packages/client/src/pages/admin/database.vue @@ -37,10 +37,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { bytes, number, } diff --git a/packages/client/src/pages/admin/email-settings.vue b/packages/client/src/pages/admin/email-settings.vue index 0799755a4d..6491a453ab 100644 --- a/packages/client/src/pages/admin/email-settings.vue +++ b/packages/client/src/pages/admin/email-settings.vue @@ -93,10 +93,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue index 6baa49f825..10341e7e36 100644 --- a/packages/client/src/pages/admin/files.vue +++ b/packages/client/src/pages/admin/files.vue @@ -106,10 +106,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { clear() { os.confirm({ diff --git a/packages/client/src/pages/admin/instance-block.vue b/packages/client/src/pages/admin/instance-block.vue index d1f7914ee4..6cadc7df39 100644 --- a/packages/client/src/pages/admin/instance-block.vue +++ b/packages/client/src/pages/admin/instance-block.vue @@ -40,10 +40,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/integrations.discord.vue b/packages/client/src/pages/admin/integrations.discord.vue index 8303afa3b0..8fc340150a 100644 --- a/packages/client/src/pages/admin/integrations.discord.vue +++ b/packages/client/src/pages/admin/integrations.discord.vue @@ -58,10 +58,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/integrations.github.vue b/packages/client/src/pages/admin/integrations.github.vue index c0316c317a..d9db9c00f1 100644 --- a/packages/client/src/pages/admin/integrations.github.vue +++ b/packages/client/src/pages/admin/integrations.github.vue @@ -58,10 +58,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/integrations.twitter.vue b/packages/client/src/pages/admin/integrations.twitter.vue index 5feabcc39d..1f8074535a 100644 --- a/packages/client/src/pages/admin/integrations.twitter.vue +++ b/packages/client/src/pages/admin/integrations.twitter.vue @@ -58,10 +58,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/integrations.vue b/packages/client/src/pages/admin/integrations.vue index 455fb6f4d6..91d03fef31 100644 --- a/packages/client/src/pages/admin/integrations.vue +++ b/packages/client/src/pages/admin/integrations.vue @@ -60,10 +60,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/object-storage.vue b/packages/client/src/pages/admin/object-storage.vue index 0f74865b10..6c5be220f8 100644 --- a/packages/client/src/pages/admin/object-storage.vue +++ b/packages/client/src/pages/admin/object-storage.vue @@ -118,10 +118,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/other-settings.vue b/packages/client/src/pages/admin/other-settings.vue index d21d0c5992..6b588e88aa 100644 --- a/packages/client/src/pages/admin/other-settings.vue +++ b/packages/client/src/pages/admin/other-settings.vue @@ -42,10 +42,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue index 564a63fda0..b8ae8ad9e1 100644 --- a/packages/client/src/pages/admin/overview.vue +++ b/packages/client/src/pages/admin/overview.vue @@ -116,8 +116,6 @@ export default defineComponent({ }, async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - os.api('meta', { detail: true }).then(meta => { this.meta = meta; }); diff --git a/packages/client/src/pages/admin/proxy-account.vue b/packages/client/src/pages/admin/proxy-account.vue index 9878df9122..5c4fbffa0c 100644 --- a/packages/client/src/pages/admin/proxy-account.vue +++ b/packages/client/src/pages/admin/proxy-account.vue @@ -44,10 +44,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/queue.vue b/packages/client/src/pages/admin/queue.vue index 719a3c2651..522210d933 100644 --- a/packages/client/src/pages/admin/queue.vue +++ b/packages/client/src/pages/admin/queue.vue @@ -38,8 +38,6 @@ export default defineComponent({ }, mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - this.$nextTick(() => { this.connection.send('requestLog', { id: Math.random().toString().substr(2, 8), diff --git a/packages/client/src/pages/admin/relays.vue b/packages/client/src/pages/admin/relays.vue index 5ab02002b4..bb840db0a2 100644 --- a/packages/client/src/pages/admin/relays.vue +++ b/packages/client/src/pages/admin/relays.vue @@ -48,10 +48,6 @@ export default defineComponent({ this.refresh(); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async addRelay() { const { canceled, result: inbox } = await os.inputText({ diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue index 276c514f16..d069891647 100644 --- a/packages/client/src/pages/admin/security.vue +++ b/packages/client/src/pages/admin/security.vue @@ -70,10 +70,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue index 802d7463ec..a4bac93834 100644 --- a/packages/client/src/pages/admin/settings.vue +++ b/packages/client/src/pages/admin/settings.vue @@ -197,10 +197,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/users.vue b/packages/client/src/pages/admin/users.vue index a094227ae9..30220a2a29 100644 --- a/packages/client/src/pages/admin/users.vue +++ b/packages/client/src/pages/admin/users.vue @@ -124,10 +124,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { lookupUser, diff --git a/packages/client/src/pages/federation.vue b/packages/client/src/pages/federation.vue index 9815e68986..e23c4f8f16 100644 --- a/packages/client/src/pages/federation.vue +++ b/packages/client/src/pages/federation.vue @@ -155,10 +155,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { getStatus(instance) { if (instance.isSuspended) return 'suspended'; From 25f15677c3e16bfaf7e47b378df39400d6fd2776 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 13 Jan 2022 01:50:53 +0900 Subject: [PATCH 19/81] refactor --- packages/client/src/pages/settings/account-info.vue | 2 -- packages/client/src/pages/settings/accounts.vue | 4 ---- packages/client/src/pages/settings/api.vue | 4 ---- packages/client/src/pages/settings/apps.vue | 4 ---- packages/client/src/pages/settings/custom-css.vue | 2 -- packages/client/src/pages/settings/deck.vue | 4 ---- packages/client/src/pages/settings/delete-account.vue | 4 ---- packages/client/src/pages/settings/drive.vue | 4 ---- packages/client/src/pages/settings/email.vue | 2 -- packages/client/src/pages/settings/general.vue | 4 ---- packages/client/src/pages/settings/import-export.vue | 4 ---- packages/client/src/pages/settings/index.vue | 9 +++++---- packages/client/src/pages/settings/instance-mute.vue | 5 ----- packages/client/src/pages/settings/integration.vue | 2 -- packages/client/src/pages/settings/menu.vue | 4 ---- packages/client/src/pages/settings/mute-block.vue | 4 ---- packages/client/src/pages/settings/notifications.vue | 4 ---- packages/client/src/pages/settings/other.vue | 4 ---- packages/client/src/pages/settings/plugin.install.vue | 4 ---- packages/client/src/pages/settings/plugin.vue | 4 ---- packages/client/src/pages/settings/privacy.vue | 4 ---- packages/client/src/pages/settings/profile.vue | 4 ---- packages/client/src/pages/settings/reaction.vue | 4 ---- packages/client/src/pages/settings/security.vue | 4 ---- packages/client/src/pages/settings/sounds.vue | 4 ---- packages/client/src/pages/settings/theme.install.vue | 4 ---- packages/client/src/pages/settings/theme.manage.vue | 4 ---- packages/client/src/pages/settings/theme.vue | 4 ---- packages/client/src/pages/settings/word-mute.vue | 4 ---- 29 files changed, 5 insertions(+), 109 deletions(-) diff --git a/packages/client/src/pages/settings/account-info.vue b/packages/client/src/pages/settings/account-info.vue index ceff587302..c98ad056f6 100644 --- a/packages/client/src/pages/settings/account-info.vue +++ b/packages/client/src/pages/settings/account-info.vue @@ -154,8 +154,6 @@ export default defineComponent({ }, mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - os.api('users/stats', { userId: this.$i.id }).then(stats => { diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue index 9ff11adda3..c795ede8ac 100644 --- a/packages/client/src/pages/settings/accounts.vue +++ b/packages/client/src/pages/settings/accounts.vue @@ -53,10 +53,6 @@ export default defineComponent({ }; }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { menu(account, ev) { os.popupMenu([{ diff --git a/packages/client/src/pages/settings/api.vue b/packages/client/src/pages/settings/api.vue index 1a51b526f2..20ff2a8d96 100644 --- a/packages/client/src/pages/settings/api.vue +++ b/packages/client/src/pages/settings/api.vue @@ -32,10 +32,6 @@ export default defineComponent({ }; }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { generateToken() { os.popup(import('@/components/token-generate-window.vue'), {}, { diff --git a/packages/client/src/pages/settings/apps.vue b/packages/client/src/pages/settings/apps.vue index 68952bbbdb..a75e06165d 100644 --- a/packages/client/src/pages/settings/apps.vue +++ b/packages/client/src/pages/settings/apps.vue @@ -67,10 +67,6 @@ export default defineComponent({ }; }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { revoke(token) { os.api('i/revoke-token', { tokenId: token.id }).then(() => { diff --git a/packages/client/src/pages/settings/custom-css.vue b/packages/client/src/pages/settings/custom-css.vue index 6dbb8c2ae9..556ee30c1d 100644 --- a/packages/client/src/pages/settings/custom-css.vue +++ b/packages/client/src/pages/settings/custom-css.vue @@ -37,8 +37,6 @@ export default defineComponent({ }, mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - this.$watch('localCustomCss', this.apply); }, diff --git a/packages/client/src/pages/settings/deck.vue b/packages/client/src/pages/settings/deck.vue index e290b095ac..46b90d3d1a 100644 --- a/packages/client/src/pages/settings/deck.vue +++ b/packages/client/src/pages/settings/deck.vue @@ -83,10 +83,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async setProfile() { const { canceled, result: name } = await os.inputText({ diff --git a/packages/client/src/pages/settings/delete-account.vue b/packages/client/src/pages/settings/delete-account.vue index 17501d9510..7edc81a309 100644 --- a/packages/client/src/pages/settings/delete-account.vue +++ b/packages/client/src/pages/settings/delete-account.vue @@ -33,10 +33,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async deleteAccount() { { diff --git a/packages/client/src/pages/settings/drive.vue b/packages/client/src/pages/settings/drive.vue index c123159b61..f1016ebd84 100644 --- a/packages/client/src/pages/settings/drive.vue +++ b/packages/client/src/pages/settings/drive.vue @@ -99,10 +99,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { chooseUploadFolder() { os.selectDriveFolder(false).then(async folder => { diff --git a/packages/client/src/pages/settings/email.vue b/packages/client/src/pages/settings/email.vue index e9010fbe42..54557f8773 100644 --- a/packages/client/src/pages/settings/email.vue +++ b/packages/client/src/pages/settings/email.vue @@ -111,8 +111,6 @@ export default defineComponent({ }); onMounted(() => { - context.emit('info', INFO); - watch(emailAddress, () => { saveEmailAddress(); }); diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue index 734bc78442..2e159e56a9 100644 --- a/packages/client/src/pages/settings/general.vue +++ b/packages/client/src/pages/settings/general.vue @@ -195,10 +195,6 @@ export default defineComponent({ }, }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async reloadAsk() { const { canceled } = await os.confirm({ diff --git a/packages/client/src/pages/settings/import-export.vue b/packages/client/src/pages/settings/import-export.vue index a1dd6a1539..21031c559e 100644 --- a/packages/client/src/pages/settings/import-export.vue +++ b/packages/client/src/pages/settings/import-export.vue @@ -133,10 +133,6 @@ export default defineComponent({ os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); }; - onMounted(() => { - context.emit('info', INFO); - }); - return { [symbols.PAGE_INFO]: INFO, excludeMutingUsers, diff --git a/packages/client/src/pages/settings/index.vue b/packages/client/src/pages/settings/index.vue index c9acf2c63c..66c8b147bb 100644 --- a/packages/client/src/pages/settings/index.vue +++ b/packages/client/src/pages/settings/index.vue @@ -14,7 +14,7 @@ </div> <div class="main"> <div class="bkzroven"> - <component :is="component" :key="page" v-bind="pageProps" @info="onInfo"/> + <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> </div> </div> </div> @@ -250,8 +250,9 @@ export default defineComponent({ const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified)); - const onInfo = (info) => { - childInfo.value = info; + const pageChanged = (page) => { + if (page == null) return; + childInfo.value = page[symbols.PAGE_INFO]; }; return { @@ -264,7 +265,7 @@ export default defineComponent({ pageProps, component, emailNotConfigured, - onInfo, + pageChanged, childInfo, }; }, diff --git a/packages/client/src/pages/settings/instance-mute.vue b/packages/client/src/pages/settings/instance-mute.vue index 584a21e4bd..f84a209b60 100644 --- a/packages/client/src/pages/settings/instance-mute.vue +++ b/packages/client/src/pages/settings/instance-mute.vue @@ -47,11 +47,6 @@ export default defineComponent({ }, }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - - async created() { this.instanceMutes = this.$i.mutedInstances.join('\n'); }, diff --git a/packages/client/src/pages/settings/integration.vue b/packages/client/src/pages/settings/integration.vue index e3dbc6fde9..ca36c91665 100644 --- a/packages/client/src/pages/settings/integration.vue +++ b/packages/client/src/pages/settings/integration.vue @@ -73,8 +73,6 @@ export default defineComponent({ }, mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - document.cookie = `igi=${this.$i.token}; path=/;` + ` max-age=31536000;` + (document.location.protocol.startsWith('https') ? ' secure' : ''); diff --git a/packages/client/src/pages/settings/menu.vue b/packages/client/src/pages/settings/menu.vue index 26404f3adf..6e38cd5dfe 100644 --- a/packages/client/src/pages/settings/menu.vue +++ b/packages/client/src/pages/settings/menu.vue @@ -67,10 +67,6 @@ export default defineComponent({ }, }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async addItem() { const menu = Object.keys(this.menuDef).filter(k => !this.$store.state.menu.includes(k)); diff --git a/packages/client/src/pages/settings/mute-block.vue b/packages/client/src/pages/settings/mute-block.vue index 6a63c9eb21..a4f9b41e80 100644 --- a/packages/client/src/pages/settings/mute-block.vue +++ b/packages/client/src/pages/settings/mute-block.vue @@ -66,10 +66,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { userPage } diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue index ab26d7d558..12171530bb 100644 --- a/packages/client/src/pages/settings/notifications.vue +++ b/packages/client/src/pages/settings/notifications.vue @@ -37,10 +37,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { readAllUnreadNotes() { os.api('i/read-all-unread-notes'); diff --git a/packages/client/src/pages/settings/other.vue b/packages/client/src/pages/settings/other.vue index 7547013832..6e48cb58a6 100644 --- a/packages/client/src/pages/settings/other.vue +++ b/packages/client/src/pages/settings/other.vue @@ -47,10 +47,6 @@ export default defineComponent({ reportError: defaultStore.makeGetterSetter('reportError'), }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { changeDebug(v) { console.log(v); diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue index bf494fa719..d35d20d17a 100644 --- a/packages/client/src/pages/settings/plugin.install.vue +++ b/packages/client/src/pages/settings/plugin.install.vue @@ -45,10 +45,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { installPlugin({ id, meta, ast, token }) { ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({ diff --git a/packages/client/src/pages/settings/plugin.vue b/packages/client/src/pages/settings/plugin.vue index d411ad2961..7a3ab9d152 100644 --- a/packages/client/src/pages/settings/plugin.vue +++ b/packages/client/src/pages/settings/plugin.vue @@ -64,10 +64,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { uninstall(plugin) { ColdDeviceStorage.set('plugins', this.plugins.filter(x => x.id !== plugin.id)); diff --git a/packages/client/src/pages/settings/privacy.vue b/packages/client/src/pages/settings/privacy.vue index 78a0ea8b8d..593ca75105 100644 --- a/packages/client/src/pages/settings/privacy.vue +++ b/packages/client/src/pages/settings/privacy.vue @@ -101,10 +101,6 @@ export default defineComponent({ this.ffVisibility = this.$i.ffVisibility; }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { save() { os.api('i/update', { diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue index 2eaf9a9f83..9a2395872e 100644 --- a/packages/client/src/pages/settings/profile.vue +++ b/packages/client/src/pages/settings/profile.vue @@ -132,10 +132,6 @@ export default defineComponent({ this.$watch('alwaysMarkNsfw', this.save); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { changeAvatar(e) { selectFile(e.currentTarget || e.target, this.$ts.avatar).then(file => { diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue index 0d4db46936..e5b1189947 100644 --- a/packages/client/src/pages/settings/reaction.vue +++ b/packages/client/src/pages/settings/reaction.vue @@ -100,10 +100,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { save() { this.$store.set('reactions', this.reactions); diff --git a/packages/client/src/pages/settings/security.vue b/packages/client/src/pages/settings/security.vue index 03f2d6300b..2386340028 100644 --- a/packages/client/src/pages/settings/security.vue +++ b/packages/client/src/pages/settings/security.vue @@ -72,10 +72,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async change() { const { canceled: canceled1, result: currentPassword } = await os.inputText({ diff --git a/packages/client/src/pages/settings/sounds.vue b/packages/client/src/pages/settings/sounds.vue index b538bf7cf5..490a1b5514 100644 --- a/packages/client/src/pages/settings/sounds.vue +++ b/packages/client/src/pages/settings/sounds.vue @@ -96,10 +96,6 @@ export default defineComponent({ this.sounds.channel = ColdDeviceStorage.get('sound_channel'); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async edit(type) { const { canceled, result } = await os.form(this.$t('_sfx.' + type), { diff --git a/packages/client/src/pages/settings/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue index 52935c75dc..deab77c615 100644 --- a/packages/client/src/pages/settings/theme.install.vue +++ b/packages/client/src/pages/settings/theme.install.vue @@ -40,10 +40,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { parseThemeCode(code) { let theme; diff --git a/packages/client/src/pages/settings/theme.manage.vue b/packages/client/src/pages/settings/theme.manage.vue index a913ba4748..a1e849b540 100644 --- a/packages/client/src/pages/settings/theme.manage.vue +++ b/packages/client/src/pages/settings/theme.manage.vue @@ -78,10 +78,6 @@ export default defineComponent({ }, }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { copyThemeCode() { copyToClipboard(this.selectedThemeCode); diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue index 6c88b65699..658e36ec05 100644 --- a/packages/client/src/pages/settings/theme.vue +++ b/packages/client/src/pages/settings/theme.vue @@ -163,10 +163,6 @@ export default defineComponent({ location.reload(); }); - onMounted(() => { - emit('info', INFO); - }); - onActivated(() => { fetchThemes().then(() => { installedThemes.value = getThemes(); diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue index 34edd0492c..19980dea14 100644 --- a/packages/client/src/pages/settings/word-mute.vue +++ b/packages/client/src/pages/settings/word-mute.vue @@ -87,10 +87,6 @@ export default defineComponent({ this.hardWordMutedNotesCount = (await os.api('i/get-word-muted-notes-count', {})).count; }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async save() { this.$store.set('mutedWords', this.softMutedWords.trim().split('\n').map(x => x.trim().split(' '))); From f96d50bc07dea94784a9b47f9ebd0800c5c5bcca Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 13 Jan 2022 02:21:43 +0900 Subject: [PATCH 20/81] wip: refactor(client): migrate paging components to composition api --- packages/client/src/pages/follow-requests.vue | 88 +++++++++---------- packages/client/src/pages/preview.vue | 24 ++--- packages/client/src/pages/search.vue | 44 +++++----- packages/client/src/pages/tag.vue | 45 ++++------ packages/client/src/router.ts | 2 +- 5 files changed, 92 insertions(+), 111 deletions(-) diff --git a/packages/client/src/pages/follow-requests.vue b/packages/client/src/pages/follow-requests.vue index 54d695091d..00dfa06564 100644 --- a/packages/client/src/pages/follow-requests.vue +++ b/packages/client/src/pages/follow-requests.vue @@ -1,6 +1,6 @@ <template> <div> - <MkPagination ref="list" :pagination="pagination" class="mk-follow-requests"> + <MkPagination ref="paginationComponent" :pagination="pagination"> <template #empty> <div class="_fullinfo"> <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> @@ -8,19 +8,21 @@ </div> </template> <template v-slot="{items}"> - <div v-for="req in items" :key="req.id" class="user _panel"> - <MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/> - <div class="body"> - <div class="name"> - <MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA> - <p class="acct">@{{ acct(req.follower) }}</p> - </div> - <div v-if="req.follower.description" class="description" :title="req.follower.description"> - <Mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/> - </div> - <div class="actions"> - <button class="_button" @click="accept(req.follower)"><i class="fas fa-check"></i></button> - <button class="_button" @click="reject(req.follower)"><i class="fas fa-times"></i></button> + <div class="mk-follow-requests"> + <div v-for="req in items" :key="req.id" class="user _panel"> + <MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/> + <div class="body"> + <div class="name"> + <MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA> + <p class="acct">@{{ acct(req.follower) }}</p> + </div> + <div v-if="req.follower.description" class="description" :title="req.follower.description"> + <Mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/> + </div> + <div class="actions"> + <button class="_button" @click="accept(req.follower)"><i class="fas fa-check"></i></button> + <button class="_button" @click="reject(req.follower)"><i class="fas fa-times"></i></button> + </div> </div> </div> </div> @@ -29,45 +31,39 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref, computed } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import { userPage, acct } from '@/filters/user'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkPagination - }, +const paginationComponent = ref<InstanceType<typeof MkPagination>>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.followRequests, - icon: 'fas fa-user-clock', - }, - pagination: { - endpoint: 'following/requests/list', - limit: 10, - }, - }; - }, +const pagination = { + endpoint: 'following/requests/list', + limit: 10, +}; - methods: { - accept(user) { - os.api('following/requests/accept', { userId: user.id }).then(() => { - this.$refs.list.reload(); - }); - }, - reject(user) { - os.api('following/requests/reject', { userId: user.id }).then(() => { - this.$refs.list.reload(); - }); - }, - userPage, - acct - } +function accept(user) { + os.api('following/requests/accept', { userId: user.id }).then(() => { + paginationComponent.value.reload(); + }); +} + +function reject(user) { + os.api('following/requests/reject', { userId: user.id }).then(() => { + paginationComponent.value.reload(); + }); +} + +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.followRequests, + icon: 'fas fa-user-clock', + bg: 'var(--bg)', + })), }); </script> diff --git a/packages/client/src/pages/preview.vue b/packages/client/src/pages/preview.vue index 9d1ebb74ed..8eb4549516 100644 --- a/packages/client/src/pages/preview.vue +++ b/packages/client/src/pages/preview.vue @@ -4,24 +4,18 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import MkSample from '@/components/sample.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkSample, - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.preview, - icon: 'fas fa-eye', - }, - } - }, +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.preview, + icon: 'fas fa-eye', + bg: 'var(--bg)', + })), }); </script> diff --git a/packages/client/src/pages/search.vue b/packages/client/src/pages/search.vue index 771d0b557e..d25d5af147 100644 --- a/packages/client/src/pages/search.vue +++ b/packages/client/src/pages/search.vue @@ -6,31 +6,31 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XNotes from '@/components/notes.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotes - }, +const props = defineProps<{ + query: string; + channel?: string; +}>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: computed(() => this.$t('searchWith', { q: this.$route.query.q })), - icon: 'fas fa-search', - }, - pagination: { - endpoint: 'notes/search', - limit: 10, - params: computed(() => ({ - query: this.$route.query.q, - channelId: this.$route.query.channel, - })) - }, - }; - }, +const pagination = { + endpoint: 'notes/search', + limit: 10, + params: computed(() => ({ + query: props.query, + channelId: props.channel, + })) +}; + +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.t('searchWith', { q: props.query }), + icon: 'fas fa-search', + bg: 'var(--bg)', + })), }); </script> diff --git a/packages/client/src/pages/tag.vue b/packages/client/src/pages/tag.vue index a9497ae801..debee2606d 100644 --- a/packages/client/src/pages/tag.vue +++ b/packages/client/src/pages/tag.vue @@ -4,37 +4,28 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XNotes from '@/components/notes.vue'; import * as symbols from '@/symbols'; -export default defineComponent({ - components: { - XNotes - }, +const props = defineProps<{ + tag: string; +}>(); - props: { - tag: { - type: String, - required: true - } - }, +const pagination = { + endpoint: 'notes/search-by-tag', + limit: 10, + params: computed(() => ({ + tag: props.tag, + })), +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.tag, - icon: 'fas fa-hashtag' - }, - pagination: { - endpoint: 'notes/search-by-tag', - limit: 10, - params: computed(() => ({ - tag: this.tag, - })) - }, - }; - }, +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: props.tag, + icon: 'fas fa-hashtag', + bg: 'var(--bg)', + })), }); </script> diff --git a/packages/client/src/router.ts b/packages/client/src/router.ts index 312374cdb9..499afefbfe 100644 --- a/packages/client/src/router.ts +++ b/packages/client/src/router.ts @@ -33,7 +33,7 @@ const defaultRoutes = [ { path: '/explore/tags/:tag', props: true, component: page('explore') }, { path: '/federation', component: page('federation') }, { path: '/emojis', component: page('emojis') }, - { path: '/search', component: page('search') }, + { path: '/search', component: page('search'), props: route => ({ query: route.query.q, channel: route.query.channel }) }, { path: '/pages', name: 'pages', component: page('pages') }, { path: '/pages/new', component: page('page-editor/page-editor') }, { path: '/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) }, From 861d028d0971b026c98f4a68881b3c37749760ee Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 13 Jan 2022 02:26:10 +0900 Subject: [PATCH 21/81] refactor --- packages/client/src/pages/admin/abuses.vue | 2 +- packages/client/src/pages/admin/emojis.vue | 4 ++-- packages/client/src/pages/admin/files.vue | 2 +- packages/client/src/pages/admin/users.vue | 2 +- packages/client/src/pages/announcements.vue | 2 +- packages/client/src/pages/channel.vue | 2 +- packages/client/src/pages/channels.vue | 6 +++--- packages/client/src/pages/clip.vue | 2 +- packages/client/src/pages/explore.vue | 4 ++-- packages/client/src/pages/featured.vue | 2 +- packages/client/src/pages/federation.vue | 2 +- packages/client/src/pages/follow-requests.vue | 2 +- packages/client/src/pages/gallery/index.vue | 10 +++++----- packages/client/src/pages/gallery/post.vue | 2 +- packages/client/src/pages/mentions.vue | 2 +- packages/client/src/pages/messages.vue | 2 +- packages/client/src/pages/my-antennas/index.vue | 2 +- packages/client/src/pages/my-clips/index.vue | 2 +- packages/client/src/pages/my-groups/index.vue | 6 +++--- packages/client/src/pages/my-lists/index.vue | 2 +- packages/client/src/pages/note.vue | 4 ++-- packages/client/src/pages/page.vue | 2 +- packages/client/src/pages/pages.vue | 6 +++--- packages/client/src/pages/search.vue | 2 +- packages/client/src/pages/settings/apps.vue | 2 +- packages/client/src/pages/settings/mute-block.vue | 4 ++-- packages/client/src/pages/settings/security.vue | 2 +- packages/client/src/pages/tag.vue | 2 +- packages/client/src/pages/user/clips.vue | 2 +- packages/client/src/pages/user/follow-list.vue | 4 ++-- packages/client/src/pages/user/gallery.vue | 2 +- packages/client/src/pages/user/pages.vue | 2 +- packages/client/src/pages/user/reactions.vue | 2 +- packages/client/src/ui/deck/direct-column.vue | 2 +- packages/client/src/ui/deck/mentions-column.vue | 2 +- 35 files changed, 50 insertions(+), 50 deletions(-) diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue index 0f19f8dbe9..31cdef492a 100644 --- a/packages/client/src/pages/admin/abuses.vue +++ b/packages/client/src/pages/admin/abuses.vue @@ -95,7 +95,7 @@ export default defineComponent({ reporterOrigin: 'combined', targetUserOrigin: 'combined', pagination: { - endpoint: 'admin/abuse-user-reports', + endpoint: 'admin/abuse-user-reports' as const, limit: 10, params: computed(() => ({ state: this.state, diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue index 38182e5be7..5b1dfe565a 100644 --- a/packages/client/src/pages/admin/emojis.vue +++ b/packages/client/src/pages/admin/emojis.vue @@ -85,7 +85,7 @@ const selectMode = ref(false); const selectedEmojis = ref<string[]>([]); const pagination = { - endpoint: 'admin/emoji/list', + endpoint: 'admin/emoji/list' as const, limit: 30, params: computed(() => ({ query: (query.value && query.value !== '') ? query.value : null, @@ -93,7 +93,7 @@ const pagination = { }; const remotePagination = { - endpoint: 'admin/emoji/list-remote', + endpoint: 'admin/emoji/list-remote' as const, limit: 30, params: computed(() => ({ query: (queryRemote.value && queryRemote.value !== '') ? queryRemote.value : null, diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue index 10341e7e36..87dd12f489 100644 --- a/packages/client/src/pages/admin/files.vue +++ b/packages/client/src/pages/admin/files.vue @@ -95,7 +95,7 @@ export default defineComponent({ type: null, searchHost: '', pagination: { - endpoint: 'admin/drive/files', + endpoint: 'admin/drive/files' as const, limit: 10, params: computed(() => ({ type: (this.type && this.type !== '') ? this.type : null, diff --git a/packages/client/src/pages/admin/users.vue b/packages/client/src/pages/admin/users.vue index 30220a2a29..03e155ddcf 100644 --- a/packages/client/src/pages/admin/users.vue +++ b/packages/client/src/pages/admin/users.vue @@ -110,7 +110,7 @@ export default defineComponent({ searchUsername: '', searchHost: '', pagination: { - endpoint: 'admin/show-users', + endpoint: 'admin/show-users' as const, limit: 10, params: computed(() => ({ sort: this.sort, diff --git a/packages/client/src/pages/announcements.vue b/packages/client/src/pages/announcements.vue index ca94640dda..53727823a4 100644 --- a/packages/client/src/pages/announcements.vue +++ b/packages/client/src/pages/announcements.vue @@ -36,7 +36,7 @@ export default defineComponent({ bg: 'var(--bg)', }, pagination: { - endpoint: 'announcements', + endpoint: 'announcements' as const, limit: 10, }, }; diff --git a/packages/client/src/pages/channel.vue b/packages/client/src/pages/channel.vue index 598e173d81..c9a8f36844 100644 --- a/packages/client/src/pages/channel.vue +++ b/packages/client/src/pages/channel.vue @@ -67,7 +67,7 @@ export default defineComponent({ channel: null, showBanner: true, pagination: { - endpoint: 'channels/timeline', + endpoint: 'channels/timeline' as const, limit: 10, params: computed(() => ({ channelId: this.channelId, diff --git a/packages/client/src/pages/channels.vue b/packages/client/src/pages/channels.vue index 48877ab3ec..4e538a6da3 100644 --- a/packages/client/src/pages/channels.vue +++ b/packages/client/src/pages/channels.vue @@ -60,15 +60,15 @@ export default defineComponent({ })), tab: 'featured', featuredPagination: { - endpoint: 'channels/featured', + endpoint: 'channels/featured' as const, noPaging: true, }, followingPagination: { - endpoint: 'channels/followed', + endpoint: 'channels/followed' as const, limit: 5, }, ownedPagination: { - endpoint: 'channels/owned', + endpoint: 'channels/owned' as const, limit: 5, }, }; diff --git a/packages/client/src/pages/clip.vue b/packages/client/src/pages/clip.vue index b375856803..6b49221d32 100644 --- a/packages/client/src/pages/clip.vue +++ b/packages/client/src/pages/clip.vue @@ -50,7 +50,7 @@ export default defineComponent({ } : null), clip: null, pagination: { - endpoint: 'clips/notes', + endpoint: 'clips/notes' as const, limit: 10, params: computed(() => ({ clipId: this.clipId, diff --git a/packages/client/src/pages/explore.vue b/packages/client/src/pages/explore.vue index a3c3b771f2..04cc3662a7 100644 --- a/packages/client/src/pages/explore.vue +++ b/packages/client/src/pages/explore.vue @@ -156,7 +156,7 @@ export default defineComponent({ sort: '+createdAt', } }, searchPagination: { - endpoint: 'users/search', + endpoint: 'users/search' as const, limit: 10, params: computed(() => (this.searchQuery && this.searchQuery !== '') ? { query: this.searchQuery, @@ -178,7 +178,7 @@ export default defineComponent({ }, tagUsers(): any { return { - endpoint: 'hashtags/users', + endpoint: 'hashtags/users' as const, limit: 30, params: { tag: this.tag, diff --git a/packages/client/src/pages/featured.vue b/packages/client/src/pages/featured.vue index efa74ca599..725c70f0f7 100644 --- a/packages/client/src/pages/featured.vue +++ b/packages/client/src/pages/featured.vue @@ -10,7 +10,7 @@ import * as symbols from '@/symbols'; import { i18n } from '@/i18n'; const pagination = { - endpoint: 'notes/featured', + endpoint: 'notes/featured' as const, limit: 10, offsetMode: true, }; diff --git a/packages/client/src/pages/federation.vue b/packages/client/src/pages/federation.vue index e23c4f8f16..610c9233a3 100644 --- a/packages/client/src/pages/federation.vue +++ b/packages/client/src/pages/federation.vue @@ -127,7 +127,7 @@ export default defineComponent({ state: 'federating', sort: '+pubSub', pagination: { - endpoint: 'federation/instances', + endpoint: 'federation/instances' as const, limit: 10, offsetMode: true, params: computed(() => ({ diff --git a/packages/client/src/pages/follow-requests.vue b/packages/client/src/pages/follow-requests.vue index 00dfa06564..764daa0d3e 100644 --- a/packages/client/src/pages/follow-requests.vue +++ b/packages/client/src/pages/follow-requests.vue @@ -42,7 +42,7 @@ import { i18n } from '@/i18n'; const paginationComponent = ref<InstanceType<typeof MkPagination>>(); const pagination = { - endpoint: 'following/requests/list', + endpoint: 'following/requests/list' as const, limit: 10, }; diff --git a/packages/client/src/pages/gallery/index.vue b/packages/client/src/pages/gallery/index.vue index cd0d2a40e4..a19d69d5c2 100644 --- a/packages/client/src/pages/gallery/index.vue +++ b/packages/client/src/pages/gallery/index.vue @@ -81,19 +81,19 @@ export default defineComponent({ }, tab: 'explore', recentPostsPagination: { - endpoint: 'gallery/posts', + endpoint: 'gallery/posts' as const, limit: 6, }, popularPostsPagination: { - endpoint: 'gallery/featured', + endpoint: 'gallery/featured' as const, limit: 5, }, myPostsPagination: { - endpoint: 'i/gallery/posts', + endpoint: 'i/gallery/posts' as const, limit: 5, }, likedPostsPagination: { - endpoint: 'i/gallery/likes', + endpoint: 'i/gallery/likes' as const, limit: 5, }, tags: [], @@ -106,7 +106,7 @@ export default defineComponent({ }, tagUsers(): any { return { - endpoint: 'hashtags/users', + endpoint: 'hashtags/users' as const, limit: 30, params: { tag: this.tag, diff --git a/packages/client/src/pages/gallery/post.vue b/packages/client/src/pages/gallery/post.vue index 9d769deca0..fff2b6a74e 100644 --- a/packages/client/src/pages/gallery/post.vue +++ b/packages/client/src/pages/gallery/post.vue @@ -93,7 +93,7 @@ export default defineComponent({ }] } : null), otherPostsPagination: { - endpoint: 'users/gallery/posts', + endpoint: 'users/gallery/posts' as const, limit: 6, params: computed(() => ({ userId: this.post.user.id diff --git a/packages/client/src/pages/mentions.vue b/packages/client/src/pages/mentions.vue index ea23c6a2f6..bda56fc729 100644 --- a/packages/client/src/pages/mentions.vue +++ b/packages/client/src/pages/mentions.vue @@ -10,7 +10,7 @@ import * as symbols from '@/symbols'; import { i18n } from '@/i18n'; const pagination = { - endpoint: 'notes/mentions', + endpoint: 'notes/mentions' as const, limit: 10, }; diff --git a/packages/client/src/pages/messages.vue b/packages/client/src/pages/messages.vue index 448aa0241f..8efdc55586 100644 --- a/packages/client/src/pages/messages.vue +++ b/packages/client/src/pages/messages.vue @@ -10,7 +10,7 @@ import * as symbols from '@/symbols'; import { i18n } from '@/i18n'; const pagination = { - endpoint: 'notes/mentions', + endpoint: 'notes/mentions' as const, limit: 10, params: () => ({ visibility: 'specified' diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue index d185e796c3..7138d269a9 100644 --- a/packages/client/src/pages/my-antennas/index.vue +++ b/packages/client/src/pages/my-antennas/index.vue @@ -38,7 +38,7 @@ export default defineComponent({ } }, pagination: { - endpoint: 'antennas/list', + endpoint: 'antennas/list' as const, limit: 10, }, }; diff --git a/packages/client/src/pages/my-clips/index.vue b/packages/client/src/pages/my-clips/index.vue index a5bbc3fd2d..ccfb9095d1 100644 --- a/packages/client/src/pages/my-clips/index.vue +++ b/packages/client/src/pages/my-clips/index.vue @@ -38,7 +38,7 @@ export default defineComponent({ } }, pagination: { - endpoint: 'clips/list', + endpoint: 'clips/list' as const, limit: 10, }, draft: null, diff --git a/packages/client/src/pages/my-groups/index.vue b/packages/client/src/pages/my-groups/index.vue index db5ccde466..4b2b2963a8 100644 --- a/packages/client/src/pages/my-groups/index.vue +++ b/packages/client/src/pages/my-groups/index.vue @@ -87,15 +87,15 @@ export default defineComponent({ })), tab: 'owned', ownedPagination: { - endpoint: 'users/groups/owned', + endpoint: 'users/groups/owned' as const, limit: 10, }, joinedPagination: { - endpoint: 'users/groups/joined', + endpoint: 'users/groups/joined' as const, limit: 10, }, invitationPagination: { - endpoint: 'i/user-group-invites', + endpoint: 'i/user-group-invites' as const, limit: 10, }, }; diff --git a/packages/client/src/pages/my-lists/index.vue b/packages/client/src/pages/my-lists/index.vue index 94a869b9ff..1b1b7e2d02 100644 --- a/packages/client/src/pages/my-lists/index.vue +++ b/packages/client/src/pages/my-lists/index.vue @@ -40,7 +40,7 @@ export default defineComponent({ }, }, pagination: { - endpoint: 'users/lists/list', + endpoint: 'users/lists/list' as const, limit: 10, }, }; diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue index d40082381c..2249fa2b91 100644 --- a/packages/client/src/pages/note.vue +++ b/packages/client/src/pages/note.vue @@ -82,7 +82,7 @@ export default defineComponent({ showNext: false, error: null, prev: { - endpoint: 'users/notes', + endpoint: 'users/notes' as const, limit: 10, params: init => ({ userId: this.note.userId, @@ -91,7 +91,7 @@ export default defineComponent({ }, next: { reversed: true, - endpoint: 'users/notes', + endpoint: 'users/notes' as const, limit: 10, params: init => ({ userId: this.note.userId, diff --git a/packages/client/src/pages/page.vue b/packages/client/src/pages/page.vue index 5cb3948f1c..429d1ddea2 100644 --- a/packages/client/src/pages/page.vue +++ b/packages/client/src/pages/page.vue @@ -106,7 +106,7 @@ export default defineComponent({ page: null, error: null, otherPostsPagination: { - endpoint: 'users/pages', + endpoint: 'users/pages' as const, limit: 6, params: computed(() => ({ userId: this.page.user.id diff --git a/packages/client/src/pages/pages.vue b/packages/client/src/pages/pages.vue index f1dd64f119..dcccf7f7c4 100644 --- a/packages/client/src/pages/pages.vue +++ b/packages/client/src/pages/pages.vue @@ -62,15 +62,15 @@ export default defineComponent({ })), tab: 'featured', featuredPagesPagination: { - endpoint: 'pages/featured', + endpoint: 'pages/featured' as const, noPaging: true, }, myPagesPagination: { - endpoint: 'i/pages', + endpoint: 'i/pages' as const, limit: 5, }, likedPagesPagination: { - endpoint: 'i/page-likes', + endpoint: 'i/page-likes' as const, limit: 5, }, }; diff --git a/packages/client/src/pages/search.vue b/packages/client/src/pages/search.vue index d25d5af147..ce2b7035da 100644 --- a/packages/client/src/pages/search.vue +++ b/packages/client/src/pages/search.vue @@ -18,7 +18,7 @@ const props = defineProps<{ }>(); const pagination = { - endpoint: 'notes/search', + endpoint: 'notes/search' as const, limit: 10, params: computed(() => ({ query: props.query, diff --git a/packages/client/src/pages/settings/apps.vue b/packages/client/src/pages/settings/apps.vue index a75e06165d..9c0fa8a54d 100644 --- a/packages/client/src/pages/settings/apps.vue +++ b/packages/client/src/pages/settings/apps.vue @@ -58,7 +58,7 @@ export default defineComponent({ bg: 'var(--bg)', }, pagination: { - endpoint: 'i/apps', + endpoint: 'i/apps' as const, limit: 100, params: { sort: '+lastUsedAt' diff --git a/packages/client/src/pages/settings/mute-block.vue b/packages/client/src/pages/settings/mute-block.vue index a4f9b41e80..903d32d08c 100644 --- a/packages/client/src/pages/settings/mute-block.vue +++ b/packages/client/src/pages/settings/mute-block.vue @@ -56,11 +56,11 @@ export default defineComponent({ }, tab: 'mute', mutingPagination: { - endpoint: 'mute/list', + endpoint: 'mute/list' as const, limit: 10, }, blockingPagination: { - endpoint: 'blocking/list', + endpoint: 'blocking/list' as const, limit: 10, }, } diff --git a/packages/client/src/pages/settings/security.vue b/packages/client/src/pages/settings/security.vue index 2386340028..6fb3f1c413 100644 --- a/packages/client/src/pages/settings/security.vue +++ b/packages/client/src/pages/settings/security.vue @@ -66,7 +66,7 @@ export default defineComponent({ bg: 'var(--bg)', }, pagination: { - endpoint: 'i/signin-history', + endpoint: 'i/signin-history' as const, limit: 5, }, } diff --git a/packages/client/src/pages/tag.vue b/packages/client/src/pages/tag.vue index debee2606d..8d8dc0a65c 100644 --- a/packages/client/src/pages/tag.vue +++ b/packages/client/src/pages/tag.vue @@ -14,7 +14,7 @@ const props = defineProps<{ }>(); const pagination = { - endpoint: 'notes/search-by-tag', + endpoint: 'notes/search-by-tag' as const, limit: 10, params: computed(() => ({ tag: props.tag, diff --git a/packages/client/src/pages/user/clips.vue b/packages/client/src/pages/user/clips.vue index aad5317ce0..870e6f7174 100644 --- a/packages/client/src/pages/user/clips.vue +++ b/packages/client/src/pages/user/clips.vue @@ -28,7 +28,7 @@ export default defineComponent({ data() { return { pagination: { - endpoint: 'users/clips', + endpoint: 'users/clips' as const, limit: 20, params: { userId: this.user.id, diff --git a/packages/client/src/pages/user/follow-list.vue b/packages/client/src/pages/user/follow-list.vue index e12ea477ca..b34757c166 100644 --- a/packages/client/src/pages/user/follow-list.vue +++ b/packages/client/src/pages/user/follow-list.vue @@ -33,14 +33,14 @@ export default defineComponent({ data() { return { followingPagination: { - endpoint: 'users/following', + endpoint: 'users/following' as const, limit: 20, params: computed(() => ({ userId: this.user.id, })), }, followersPagination: { - endpoint: 'users/followers', + endpoint: 'users/followers' as const, limit: 20, params: computed(() => ({ userId: this.user.id, diff --git a/packages/client/src/pages/user/gallery.vue b/packages/client/src/pages/user/gallery.vue index 88f0604f1f..07dda4a292 100644 --- a/packages/client/src/pages/user/gallery.vue +++ b/packages/client/src/pages/user/gallery.vue @@ -29,7 +29,7 @@ export default defineComponent({ data() { return { pagination: { - endpoint: 'users/gallery/posts', + endpoint: 'users/gallery/posts' as const, limit: 6, params: computed(() => ({ userId: this.user.id diff --git a/packages/client/src/pages/user/pages.vue b/packages/client/src/pages/user/pages.vue index 3075dd5729..6ce84da0a2 100644 --- a/packages/client/src/pages/user/pages.vue +++ b/packages/client/src/pages/user/pages.vue @@ -27,7 +27,7 @@ export default defineComponent({ data() { return { pagination: { - endpoint: 'users/pages', + endpoint: 'users/pages' as const, limit: 20, params: computed(() => ({ userId: this.user.id, diff --git a/packages/client/src/pages/user/reactions.vue b/packages/client/src/pages/user/reactions.vue index f51f6669c3..5cb035bace 100644 --- a/packages/client/src/pages/user/reactions.vue +++ b/packages/client/src/pages/user/reactions.vue @@ -36,7 +36,7 @@ export default defineComponent({ data() { return { pagination: { - endpoint: 'users/reactions', + endpoint: 'users/reactions' as const, limit: 20, params: computed(() => ({ userId: this.user.id, diff --git a/packages/client/src/ui/deck/direct-column.vue b/packages/client/src/ui/deck/direct-column.vue index 4206b09b97..7bf6344153 100644 --- a/packages/client/src/ui/deck/direct-column.vue +++ b/packages/client/src/ui/deck/direct-column.vue @@ -32,7 +32,7 @@ export default defineComponent({ data() { return { pagination: { - endpoint: 'notes/mentions', + endpoint: 'notes/mentions' as const, limit: 10, params: computed(() => ({ visibility: 'specified' diff --git a/packages/client/src/ui/deck/mentions-column.vue b/packages/client/src/ui/deck/mentions-column.vue index 4b8dc0c4ee..e007b3e08a 100644 --- a/packages/client/src/ui/deck/mentions-column.vue +++ b/packages/client/src/ui/deck/mentions-column.vue @@ -32,7 +32,7 @@ export default defineComponent({ data() { return { pagination: { - endpoint: 'notes/mentions', + endpoint: 'notes/mentions' as const, limit: 10, }, } From 7271fbb09217bc79380e924d3831fe054c9c3ab6 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 13 Jan 2022 02:29:27 +0900 Subject: [PATCH 22/81] wip: refactor(client): migrate paging components to composition api --- packages/client/src/ui/deck/direct-column.vue | 45 ++++++------------- .../client/src/ui/deck/mentions-column.vue | 39 +++++----------- 2 files changed, 25 insertions(+), 59 deletions(-) diff --git a/packages/client/src/ui/deck/direct-column.vue b/packages/client/src/ui/deck/direct-column.vue index 7bf6344153..ca70f693c3 100644 --- a/packages/client/src/ui/deck/direct-column.vue +++ b/packages/client/src/ui/deck/direct-column.vue @@ -2,43 +2,26 @@ <XColumn :column="column" :is-stacked="isStacked"> <template #header><i class="fas fa-envelope" style="margin-right: 8px;"></i>{{ column.name }}</template> - <XNotes :pagination="pagination" @before="before()" @after="after()"/> + <XNotes :pagination="pagination"/> </XColumn> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XColumn from './column.vue'; import XNotes from '@/components/notes.vue'; import * as os from '@/os'; -export default defineComponent({ - components: { - XColumn, - XNotes - }, +const props = defineProps<{ + column: Record<string, unknown>; // TODO + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, - - data() { - return { - pagination: { - endpoint: 'notes/mentions' as const, - limit: 10, - params: computed(() => ({ - visibility: 'specified' - })), - }, - } - }, -}); +const pagination = { + point: 'notes/mentions' as const, + limit: 10, + params: computed(() => ({ + visibility: 'specified' as const, + })), +}; </script> diff --git a/packages/client/src/ui/deck/mentions-column.vue b/packages/client/src/ui/deck/mentions-column.vue index e007b3e08a..6822e7ef06 100644 --- a/packages/client/src/ui/deck/mentions-column.vue +++ b/packages/client/src/ui/deck/mentions-column.vue @@ -2,40 +2,23 @@ <XColumn :column="column" :is-stacked="isStacked"> <template #header><i class="fas fa-at" style="margin-right: 8px;"></i>{{ column.name }}</template> - <XNotes :pagination="pagination" @before="before()" @after="after()"/> + <XNotes :pagination="pagination"/> </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XColumn from './column.vue'; import XNotes from '@/components/notes.vue'; import * as os from '@/os'; -export default defineComponent({ - components: { - XColumn, - XNotes - }, +const props = defineProps<{ + column: Record<string, unknown>; // TODO + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, - - data() { - return { - pagination: { - endpoint: 'notes/mentions' as const, - limit: 10, - }, - } - }, -}); +const pagination = { + endpoint: 'notes/mentions' as const, + limit: 10, +}; </script> From 2900f998b13e2fd1880224a53cc2472110b55dd4 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 13 Jan 2022 02:36:51 +0900 Subject: [PATCH 23/81] wip: refactor(client): migrate paging components to composition api --- packages/client/src/pages/emojis.emoji.vue | 42 +++++----- packages/client/src/pages/emojis.vue | 76 +++++++++---------- .../client/src/pages/timeline.tutorial.vue | 24 ++---- 3 files changed, 58 insertions(+), 84 deletions(-) diff --git a/packages/client/src/pages/emojis.emoji.vue b/packages/client/src/pages/emojis.emoji.vue index 5dab72daea..83539ce7a3 100644 --- a/packages/client/src/pages/emojis.emoji.vue +++ b/packages/client/src/pages/emojis.emoji.vue @@ -8,35 +8,29 @@ </button> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import * as os from '@/os'; import copyToClipboard from '@/scripts/copy-to-clipboard'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - emoji: { - type: Object, - required: true, - } - }, +const props = defineProps<{ + emoji: Record<string, unknown>; // TODO +}>(); - methods: { - menu(ev) { - os.popupMenu([{ - type: 'label', - text: ':' + this.emoji.name + ':', - }, { - text: this.$ts.copy, - icon: 'fas fa-copy', - action: () => { - copyToClipboard(`:${this.emoji.name}:`); - os.success(); - } - }], ev.currentTarget || ev.target); +function menu(ev) { + os.popupMenu([{ + type: 'label', + text: ':' + props.emoji.name + ':', + }, { + text: i18n.locale.copy, + icon: 'fas fa-copy', + action: () => { + copyToClipboard(`:${props.emoji.name}:`); + os.success(); } - } -}); + }], ev.currentTarget || ev.target); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/emojis.vue b/packages/client/src/pages/emojis.vue index 2adb5345e2..6577f5abd9 100644 --- a/packages/client/src/pages/emojis.vue +++ b/packages/client/src/pages/emojis.vue @@ -4,55 +4,47 @@ </div> </template> -<script lang="ts"> -import { defineComponent, computed } from 'vue'; +<script lang="ts" setup> +import { ref, computed } from 'vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import XCategory from './emojis.category.vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XCategory, - }, +const tab = ref('category'); - data() { - return { - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.customEmojis, - icon: 'fas fa-laugh', - bg: 'var(--bg)', - actions: [{ - icon: 'fas fa-ellipsis-h', - handler: this.menu - }], - })), - tab: 'category', +function menu(ev) { + os.popupMenu([{ + icon: 'fas fa-download', + text: i18n.locale.export, + action: async () => { + os.api('export-custom-emojis', { + }) + .then(() => { + os.alert({ + type: 'info', + text: i18n.locale.exportRequested, + }); + }).catch((e) => { + os.alert({ + type: 'error', + text: e.message, + }); + }); } - }, + }], ev.currentTarget || ev.target); +} - methods: { - menu(ev) { - os.popupMenu([{ - icon: 'fas fa-download', - text: this.$ts.export, - action: async () => { - os.api('export-custom-emojis', { - }) - .then(() => { - os.alert({ - type: 'info', - text: this.$ts.exportRequested, - }); - }).catch((e) => { - os.alert({ - type: 'error', - text: e.message, - }); - }); - } - }], ev.currentTarget || ev.target); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.customEmojis, + icon: 'fas fa-laugh', + bg: 'var(--bg)', + actions: [{ + icon: 'fas fa-ellipsis-h', + handler: menu, + }], + }, }); </script> diff --git a/packages/client/src/pages/timeline.tutorial.vue b/packages/client/src/pages/timeline.tutorial.vue index 3775796940..432d28c60b 100644 --- a/packages/client/src/pages/timeline.tutorial.vue +++ b/packages/client/src/pages/timeline.tutorial.vue @@ -65,26 +65,14 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import MkButton from '@/components/ui/button.vue'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - MkButton, - }, - - data() { - return { - } - }, - - computed: { - tutorial: { - get() { return this.$store.reactiveState.tutorial.value || 0; }, - set(value) { this.$store.set('tutorial', value); } - }, - }, +const tutorial = computed({ + get() { return defaultStore.reactiveState.tutorial.value || 0; }, + set(value) { defaultStore.set('tutorial', value); } }); </script> From bd1f741dad0c33ed945d05ed413938fe7ea42ee5 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 13 Jan 2022 02:46:14 +0900 Subject: [PATCH 24/81] wip: refactor(client): migrate paging components to composition api --- .../client/src/pages/user/follow-list.vue | 57 +++++++------------ .../client/src/pages/user/index.activity.vue | 27 +++------ packages/client/src/pages/user/pages.vue | 39 +++++-------- packages/client/src/pages/user/reactions.vue | 40 +++++-------- 4 files changed, 55 insertions(+), 108 deletions(-) diff --git a/packages/client/src/pages/user/follow-list.vue b/packages/client/src/pages/user/follow-list.vue index b34757c166..98a1fc0f86 100644 --- a/packages/client/src/pages/user/follow-list.vue +++ b/packages/client/src/pages/user/follow-list.vue @@ -8,47 +8,32 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as misskey from 'misskey-js'; import MkUserInfo from '@/components/user-info.vue'; import MkPagination from '@/components/ui/pagination.vue'; -export default defineComponent({ - components: { - MkPagination, - MkUserInfo, - }, +const props = defineProps<{ + user: misskey.entities.User; + type: 'following' | 'followers'; +}>(); - props: { - user: { - type: Object, - required: true - }, - type: { - type: String, - required: true - }, - }, +const followingPagination = { + endpoint: 'users/following' as const, + limit: 20, + params: computed(() => ({ + userId: props.user.id, + })), +}; - data() { - return { - followingPagination: { - endpoint: 'users/following' as const, - limit: 20, - params: computed(() => ({ - userId: this.user.id, - })), - }, - followersPagination: { - endpoint: 'users/followers' as const, - limit: 20, - params: computed(() => ({ - userId: this.user.id, - })), - }, - }; - }, -}); +const followersPagination = { + endpoint: 'users/followers' as const, + limit: 20, + params: computed(() => ({ + userId: props.user.id, + })), +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/user/index.activity.vue b/packages/client/src/pages/user/index.activity.vue index e51d6c6090..43a4f476f1 100644 --- a/packages/client/src/pages/user/index.activity.vue +++ b/packages/client/src/pages/user/index.activity.vue @@ -8,27 +8,16 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import MkContainer from '@/components/ui/container.vue'; import MkChart from '@/components/chart.vue'; -export default defineComponent({ - components: { - MkContainer, - MkChart, - }, - props: { - user: { - type: Object, - required: true - }, - limit: { - type: Number, - required: false, - default: 40 - } - }, +const props = withDefaults(defineProps<{ + user: misskey.entities.User; + limit?: number; +}>(), { + limit: 40, }); </script> diff --git a/packages/client/src/pages/user/pages.vue b/packages/client/src/pages/user/pages.vue index 6ce84da0a2..ad101158e0 100644 --- a/packages/client/src/pages/user/pages.vue +++ b/packages/client/src/pages/user/pages.vue @@ -6,36 +6,23 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as misskey from 'misskey-js'; import MkPagePreview from '@/components/page-preview.vue'; import MkPagination from '@/components/ui/pagination.vue'; -export default defineComponent({ - components: { - MkPagination, - MkPagePreview, - }, +const props = defineProps<{ + user: misskey.entities.User; +}>(); - props: { - user: { - type: Object, - required: true - }, - }, - - data() { - return { - pagination: { - endpoint: 'users/pages' as const, - limit: 20, - params: computed(() => ({ - userId: this.user.id, - })), - }, - }; - }, -}); +const pagination = { + endpoint: 'users/pages' as const, + limit: 20, + params: computed(() => ({ + userId: props.user.id, + })), +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/user/reactions.vue b/packages/client/src/pages/user/reactions.vue index 5cb035bace..0381542b4d 100644 --- a/packages/client/src/pages/user/reactions.vue +++ b/packages/client/src/pages/user/reactions.vue @@ -13,38 +13,24 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as misskey from 'misskey-js'; import MkPagination from '@/components/ui/pagination.vue'; import MkNote from '@/components/note.vue'; import MkReactionIcon from '@/components/reaction-icon.vue'; -export default defineComponent({ - components: { - MkPagination, - MkNote, - MkReactionIcon, - }, +const props = defineProps<{ + user: misskey.entities.User; +}>(); - props: { - user: { - type: Object, - required: true - }, - }, - - data() { - return { - pagination: { - endpoint: 'users/reactions' as const, - limit: 20, - params: computed(() => ({ - userId: this.user.id, - })), - }, - }; - }, -}); +const pagination = { + endpoint: 'users/reactions' as const, + limit: 20, + params: computed(() => ({ + userId: props.user.id, + })), +}; </script> <style lang="scss" scoped> From ef4d78dda25e741c536f535aece0b20b488b9af4 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 13 Jan 2022 02:55:19 +0900 Subject: [PATCH 25/81] wip: refactor(client): migrate paging components to composition api --- .../src/components/reactions-viewer.vue | 34 ++++------- .../client/src/components/waiting-dialog.vue | 57 +++++++------------ 2 files changed, 30 insertions(+), 61 deletions(-) diff --git a/packages/client/src/components/reactions-viewer.vue b/packages/client/src/components/reactions-viewer.vue index 59fcbb7129..a9bf51f65f 100644 --- a/packages/client/src/components/reactions-viewer.vue +++ b/packages/client/src/components/reactions-viewer.vue @@ -4,31 +4,19 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as misskey from 'misskey-js'; +import { $i } from '@/account'; import XReaction from './reactions-viewer.reaction.vue'; -export default defineComponent({ - components: { - XReaction - }, - props: { - note: { - type: Object, - required: true - }, - }, - data() { - return { - initialReactions: new Set(Object.keys(this.note.reactions)) - }; - }, - computed: { - isMe(): boolean { - return this.$i && this.$i.id === this.note.userId; - }, - }, -}); +const props = defineProps<{ + note: misskey.entities.Note; +}>(); + +const initialReactions = new Set(Object.keys(props.note.reactions)); + +const isMe = computed(() => $i && $i.id === props.note.userId); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/waiting-dialog.vue b/packages/client/src/components/waiting-dialog.vue index 10aedbd8f6..7dfcc55695 100644 --- a/packages/client/src/components/waiting-dialog.vue +++ b/packages/client/src/components/waiting-dialog.vue @@ -1,5 +1,5 @@ <template> -<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="success ? done() : () => {}" @closed="$emit('closed')"> +<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="success ? done() : () => {}" @closed="emit('closed')"> <div class="iuyakobc" :class="{ iconOnly: (text == null) || success }"> <i v-if="success" class="fas fa-check icon success"></i> <i v-else class="fas fa-spinner fa-pulse icon waiting"></i> @@ -8,49 +8,30 @@ </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { watch, ref } from 'vue'; import MkModal from '@/components/ui/modal.vue'; -export default defineComponent({ - components: { - MkModal, - }, +const modal = ref<InstanceType<typeof MkModal>>(); - props: { - success: { - type: Boolean, - required: true, - }, - showing: { - type: Boolean, - required: true, - }, - text: { - type: String, - required: false, - }, - }, +const props = defineProps<{ + success: boolean; + showing: boolean; + text?: string; +}>(); - emits: ['done', 'closed'], +const emit = defineEmits<{ + (e: 'done'); + (e: 'closed'); +}>(); - data() { - return { - }; - }, +function done() { + emit('done'); + modal.value.close(); +} - watch: { - showing() { - if (!this.showing) this.done(); - } - }, - - methods: { - done() { - this.$emit('done'); - this.$refs.modal.close(); - }, - } +watch(() => props.showing, () => { + if (!props.showing) done(); }); </script> From 27778f839ac7435abc7852ed01cf1d6b9a3915a2 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 14 Jan 2022 01:44:08 +0900 Subject: [PATCH 26/81] :art: --- packages/client/src/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/src/style.scss b/packages/client/src/style.scss index b95a5c3950..c1d47ffd08 100644 --- a/packages/client/src/style.scss +++ b/packages/client/src/style.scss @@ -26,6 +26,7 @@ html { background-size: cover; background-position: center; color: var(--fg); + accent-color: var(--accent); overflow: auto; overflow-wrap: break-word; font-family: "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif; From 28193f12ca0fd20544b0c9807661fb9a7e7c2a0e Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 14 Jan 2022 10:25:51 +0900 Subject: [PATCH 27/81] wip: refactor(client): migrate paging components to composition api --- .../{note.sub.vue => MkNoteSub.vue} | 77 +- .../client/src/components/note-detailed.vue | 900 +++-------------- .../client/src/components/note-header.vue | 28 +- .../client/src/components/note-simple.vue | 6 +- packages/client/src/components/note.vue | 906 ++++-------------- packages/client/src/components/notes.vue | 6 +- .../client/src/components/notifications.vue | 9 +- .../client/src/components/ui/pagination.vue | 5 - packages/client/src/pages/favorites.vue | 11 +- packages/client/src/pages/user/reactions.vue | 2 +- .../client/src/scripts/check-word-mute.ts | 2 +- packages/client/src/scripts/get-note-menu.ts | 310 ++++++ .../client/src/scripts/use-note-capture.ts | 123 +++ packages/client/src/store.ts | 2 +- 14 files changed, 791 insertions(+), 1596 deletions(-) rename packages/client/src/components/{note.sub.vue => MkNoteSub.vue} (66%) create mode 100644 packages/client/src/scripts/get-note-menu.ts create mode 100644 packages/client/src/scripts/use-note-capture.ts diff --git a/packages/client/src/components/note.sub.vue b/packages/client/src/components/MkNoteSub.vue similarity index 66% rename from packages/client/src/components/note.sub.vue rename to packages/client/src/components/MkNoteSub.vue index de4218e535..30c27e6235 100644 --- a/packages/client/src/components/note.sub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -10,13 +10,13 @@ <XCwButton v-model="showContent" :note="note"/> </p> <div v-show="note.cw == null || showContent" class="content"> - <XSubNote-content class="text" :note="note"/> + <MkNoteSubNoteContent class="text" :note="note"/> </div> </div> </div> </div> <template v-if="depth < 5"> - <XSub v-for="reply in replies" :key="reply.id" :note="reply" class="reply" :detail="true" :depth="depth + 1"/> + <MkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" class="reply" :detail="true" :depth="depth + 1"/> </template> <div v-else class="more"> <MkA class="text _link" :to="notePage(note)">{{ $ts.continueThread }} <i class="fas fa-angle-double-right"></i></MkA> @@ -24,63 +24,36 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import { notePage } from '@/filters/note'; import XNoteHeader from './note-header.vue'; -import XSubNoteContent from './sub-note-content.vue'; +import MkNoteSubNoteContent from './sub-note-content.vue'; import XCwButton from './cw-button.vue'; import * as os from '@/os'; -export default defineComponent({ - name: 'XSub', +const props = withDefaults(defineProps<{ + note: misskey.entities.Note; + detail?: boolean; - components: { - XNoteHeader, - XSubNoteContent, - XCwButton, - }, - - props: { - note: { - type: Object, - required: true - }, - detail: { - type: Boolean, - required: false, - default: false - }, - // how many notes are in between this one and the note being viewed in detail - depth: { - type: Number, - required: false, - default: 1 - }, - }, - - data() { - return { - showContent: false, - replies: [], - }; - }, - - created() { - if (this.detail) { - os.api('notes/children', { - noteId: this.note.id, - limit: 5 - }).then(replies => { - this.replies = replies; - }); - } - }, - - methods: { - notePage, - } + // how many notes are in between this one and the note being viewed in detail + depth?: number; +}>(), { + depth: 1, }); + +let showContent = $ref(false); +let replies: misskey.entities.Note[] = $ref([]); + +if (props.detail) { + os.api('notes/children', { + noteId: props.note.id, + limit: 5 + }).then(res => { + replies = res; + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue index a5cb2f0426..07e9920f65 100644 --- a/packages/client/src/components/note-detailed.vue +++ b/packages/client/src/components/note-detailed.vue @@ -8,8 +8,8 @@ :tabindex="!isDeleted ? '-1' : null" :class="{ renote: isRenote }" > - <XSub v-for="note in conversation" :key="note.id" class="reply-to-more" :note="note"/> - <XSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> + <MkNoteSub v-for="note in conversation" :key="note.id" class="reply-to-more" :note="note"/> + <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> <div v-if="isRenote" class="renote"> <MkAvatar class="avatar" :user="note.user"/> <i class="fas fa-retweet"></i> @@ -107,7 +107,7 @@ </footer> </div> </article> - <XSub v-for="note in replies" :key="note.id" :note="note" class="reply" :detail="true"/> + <MkNoteSub 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 :src="$ts.userSaysSomething" tag="small"> @@ -120,765 +120,171 @@ </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { computed, inject, onMounted, onUnmounted, reactive, ref } from 'vue'; import * as mfm from 'mfm-js'; -import { sum } from '@/scripts/array'; -import XSub from './note.sub.vue'; -import XNoteHeader from './note-header.vue'; +import * as misskey from 'misskey-js'; +import MkNoteSub from './MkNoteSub.vue'; import XNoteSimple from './note-simple.vue'; import XReactionsViewer from './reactions-viewer.vue'; import XMediaList from './media-list.vue'; import XCwButton from './cw-button.vue'; import XPoll from './poll.vue'; import XRenoteButton from './renote-button.vue'; +import MkUrlPreview from '@/components/url-preview.vue'; +import MkInstanceTicker from '@/components/instance-ticker.vue'; 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'; import { userPage } from '@/filters/user'; import { notePage } from '@/filters/note'; import * as os from '@/os'; -import { stream } from '@/stream'; -import { noteActions, noteViewInterruptors } from '@/store'; +import { defaultStore, noteViewInterruptors } from '@/store'; import { reactionPicker } from '@/scripts/reaction-picker'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; - -// TODO: note.vueとほぼ同じなので共通化したい -export default defineComponent({ - components: { - XSub, - XNoteHeader, - XNoteSimple, - XReactionsViewer, - XMediaList, - XCwButton, - XPoll, - XRenoteButton, - MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')), - MkInstanceTicker: defineAsyncComponent(() => import('@/components/instance-ticker.vue')), - }, - - inject: { - inChannel: { - default: null - }, - }, - - props: { - note: { - type: Object, - required: true - }, - }, - - emits: ['update:note'], - - data() { - return { - connection: null, - conversation: [], - replies: [], - showContent: false, - isDeleted: false, - muted: false, - translation: null, - translating: false, - notePage, - }; - }, - - computed: { - rs() { - return this.$store.state.reactions; - }, - keymap(): any { - return { - 'r': () => this.reply(true), - 'e|a|plus': () => this.react(true), - 'q': () => this.$refs.renoteButton.renote(true), - 'f|b': this.favorite, - 'delete|ctrl+d': this.del, - 'ctrl+q': this.renoteDirectly, - 'up|k|shift+tab': this.focusBefore, - 'down|j|tab': this.focusAfter, - 'esc': this.blur, - 'm|o': () => this.menu(true), - 's': this.toggleShowContent, - '1': () => this.reactDirectly(this.rs[0]), - '2': () => this.reactDirectly(this.rs[1]), - '3': () => this.reactDirectly(this.rs[2]), - '4': () => this.reactDirectly(this.rs[3]), - '5': () => this.reactDirectly(this.rs[4]), - '6': () => this.reactDirectly(this.rs[5]), - '7': () => this.reactDirectly(this.rs[6]), - '8': () => this.reactDirectly(this.rs[7]), - '9': () => this.reactDirectly(this.rs[8]), - '0': () => this.reactDirectly(this.rs[9]), - }; - }, - - isRenote(): boolean { - return (this.note.renote && - this.note.text == null && - this.note.fileIds.length == 0 && - this.note.poll == null); - }, - - appearNote(): any { - return this.isRenote ? this.note.renote : this.note; - }, - - isMyNote(): boolean { - return this.$i && (this.$i.id === this.appearNote.userId); - }, - - isMyRenote(): boolean { - return this.$i && (this.$i.id === this.note.userId); - }, - - reactionsCount(): number { - return this.appearNote.reactions - ? sum(Object.values(this.appearNote.reactions)) - : 0; - }, - - urls(): string[] { - if (this.appearNote.text) { - return extractUrlFromMfm(mfm.parse(this.appearNote.text)); - } else { - return null; - } - }, - - showTicker() { - if (this.$store.state.instanceTicker === 'always') return true; - if (this.$store.state.instanceTicker === 'remote' && this.appearNote.user.instance) return true; - return false; - } - }, - - async created() { - if (this.$i) { - this.connection = stream; - } - - this.muted = await checkWordMute(this.appearNote, this.$i, this.$store.state.mutedWords); - - // plugin - if (noteViewInterruptors.length > 0) { - let result = this.note; - for (const interruptor of noteViewInterruptors) { - result = await interruptor.handler(JSON.parse(JSON.stringify(result))); - } - this.$emit('update:note', Object.freeze(result)); - } - - os.api('notes/children', { - noteId: this.appearNote.id, - limit: 30 - }).then(replies => { - this.replies = replies; - }); - - if (this.appearNote.replyId) { - os.api('notes/conversation', { - noteId: this.appearNote.replyId - }).then(conversation => { - this.conversation = conversation.reverse(); - }); - } - }, - - mounted() { - this.capture(true); - - if (this.$i) { - this.connection.on('_connected_', this.onStreamConnected); - } - }, - - beforeUnmount() { - this.decapture(true); - - if (this.$i) { - this.connection.off('_connected_', this.onStreamConnected); - } - }, - - methods: { - updateAppearNote(v) { - this.$emit('update:note', Object.freeze(this.isRenote ? { - ...this.note, - renote: { - ...this.note.renote, - ...v - } - } : { - ...this.note, - ...v - })); - }, - - readPromo() { - os.api('promo/read', { - noteId: this.appearNote.id - }); - this.isDeleted = true; - }, - - capture(withHandler = false) { - if (this.$i) { - // TODO: このノートがストリーミング経由で流れてきた場合のみ sr する - this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id }); - if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); - } - }, - - decapture(withHandler = false) { - if (this.$i) { - this.connection.send('un', { - id: this.appearNote.id - }); - if (withHandler) this.connection.off('noteUpdated', this.onStreamNoteUpdated); - } - }, - - onStreamConnected() { - this.capture(); - }, - - onStreamNoteUpdated(data) { - const { type, id, body } = data; - - if (id !== this.appearNote.id) return; - - switch (type) { - case 'reacted': { - const reaction = body.reaction; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - if (body.emoji) { - const emojis = this.appearNote.emojis || []; - if (!emojis.includes(body.emoji)) { - n.emojis = [...emojis, body.emoji]; - } - } - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Increment the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: currentCount + 1 - }; - - if (body.userId === this.$i.id) { - n.myReaction = reaction; - } - - this.updateAppearNote(n); - break; - } - - case 'unreacted': { - const reaction = body.reaction; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Decrement the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: Math.max(0, currentCount - 1) - }; - - if (body.userId === this.$i.id) { - n.myReaction = null; - } - - this.updateAppearNote(n); - break; - } - - case 'pollVoted': { - const choice = body.choice; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - const choices = [...this.appearNote.poll.choices]; - choices[choice] = { - ...choices[choice], - votes: choices[choice].votes + 1, - ...(body.userId === this.$i.id ? { - isVoted: true - } : {}) - }; - - n.poll = { - ...this.appearNote.poll, - choices: choices - }; - - this.updateAppearNote(n); - break; - } - - case 'deleted': { - this.isDeleted = true; - break; - } - } - }, - - reply(viaKeyboard = false) { - pleaseLogin(); - os.post({ - reply: this.appearNote, - animation: !viaKeyboard, - }, () => { - this.focus(); - }); - }, - - renoteDirectly() { - os.apiWithDialog('notes/create', { - renoteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.renoted, - }); - }, (e: Error) => { - if (e.id === 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4') { - os.alert({ - type: 'error', - text: this.$ts.cantRenote, - }); - } else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') { - os.alert({ - type: 'error', - text: this.$ts.cantReRenote, - }); - } - }); - }, - - react(viaKeyboard = false) { - pleaseLogin(); - this.blur(); - reactionPicker.show(this.$refs.reactButton, reaction => { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, () => { - this.focus(); - }); - }, - - reactDirectly(reaction) { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, - - undoReact(note) { - const oldReaction = note.myReaction; - if (!oldReaction) return; - os.api('notes/reactions/delete', { - noteId: note.id - }); - }, - - favorite() { - pleaseLogin(); - os.apiWithDialog('notes/favorites/create', { - noteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.favorited, - }); - }, (e: Error) => { - if (e.id === 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6') { - os.alert({ - type: 'error', - text: this.$ts.alreadyFavorited, - }); - } else if (e.id === '6dd26674-e060-4816-909a-45ba3f4da458') { - os.alert({ - type: 'error', - text: this.$ts.cantFavorite, - }); - } - }); - }, - - del() { - os.confirm({ - type: 'warning', - text: this.$ts.noteDeleteConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - }); - }, - - delEdit() { - os.confirm({ - type: 'warning', - text: this.$ts.deleteAndEditConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - - os.post({ initialNote: this.appearNote, renote: this.appearNote.renote, reply: this.appearNote.reply, channel: this.appearNote.channel }); - }); - }, - - toggleFavorite(favorite: boolean) { - os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { - noteId: this.appearNote.id - }); - }, - - toggleWatch(watch: boolean) { - os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { - noteId: this.appearNote.id - }); - }, - - toggleThreadMute(mute: boolean) { - os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { - noteId: this.appearNote.id - }); - }, - - getMenu() { - let menu; - if (this.$i) { - const statePromise = os.api('notes/state', { - noteId: this.appearNote.id - }); - - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined, - { - icon: 'fas fa-share-alt', - text: this.$ts.share, - action: this.share - }, - this.$instance.translatorAvailable ? { - icon: 'fas fa-language', - text: this.$ts.translate, - action: this.translate - } : undefined, - null, - statePromise.then(state => state.isFavorited ? { - icon: 'fas fa-star', - text: this.$ts.unfavorite, - action: () => this.toggleFavorite(false) - } : { - icon: 'fas fa-star', - text: this.$ts.favorite, - action: () => this.toggleFavorite(true) - }), - { - icon: 'fas fa-paperclip', - text: this.$ts.clip, - action: () => this.clip() - }, - (this.appearNote.userId != this.$i.id) ? statePromise.then(state => state.isWatching ? { - icon: 'fas fa-eye-slash', - text: this.$ts.unwatch, - action: () => this.toggleWatch(false) - } : { - icon: 'fas fa-eye', - text: this.$ts.watch, - action: () => this.toggleWatch(true) - }) : undefined, - statePromise.then(state => state.isMutedThread ? { - icon: 'fas fa-comment-slash', - text: this.$ts.unmuteThread, - action: () => this.toggleThreadMute(false) - } : { - icon: 'fas fa-comment-slash', - text: this.$ts.muteThread, - action: () => this.toggleThreadMute(true) - }), - this.appearNote.userId == this.$i.id ? (this.$i.pinnedNoteIds || []).includes(this.appearNote.id) ? { - icon: 'fas fa-thumbtack', - text: this.$ts.unpin, - action: () => this.togglePin(false) - } : { - icon: 'fas fa-thumbtack', - text: this.$ts.pin, - action: () => this.togglePin(true) - } : undefined, - /*...(this.$i.isModerator || this.$i.isAdmin ? [ - null, - { - icon: 'fas fa-bullhorn', - text: this.$ts.promote, - action: this.promote - }] - : [] - ),*/ - ...(this.appearNote.userId != this.$i.id ? [ - null, - { - icon: 'fas fa-exclamation-circle', - text: this.$ts.reportAbuse, - action: () => { - const u = `${url}/notes/${this.appearNote.id}`; - os.popup(import('@/components/abuse-report-window.vue'), { - user: this.appearNote.user, - initialComment: `Note: ${u}\n-----\n` - }, {}, 'closed'); - } - }] - : [] - ), - ...(this.appearNote.userId == this.$i.id || this.$i.isModerator || this.$i.isAdmin ? [ - null, - this.appearNote.userId == this.$i.id ? { - icon: 'fas fa-edit', - text: this.$ts.deleteAndEdit, - action: this.delEdit - } : undefined, - { - icon: 'fas fa-trash-alt', - text: this.$ts.delete, - danger: true, - action: this.del - }] - : [] - )] - .filter(x => x !== undefined); - } else { - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined] - .filter(x => x !== undefined); - } - - if (noteActions.length > 0) { - menu = menu.concat([null, ...noteActions.map(action => ({ - icon: 'fas fa-plug', - text: action.title, - action: () => { - action.handler(this.appearNote); - } - }))]); - } - - return menu; - }, - - onContextmenu(e) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(e.target)) return; - if (window.getSelection().toString() !== '') return; - - if (this.$store.state.useReactionPickerForContextMenu) { - e.preventDefault(); - this.react(); - } else { - os.contextMenu(this.getMenu(), e).then(this.focus); - } - }, - - menu(viaKeyboard = false) { - os.popupMenu(this.getMenu(), this.$refs.menuButton, { - viaKeyboard - }).then(this.focus); - }, - - showRenoteMenu(viaKeyboard = false) { - if (!this.isMyRenote) return; - os.popupMenu([{ - text: this.$ts.unrenote, - icon: 'fas fa-trash-alt', - danger: true, - action: () => { - os.api('notes/delete', { - noteId: this.note.id - }); - this.isDeleted = true; - } - }], this.$refs.renoteTime, { - viaKeyboard: viaKeyboard - }); - }, - - toggleShowContent() { - this.showContent = !this.showContent; - }, - - copyContent() { - copyToClipboard(this.appearNote.text); - os.success(); - }, - - copyLink() { - copyToClipboard(`${url}/notes/${this.appearNote.id}`); - os.success(); - }, - - togglePin(pin: boolean) { - os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { - noteId: this.appearNote.id - }, undefined, null, e => { - if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { - os.alert({ - type: 'error', - text: this.$ts.pinLimitExceeded - }); - } - }); - }, - - async clip() { - const clips = await os.api('clips/list'); - os.popupMenu([{ - icon: 'fas fa-plus', - text: this.$ts.createNew, - action: async () => { - const { canceled, result } = await os.form(this.$ts.createNewClip, { - name: { - type: 'string', - label: this.$ts.name - }, - description: { - type: 'string', - required: false, - multiline: true, - label: this.$ts.description - }, - isPublic: { - type: 'boolean', - label: this.$ts.public, - default: false - } - }); - if (canceled) return; - - const clip = await os.apiWithDialog('clips/create', result); - - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }, null, ...clips.map(clip => ({ - text: clip.name, - action: () => { - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }))], this.$refs.menuButton, { - }).then(this.focus); - }, - - async promote() { - const { canceled, result: days } = await os.inputNumber({ - title: this.$ts.numberOfDays, - }); - - if (canceled) return; - - os.apiWithDialog('admin/promo/create', { - noteId: this.appearNote.id, - expiresAt: Date.now() + (86400000 * days) - }); - }, - - share() { - navigator.share({ - title: this.$t('noteOf', { user: this.appearNote.user.name }), - text: this.appearNote.text, - url: `${url}/notes/${this.appearNote.id}` - }); - }, - - async translate() { - if (this.translation != null) return; - this.translating = true; - const res = await os.api('notes/translate', { - noteId: this.appearNote.id, - targetLang: localStorage.getItem('lang') || navigator.language, - }); - this.translating = false; - this.translation = res; - }, - - focus() { - this.$el.focus(); - }, - - blur() { - this.$el.blur(); - }, - - focusBefore() { - focusPrev(this.$el); - }, - - focusAfter() { - focusNext(this.$el); - }, - - userPage - } +import { $i } from '@/account'; +import { i18n } from '@/i18n'; +import { getNoteMenu } from '@/scripts/get-note-menu'; +import { useNoteCapture } from '@/scripts/use-note-capture'; + +const props = defineProps<{ + note: misskey.entities.Note; + pinned?: boolean; +}>(); + +const inChannel = inject('inChannel', null); + +const isRenote = ( + props.note.renote != null && + props.note.text == null && + props.note.fileIds.length === 0 && + props.note.poll == null +); + +const el = ref<HTMLElement>(); +const menuButton = ref<HTMLElement>(); +const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); +const renoteTime = ref<HTMLElement>(); +const reactButton = ref<HTMLElement>(); +let appearNote = $ref(isRenote ? props.note.renote as misskey.entities.Note : props.note); +const isMyRenote = $i && ($i.id === props.note.userId); +const showContent = ref(false); +const isDeleted = ref(false); +const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords)); +const translation = ref(null); +const translating = ref(false); +const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null; +const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance); +const conversation = ref<misskey.entities.Note[]>([]); +const replies = ref<misskey.entities.Note[]>([]); + +const keymap = { + 'r': () => reply(true), + 'e|a|plus': () => react(true), + 'q': () => renoteButton.value.renote(true), + 'esc': blur, + 'm|o': () => menu(true), + 's': () => showContent.value != showContent.value, +}; + +useNoteCapture({ + appearNote: $$(appearNote), + rootEl: el, }); + +function reply(viaKeyboard = false): void { + pleaseLogin(); + os.post({ + reply: appearNote, + animation: !viaKeyboard, + }, () => { + focus(); + }); +} + +function react(viaKeyboard = false): void { + pleaseLogin(); + blur(); + reactionPicker.show(reactButton.value, reaction => { + os.api('notes/reactions/create', { + noteId: appearNote.id, + reaction: reaction + }); + }, () => { + focus(); + }); +} + +function undoReact(note): void { + const oldReaction = note.myReaction; + if (!oldReaction) return; + os.api('notes/reactions/delete', { + noteId: note.id + }); +} + +function onContextmenu(e): void { + const isLink = (el: HTMLElement) => { + if (el.tagName === 'A') return true; + if (el.parentElement) { + return isLink(el.parentElement); + } + }; + if (isLink(e.target)) return; + if (window.getSelection().toString() !== '') return; + + if (defaultStore.state.useReactionPickerForContextMenu) { + e.preventDefault(); + react(); + } else { + os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), e).then(focus); + } +} + +function menu(viaKeyboard = false): void { + os.popupMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), menuButton.value, { + viaKeyboard + }).then(focus); +} + +function showRenoteMenu(viaKeyboard = false): void { + if (!isMyRenote) return; + os.popupMenu([{ + text: i18n.locale.unrenote, + icon: 'fas fa-trash-alt', + danger: true, + action: () => { + os.api('notes/delete', { + noteId: props.note.id + }); + isDeleted.value = true; + } + }], renoteTime.value, { + viaKeyboard: viaKeyboard + }); +} + +function focus() { + el.value.focus(); +} + +function blur() { + el.value.blur(); +} + +os.api('notes/children', { + noteId: appearNote.id, + limit: 30 +}).then(res => { + replies.value = res; +}); + +if (appearNote.replyId) { + os.api('notes/conversation', { + noteId: appearNote.replyId + }).then(res => { + conversation.value = res.reverse(); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/note-header.vue b/packages/client/src/components/note-header.vue index 26e725c6b8..56a3a37e75 100644 --- a/packages/client/src/components/note-header.vue +++ b/packages/client/src/components/note-header.vue @@ -19,30 +19,16 @@ </header> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import { notePage } from '@/filters/note'; import { userPage } from '@/filters/user'; -import * as os from '@/os'; -export default defineComponent({ - props: { - note: { - type: Object, - required: true - }, - }, - - data() { - return { - }; - }, - - methods: { - notePage, - userPage - } -}); +defineProps<{ + note: misskey.entities.Note; + pinned?: boolean; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/note-simple.vue b/packages/client/src/components/note-simple.vue index 135f06602d..d8236a2096 100644 --- a/packages/client/src/components/note-simple.vue +++ b/packages/client/src/components/note-simple.vue @@ -9,7 +9,7 @@ <XCwButton v-model="showContent" :note="note"/> </p> <div v-show="note.cw == null || showContent" class="content"> - <XSubNote-content class="text" :note="note"/> + <MkNoteSubNoteContent class="text" :note="note"/> </div> </div> </div> @@ -19,14 +19,14 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XNoteHeader from './note-header.vue'; -import XSubNoteContent from './sub-note-content.vue'; +import MkNoteSubNoteContent from './sub-note-content.vue'; import XCwButton from './cw-button.vue'; import * as os from '@/os'; export default defineComponent({ components: { XNoteHeader, - XSubNoteContent, + MkNoteSubNoteContent, XCwButton, }, diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue index 3cf924928a..b309afe051 100644 --- a/packages/client/src/components/note.vue +++ b/packages/client/src/components/note.vue @@ -2,20 +2,21 @@ <div v-if="!muted" v-show="!isDeleted" + ref="el" v-hotkey="keymap" v-size="{ max: [500, 450, 350, 300] }" class="tkcbzcuz" :tabindex="!isDeleted ? '-1' : null" :class="{ renote: isRenote }" > - <XSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> - <div v-if="pinned" class="info"><i class="fas fa-thumbtack"></i> {{ $ts.pinnedNote }}</div> - <div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ $ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ $ts.hideThisNote }} <i class="fas fa-times"></i></button></div> - <div v-if="appearNote._featuredId_" class="info"><i class="fas fa-bolt"></i> {{ $ts.featured }}</div> + <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> + <div v-if="pinned" class="info"><i class="fas fa-thumbtack"></i> {{ i18n.locale.pinnedNote }}</div> + <div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ i18n.locale.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.locale.hideThisNote }} <i class="fas fa-times"></i></button></div> + <div v-if="appearNote._featuredId_" class="info"><i class="fas fa-bolt"></i> {{ i18n.locale.featured }}</div> <div v-if="isRenote" class="renote"> <MkAvatar class="avatar" :user="note.user"/> <i class="fas fa-retweet"></i> - <I18n :src="$ts.renotedBy" tag="span"> + <I18n :src="i18n.locale.renotedBy" tag="span"> <template #user> <MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)"> <MkUserName :user="note.user"/> @@ -47,7 +48,7 @@ </p> <div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed }"> <div class="text"> - <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $ts.private }})</span> + <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.locale.private }})</span> <MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA> <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> <a v-if="appearNote.renote != null" class="rp">RN:</a> @@ -66,7 +67,7 @@ <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/> <div v-if="appearNote.renote" class="renote"><XNoteSimple :note="appearNote.renote"/></div> <button v-if="collapsed" class="fade _button" @click="collapsed = false"> - <span>{{ $ts.showMore }}</span> + <span>{{ i18n.locale.showMore }}</span> </button> </div> <MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="fas fa-satellite-dish"></i> {{ appearNote.channel.name }}</MkA> @@ -93,7 +94,7 @@ </article> </div> <div v-else class="muted" @click="muted = false"> - <I18n :src="$ts.userSaysSomething" tag="small"> + <I18n :src="i18n.locale.userSaysSomething" tag="small"> <template #name> <MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)"> <MkUserName :user="appearNote.user"/> @@ -103,11 +104,11 @@ </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { computed, inject, onMounted, onUnmounted, reactive, ref } from 'vue'; import * as mfm from 'mfm-js'; -import { sum } from '@/scripts/array'; -import XSub from './note.sub.vue'; +import * as misskey from 'misskey-js'; +import MkNoteSub from './MkNoteSub.vue'; import XNoteHeader from './note-header.vue'; import XNoteSimple from './note-simple.vue'; import XReactionsViewer from './reactions-viewer.vue'; @@ -115,745 +116,164 @@ import XMediaList from './media-list.vue'; import XCwButton from './cw-button.vue'; import XPoll from './poll.vue'; import XRenoteButton from './renote-button.vue'; +import MkUrlPreview from '@/components/url-preview.vue'; +import MkInstanceTicker from '@/components/instance-ticker.vue'; 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'; import { userPage } from '@/filters/user'; import * as os from '@/os'; -import { stream } from '@/stream'; -import { noteActions, noteViewInterruptors } from '@/store'; +import { defaultStore, noteViewInterruptors } from '@/store'; import { reactionPicker } from '@/scripts/reaction-picker'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; - -export default defineComponent({ - components: { - XSub, - XNoteHeader, - XNoteSimple, - XReactionsViewer, - XMediaList, - XCwButton, - XPoll, - XRenoteButton, - MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')), - MkInstanceTicker: defineAsyncComponent(() => import('@/components/instance-ticker.vue')), - }, - - inject: { - inChannel: { - default: null - }, - }, - - props: { - note: { - type: Object, - required: true - }, - pinned: { - type: Boolean, - required: false, - default: false - }, - }, - - emits: ['update:note'], - - data() { - return { - connection: null, - replies: [], - showContent: false, - collapsed: false, - isDeleted: false, - muted: false, - translation: null, - translating: false, - }; - }, - - computed: { - rs() { - return this.$store.state.reactions; - }, - keymap(): any { - return { - 'r': () => this.reply(true), - 'e|a|plus': () => this.react(true), - 'q': () => this.$refs.renoteButton.renote(true), - 'f|b': this.favorite, - 'delete|ctrl+d': this.del, - 'ctrl+q': this.renoteDirectly, - 'up|k|shift+tab': this.focusBefore, - 'down|j|tab': this.focusAfter, - 'esc': this.blur, - 'm|o': () => this.menu(true), - 's': this.toggleShowContent, - '1': () => this.reactDirectly(this.rs[0]), - '2': () => this.reactDirectly(this.rs[1]), - '3': () => this.reactDirectly(this.rs[2]), - '4': () => this.reactDirectly(this.rs[3]), - '5': () => this.reactDirectly(this.rs[4]), - '6': () => this.reactDirectly(this.rs[5]), - '7': () => this.reactDirectly(this.rs[6]), - '8': () => this.reactDirectly(this.rs[7]), - '9': () => this.reactDirectly(this.rs[8]), - '0': () => this.reactDirectly(this.rs[9]), - }; - }, - - isRenote(): boolean { - return (this.note.renote && - this.note.text == null && - this.note.fileIds.length == 0 && - this.note.poll == null); - }, - - appearNote(): any { - return this.isRenote ? this.note.renote : this.note; - }, - - isMyNote(): boolean { - return this.$i && (this.$i.id === this.appearNote.userId); - }, - - isMyRenote(): boolean { - return this.$i && (this.$i.id === this.note.userId); - }, - - reactionsCount(): number { - return this.appearNote.reactions - ? sum(Object.values(this.appearNote.reactions)) - : 0; - }, - - urls(): string[] { - if (this.appearNote.text) { - return extractUrlFromMfm(mfm.parse(this.appearNote.text)); - } else { - return null; - } - }, - - showTicker() { - if (this.$store.state.instanceTicker === 'always') return true; - if (this.$store.state.instanceTicker === 'remote' && this.appearNote.user.instance) return true; - return false; - } - }, - - async created() { - if (this.$i) { - this.connection = stream; - } - - this.collapsed = this.appearNote.cw == null && this.appearNote.text && ( - (this.appearNote.text.split('\n').length > 9) || - (this.appearNote.text.length > 500) - ); - this.muted = await checkWordMute(this.appearNote, this.$i, this.$store.state.mutedWords); - - // plugin - if (noteViewInterruptors.length > 0) { - let result = this.note; - for (const interruptor of noteViewInterruptors) { - result = await interruptor.handler(JSON.parse(JSON.stringify(result))); - } - this.$emit('update:note', Object.freeze(result)); - } - }, - - mounted() { - this.capture(true); - - if (this.$i) { - this.connection.on('_connected_', this.onStreamConnected); - } - }, - - beforeUnmount() { - this.decapture(true); - - if (this.$i) { - this.connection.off('_connected_', this.onStreamConnected); - } - }, - - methods: { - updateAppearNote(v) { - this.$emit('update:note', Object.freeze(this.isRenote ? { - ...this.note, - renote: { - ...this.note.renote, - ...v - } - } : { - ...this.note, - ...v - })); - }, - - readPromo() { - os.api('promo/read', { - noteId: this.appearNote.id - }); - this.isDeleted = true; - }, - - capture(withHandler = false) { - if (this.$i) { - // TODO: このノートがストリーミング経由で流れてきた場合のみ sr する - this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id }); - if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); - } - }, - - decapture(withHandler = false) { - if (this.$i) { - this.connection.send('un', { - id: this.appearNote.id - }); - if (withHandler) this.connection.off('noteUpdated', this.onStreamNoteUpdated); - } - }, - - onStreamConnected() { - this.capture(); - }, - - onStreamNoteUpdated(data) { - const { type, id, body } = data; - - if (id !== this.appearNote.id) return; - - switch (type) { - case 'reacted': { - const reaction = body.reaction; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - if (body.emoji) { - const emojis = this.appearNote.emojis || []; - if (!emojis.includes(body.emoji)) { - n.emojis = [...emojis, body.emoji]; - } - } - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Increment the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: currentCount + 1 - }; - - if (body.userId === this.$i.id) { - n.myReaction = reaction; - } - - this.updateAppearNote(n); - break; - } - - case 'unreacted': { - const reaction = body.reaction; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Decrement the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: Math.max(0, currentCount - 1) - }; - - if (body.userId === this.$i.id) { - n.myReaction = null; - } - - this.updateAppearNote(n); - break; - } - - case 'pollVoted': { - const choice = body.choice; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - const choices = [...this.appearNote.poll.choices]; - choices[choice] = { - ...choices[choice], - votes: choices[choice].votes + 1, - ...(body.userId === this.$i.id ? { - isVoted: true - } : {}) - }; - - n.poll = { - ...this.appearNote.poll, - choices: choices - }; - - this.updateAppearNote(n); - break; - } - - case 'deleted': { - this.isDeleted = true; - break; - } - } - }, - - reply(viaKeyboard = false) { - pleaseLogin(); - os.post({ - reply: this.appearNote, - animation: !viaKeyboard, - }, () => { - this.focus(); - }); - }, - - renoteDirectly() { - os.apiWithDialog('notes/create', { - renoteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.renoted, - }); - }, (e: Error) => { - if (e.id === 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4') { - os.alert({ - type: 'error', - text: this.$ts.cantRenote, - }); - } else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') { - os.alert({ - type: 'error', - text: this.$ts.cantReRenote, - }); - } - }); - }, - - react(viaKeyboard = false) { - pleaseLogin(); - this.blur(); - reactionPicker.show(this.$refs.reactButton, reaction => { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, () => { - this.focus(); - }); - }, - - reactDirectly(reaction) { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, - - undoReact(note) { - const oldReaction = note.myReaction; - if (!oldReaction) return; - os.api('notes/reactions/delete', { - noteId: note.id - }); - }, - - favorite() { - pleaseLogin(); - os.apiWithDialog('notes/favorites/create', { - noteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.favorited, - }); - }, (e: Error) => { - if (e.id === 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6') { - os.alert({ - type: 'error', - text: this.$ts.alreadyFavorited, - }); - } else if (e.id === '6dd26674-e060-4816-909a-45ba3f4da458') { - os.alert({ - type: 'error', - text: this.$ts.cantFavorite, - }); - } - }); - }, - - del() { - os.confirm({ - type: 'warning', - text: this.$ts.noteDeleteConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - }); - }, - - delEdit() { - os.confirm({ - type: 'warning', - text: this.$ts.deleteAndEditConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - - os.post({ initialNote: this.appearNote, renote: this.appearNote.renote, reply: this.appearNote.reply, channel: this.appearNote.channel }); - }); - }, - - toggleFavorite(favorite: boolean) { - os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { - noteId: this.appearNote.id - }); - }, - - toggleWatch(watch: boolean) { - os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { - noteId: this.appearNote.id - }); - }, - - toggleThreadMute(mute: boolean) { - os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { - noteId: this.appearNote.id - }); - }, - - getMenu() { - let menu; - if (this.$i) { - const statePromise = os.api('notes/state', { - noteId: this.appearNote.id - }); - - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined, - { - icon: 'fas fa-share-alt', - text: this.$ts.share, - action: this.share - }, - this.$instance.translatorAvailable ? { - icon: 'fas fa-language', - text: this.$ts.translate, - action: this.translate - } : undefined, - null, - statePromise.then(state => state.isFavorited ? { - icon: 'fas fa-star', - text: this.$ts.unfavorite, - action: () => this.toggleFavorite(false) - } : { - icon: 'fas fa-star', - text: this.$ts.favorite, - action: () => this.toggleFavorite(true) - }), - { - icon: 'fas fa-paperclip', - text: this.$ts.clip, - action: () => this.clip() - }, - (this.appearNote.userId != this.$i.id) ? statePromise.then(state => state.isWatching ? { - icon: 'fas fa-eye-slash', - text: this.$ts.unwatch, - action: () => this.toggleWatch(false) - } : { - icon: 'fas fa-eye', - text: this.$ts.watch, - action: () => this.toggleWatch(true) - }) : undefined, - statePromise.then(state => state.isMutedThread ? { - icon: 'fas fa-comment-slash', - text: this.$ts.unmuteThread, - action: () => this.toggleThreadMute(false) - } : { - icon: 'fas fa-comment-slash', - text: this.$ts.muteThread, - action: () => this.toggleThreadMute(true) - }), - this.appearNote.userId == this.$i.id ? (this.$i.pinnedNoteIds || []).includes(this.appearNote.id) ? { - icon: 'fas fa-thumbtack', - text: this.$ts.unpin, - action: () => this.togglePin(false) - } : { - icon: 'fas fa-thumbtack', - text: this.$ts.pin, - action: () => this.togglePin(true) - } : undefined, - /* - ...(this.$i.isModerator || this.$i.isAdmin ? [ - null, - { - icon: 'fas fa-bullhorn', - text: this.$ts.promote, - action: this.promote - }] - : [] - ),*/ - ...(this.appearNote.userId != this.$i.id ? [ - null, - { - icon: 'fas fa-exclamation-circle', - text: this.$ts.reportAbuse, - action: () => { - const u = `${url}/notes/${this.appearNote.id}`; - os.popup(import('@/components/abuse-report-window.vue'), { - user: this.appearNote.user, - initialComment: `Note: ${u}\n-----\n` - }, {}, 'closed'); - } - }] - : [] - ), - ...(this.appearNote.userId == this.$i.id || this.$i.isModerator || this.$i.isAdmin ? [ - null, - this.appearNote.userId == this.$i.id ? { - icon: 'fas fa-edit', - text: this.$ts.deleteAndEdit, - action: this.delEdit - } : undefined, - { - icon: 'fas fa-trash-alt', - text: this.$ts.delete, - danger: true, - action: this.del - }] - : [] - )] - .filter(x => x !== undefined); - } else { - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined] - .filter(x => x !== undefined); - } - - if (noteActions.length > 0) { - menu = menu.concat([null, ...noteActions.map(action => ({ - icon: 'fas fa-plug', - text: action.title, - action: () => { - action.handler(this.appearNote); - } - }))]); - } - - return menu; - }, - - onContextmenu(e) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(e.target)) return; - if (window.getSelection().toString() !== '') return; - - if (this.$store.state.useReactionPickerForContextMenu) { - e.preventDefault(); - this.react(); - } else { - os.contextMenu(this.getMenu(), e).then(this.focus); - } - }, - - menu(viaKeyboard = false) { - os.popupMenu(this.getMenu(), this.$refs.menuButton, { - viaKeyboard - }).then(this.focus); - }, - - showRenoteMenu(viaKeyboard = false) { - if (!this.isMyRenote) return; - os.popupMenu([{ - text: this.$ts.unrenote, - icon: 'fas fa-trash-alt', - danger: true, - action: () => { - os.api('notes/delete', { - noteId: this.note.id - }); - this.isDeleted = true; - } - }], this.$refs.renoteTime, { - viaKeyboard: viaKeyboard - }); - }, - - toggleShowContent() { - this.showContent = !this.showContent; - }, - - copyContent() { - copyToClipboard(this.appearNote.text); - os.success(); - }, - - copyLink() { - copyToClipboard(`${url}/notes/${this.appearNote.id}`); - os.success(); - }, - - togglePin(pin: boolean) { - os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { - noteId: this.appearNote.id - }, undefined, null, e => { - if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { - os.alert({ - type: 'error', - text: this.$ts.pinLimitExceeded - }); - } - }); - }, - - async clip() { - const clips = await os.api('clips/list'); - os.popupMenu([{ - icon: 'fas fa-plus', - text: this.$ts.createNew, - action: async () => { - const { canceled, result } = await os.form(this.$ts.createNewClip, { - name: { - type: 'string', - label: this.$ts.name - }, - description: { - type: 'string', - required: false, - multiline: true, - label: this.$ts.description - }, - isPublic: { - type: 'boolean', - label: this.$ts.public, - default: false - } - }); - if (canceled) return; - - const clip = await os.apiWithDialog('clips/create', result); - - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }, null, ...clips.map(clip => ({ - text: clip.name, - action: () => { - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }))], this.$refs.menuButton, { - }).then(this.focus); - }, - - async promote() { - const { canceled, result: days } = await os.inputNumber({ - title: this.$ts.numberOfDays, - }); - - if (canceled) return; - - os.apiWithDialog('admin/promo/create', { - noteId: this.appearNote.id, - expiresAt: Date.now() + (86400000 * days) - }); - }, - - share() { - navigator.share({ - title: this.$t('noteOf', { user: this.appearNote.user.name }), - text: this.appearNote.text, - url: `${url}/notes/${this.appearNote.id}` - }); - }, - - async translate() { - if (this.translation != null) return; - this.translating = true; - const res = await os.api('notes/translate', { - noteId: this.appearNote.id, - targetLang: localStorage.getItem('lang') || navigator.language, - }); - this.translating = false; - this.translation = res; - }, - - focus() { - this.$el.focus(); - }, - - blur() { - this.$el.blur(); - }, - - focusBefore() { - focusPrev(this.$el); - }, - - focusAfter() { - focusNext(this.$el); - }, - - userPage - } +import { $i } from '@/account'; +import { i18n } from '@/i18n'; +import { getNoteMenu } from '@/scripts/get-note-menu'; +import { useNoteCapture } from '@/scripts/use-note-capture'; + +const props = defineProps<{ + note: misskey.entities.Note; + pinned?: boolean; +}>(); + +const inChannel = inject('inChannel', null); + +const isRenote = ( + props.note.renote != null && + props.note.text == null && + props.note.fileIds.length === 0 && + props.note.poll == null +); + +const el = ref<HTMLElement>(); +const menuButton = ref<HTMLElement>(); +const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); +const renoteTime = ref<HTMLElement>(); +const reactButton = ref<HTMLElement>(); +let appearNote = $ref(isRenote ? props.note.renote as misskey.entities.Note : props.note); +const isMyRenote = $i && ($i.id === props.note.userId); +const showContent = ref(false); +const collapsed = ref(appearNote.cw == null && appearNote.text != null && ( + (appearNote.text.split('\n').length > 9) || + (appearNote.text.length > 500) +)); +const isDeleted = ref(false); +const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords)); +const translation = ref(null); +const translating = ref(false); +const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null; +const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance); + +const keymap = { + 'r': () => reply(true), + 'e|a|plus': () => react(true), + 'q': () => renoteButton.value.renote(true), + 'up|k|shift+tab': focusBefore, + 'down|j|tab': focusAfter, + 'esc': blur, + 'm|o': () => menu(true), + 's': () => showContent.value != showContent.value, +}; + +useNoteCapture({ + appearNote: $$(appearNote), + rootEl: el, }); + +function reply(viaKeyboard = false): void { + pleaseLogin(); + os.post({ + reply: appearNote, + animation: !viaKeyboard, + }, () => { + focus(); + }); +} + +function react(viaKeyboard = false): void { + pleaseLogin(); + blur(); + reactionPicker.show(reactButton.value, reaction => { + os.api('notes/reactions/create', { + noteId: appearNote.id, + reaction: reaction + }); + }, () => { + focus(); + }); +} + +function undoReact(note): void { + const oldReaction = note.myReaction; + if (!oldReaction) return; + os.api('notes/reactions/delete', { + noteId: note.id + }); +} + +function onContextmenu(e): void { + const isLink = (el: HTMLElement) => { + if (el.tagName === 'A') return true; + if (el.parentElement) { + return isLink(el.parentElement); + } + }; + if (isLink(e.target)) return; + if (window.getSelection().toString() !== '') return; + + if (defaultStore.state.useReactionPickerForContextMenu) { + e.preventDefault(); + react(); + } else { + os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), e).then(focus); + } +} + +function menu(viaKeyboard = false): void { + os.popupMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), menuButton.value, { + viaKeyboard + }).then(focus); +} + +function showRenoteMenu(viaKeyboard = false): void { + if (!isMyRenote) return; + os.popupMenu([{ + text: i18n.locale.unrenote, + icon: 'fas fa-trash-alt', + danger: true, + action: () => { + os.api('notes/delete', { + noteId: props.note.id + }); + isDeleted.value = true; + } + }], renoteTime.value, { + viaKeyboard: viaKeyboard + }); +} + +function focus() { + el.value.focus(); +} + +function blur() { + el.value.blur(); +} + +function focusBefore() { + focusPrev(el.value); +} + +function focusAfter() { + focusNext(el.value); +} + +function readPromo() { + os.api('promo/read', { + noteId: appearNote.id + }); + isDeleted.value = true; +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/notes.vue b/packages/client/src/components/notes.vue index d6107216e2..aec478ac95 100644 --- a/packages/client/src/components/notes.vue +++ b/packages/client/src/components/notes.vue @@ -10,7 +10,7 @@ <template #default="{ items: notes }"> <div class="giivymft" :class="{ noGap }"> <XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" :no-gap="noGap" :ad="true" class="notes"> - <XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note" @update:note="updated(note, $event)"/> + <XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note"/> </XList> </div> </template> @@ -31,10 +31,6 @@ const props = defineProps<{ const pagingComponent = ref<InstanceType<typeof MkPagination>>(); -const updated = (oldValue, newValue) => { - pagingComponent.value?.updateItem(oldValue.id, () => newValue); -}; - defineExpose({ prepend: (note) => { pagingComponent.value?.prepend(note); diff --git a/packages/client/src/components/notifications.vue b/packages/client/src/components/notifications.vue index 31511fb515..5a77b5487e 100644 --- a/packages/client/src/components/notifications.vue +++ b/packages/client/src/components/notifications.vue @@ -9,7 +9,7 @@ <template #default="{ items: notifications }"> <XList v-slot="{ item: notification }" class="elsfgstc" :items="notifications" :no-gap="true"> - <XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note" @update:note="noteUpdated(notification, $event)"/> + <XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note"/> <XNotification v-else :key="notification.id" :notification="notification" :with-time="true" :full="true" class="_panel notification"/> </XList> </template> @@ -62,13 +62,6 @@ const onNotification = (notification) => { } }; -const noteUpdated = (item, note) => { - pagingComponent.value?.updateItem(item.id, old => ({ - ...old, - note: note, - })); -}; - onMounted(() => { const connection = stream.useChannel('main'); connection.on('notification', onNotification); diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue index d4451e27cb..571ef71eab 100644 --- a/packages/client/src/components/ui/pagination.vue +++ b/packages/client/src/components/ui/pagination.vue @@ -90,7 +90,6 @@ const init = async (): Promise<void> => { }).then(res => { for (let i = 0; i < res.length; i++) { const item = res[i]; - markRaw(item); if (props.pagination.reversed) { if (i === res.length - 2) item._shouldInsertAd_ = true; } else { @@ -134,7 +133,6 @@ const fetchMore = async (): Promise<void> => { }).then(res => { for (let i = 0; i < res.length; i++) { const item = res[i]; - markRaw(item); if (props.pagination.reversed) { if (i === res.length - 9) item._shouldInsertAd_ = true; } else { @@ -169,9 +167,6 @@ const fetchMoreAhead = async (): Promise<void> => { sinceId: props.pagination.reversed ? items.value[0].id : items.value[items.value.length - 1].id, }), }).then(res => { - for (const item of res) { - markRaw(item); - } if (res.length > SECOND_FETCH_LIMIT) { res.pop(); items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); diff --git a/packages/client/src/pages/favorites.vue b/packages/client/src/pages/favorites.vue index f530da2a19..8965b30d60 100644 --- a/packages/client/src/pages/favorites.vue +++ b/packages/client/src/pages/favorites.vue @@ -10,7 +10,7 @@ <template #default="{ items }"> <XList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false"> - <XNote :key="item.id" :note="item.note" :class="$style.note" @update:note="noteUpdated(item, $event)"/> + <XNote :key="item.id" :note="item.note" :class="$style.note"/> </XList> </template> </MkPagination> @@ -32,13 +32,6 @@ const pagination = { const pagingComponent = ref<InstanceType<typeof MkPagination>>(); -const noteUpdated = (item, note) => { - pagingComponent.value?.updateItem(item.id, old => ({ - ...old, - note: note, - })); -}; - defineExpose({ [symbols.PAGE_INFO]: { title: i18n.locale.favorites, @@ -53,4 +46,4 @@ defineExpose({ background: var(--panel); border-radius: var(--radius); } -</style> \ No newline at end of file +</style> diff --git a/packages/client/src/pages/user/reactions.vue b/packages/client/src/pages/user/reactions.vue index 0381542b4d..d2c1f92ebb 100644 --- a/packages/client/src/pages/user/reactions.vue +++ b/packages/client/src/pages/user/reactions.vue @@ -7,7 +7,7 @@ <MkReactionIcon class="reaction" :reaction="item.type" :custom-emojis="item.note.emojis" :no-style="true"/> <MkTime :time="item.createdAt" class="createdAt"/> </div> - <MkNote :key="item.id" :note="item.note" @update:note="updated(note, $event)"/> + <MkNote :key="item.id" :note="item.note"/> </div> </MkPagination> </div> diff --git a/packages/client/src/scripts/check-word-mute.ts b/packages/client/src/scripts/check-word-mute.ts index 3b1fa75b1e..55637bb3b3 100644 --- a/packages/client/src/scripts/check-word-mute.ts +++ b/packages/client/src/scripts/check-word-mute.ts @@ -1,4 +1,4 @@ -export async function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: string[][]): Promise<boolean> { +export function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: string[][]): boolean { // 自分自身 if (me && (note.userId === me.id)) return false; diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts new file mode 100644 index 0000000000..61120d53ba --- /dev/null +++ b/packages/client/src/scripts/get-note-menu.ts @@ -0,0 +1,310 @@ +import { Ref } from 'vue'; +import * as misskey from 'misskey-js'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +import * as os from '@/os'; +import copyToClipboard from '@/scripts/copy-to-clipboard'; +import { url } from '@/config'; +import { noteActions } from '@/store'; +import { pleaseLogin } from './please-login'; + +export function getNoteMenu(props: { + note: misskey.entities.Note; + menuButton: Ref<HTMLElement>; + translation: Ref<any>; + translating: Ref<boolean>; +}) { + const isRenote = ( + props.note.renote != null && + props.note.text == null && + props.note.fileIds.length === 0 && + props.note.poll == null + ); + + let appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note; + + function del(): void { + os.confirm({ + type: 'warning', + text: i18n.locale.noteDeleteConfirm, + }).then(({ canceled }) => { + if (canceled) return; + + os.api('notes/delete', { + noteId: appearNote.id + }); + }); + } + + function delEdit(): void { + os.confirm({ + type: 'warning', + text: i18n.locale.deleteAndEditConfirm, + }).then(({ canceled }) => { + if (canceled) return; + + os.api('notes/delete', { + noteId: appearNote.id + }); + + os.post({ initialNote: appearNote, renote: appearNote.renote, reply: appearNote.reply, channel: appearNote.channel }); + }); + } + + function toggleFavorite(favorite: boolean): void { + os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { + noteId: appearNote.id + }); + } + + function toggleWatch(watch: boolean): void { + os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { + noteId: appearNote.id + }); + } + + function toggleThreadMute(mute: boolean): void { + os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { + noteId: appearNote.id + }); + } + + function copyContent(): void { + copyToClipboard(appearNote.text); + os.success(); + } + + function copyLink(): void { + copyToClipboard(`${url}/notes/${appearNote.id}`); + os.success(); + } + + function togglePin(pin: boolean): void { + os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { + noteId: appearNote.id + }, undefined, null, e => { + if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { + os.alert({ + type: 'error', + text: i18n.locale.pinLimitExceeded + }); + } + }); + } + + async function clip(): Promise<void> { + const clips = await os.api('clips/list'); + os.popupMenu([{ + icon: 'fas fa-plus', + text: i18n.locale.createNew, + action: async () => { + const { canceled, result } = await os.form(i18n.locale.createNewClip, { + name: { + type: 'string', + label: i18n.locale.name + }, + description: { + type: 'string', + required: false, + multiline: true, + label: i18n.locale.description + }, + isPublic: { + type: 'boolean', + label: i18n.locale.public, + default: false + } + }); + if (canceled) return; + + const clip = await os.apiWithDialog('clips/create', result); + + os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); + } + }, null, ...clips.map(clip => ({ + text: clip.name, + action: () => { + os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); + } + }))], props.menuButton.value, { + }).then(focus); + } + + async function promote(): Promise<void> { + const { canceled, result: days } = await os.inputNumber({ + title: i18n.locale.numberOfDays, + }); + + if (canceled) return; + + os.apiWithDialog('admin/promo/create', { + noteId: appearNote.id, + expiresAt: Date.now() + (86400000 * days), + }); + } + + function share(): void { + navigator.share({ + title: i18n.t('noteOf', { user: appearNote.user.name }), + text: appearNote.text, + url: `${url}/notes/${appearNote.id}`, + }); + } + + async function translate(): Promise<void> { + if (props.translation.value != null) return; + props.translating.value = true; + const res = await os.api('notes/translate', { + noteId: appearNote.id, + targetLang: localStorage.getItem('lang') || navigator.language, + }); + props.translating.value = false; + props.translation.value = res; + } + + let menu; + if ($i) { + const statePromise = os.api('notes/state', { + noteId: appearNote.id + }); + + menu = [{ + icon: 'fas fa-copy', + text: i18n.locale.copyContent, + action: copyContent + }, { + icon: 'fas fa-link', + text: i18n.locale.copyLink, + action: copyLink + }, (appearNote.url || appearNote.uri) ? { + icon: 'fas fa-external-link-square-alt', + text: i18n.locale.showOnRemote, + action: () => { + window.open(appearNote.url || appearNote.uri, '_blank'); + } + } : undefined, + { + icon: 'fas fa-share-alt', + text: i18n.locale.share, + action: share + }, + instance.translatorAvailable ? { + icon: 'fas fa-language', + text: i18n.locale.translate, + action: translate + } : undefined, + null, + statePromise.then(state => state.isFavorited ? { + icon: 'fas fa-star', + text: i18n.locale.unfavorite, + action: () => toggleFavorite(false) + } : { + icon: 'fas fa-star', + text: i18n.locale.favorite, + action: () => toggleFavorite(true) + }), + { + icon: 'fas fa-paperclip', + text: i18n.locale.clip, + action: () => clip() + }, + (appearNote.userId != $i.id) ? statePromise.then(state => state.isWatching ? { + icon: 'fas fa-eye-slash', + text: i18n.locale.unwatch, + action: () => toggleWatch(false) + } : { + icon: 'fas fa-eye', + text: i18n.locale.watch, + action: () => toggleWatch(true) + }) : undefined, + statePromise.then(state => state.isMutedThread ? { + icon: 'fas fa-comment-slash', + text: i18n.locale.unmuteThread, + action: () => toggleThreadMute(false) + } : { + icon: 'fas fa-comment-slash', + text: i18n.locale.muteThread, + action: () => toggleThreadMute(true) + }), + appearNote.userId == $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? { + icon: 'fas fa-thumbtack', + text: i18n.locale.unpin, + action: () => togglePin(false) + } : { + icon: 'fas fa-thumbtack', + text: i18n.locale.pin, + action: () => togglePin(true) + } : undefined, + /* + ...($i.isModerator || $i.isAdmin ? [ + null, + { + icon: 'fas fa-bullhorn', + text: i18n.locale.promote, + action: promote + }] + : [] + ),*/ + ...(appearNote.userId != $i.id ? [ + null, + { + icon: 'fas fa-exclamation-circle', + text: i18n.locale.reportAbuse, + action: () => { + const u = `${url}/notes/${appearNote.id}`; + os.popup(import('@/components/abuse-report-window.vue'), { + user: appearNote.user, + initialComment: `Note: ${u}\n-----\n` + }, {}, 'closed'); + } + }] + : [] + ), + ...(appearNote.userId == $i.id || $i.isModerator || $i.isAdmin ? [ + null, + appearNote.userId == $i.id ? { + icon: 'fas fa-edit', + text: i18n.locale.deleteAndEdit, + action: delEdit + } : undefined, + { + icon: 'fas fa-trash-alt', + text: i18n.locale.delete, + danger: true, + action: del + }] + : [] + )] + .filter(x => x !== undefined); + } else { + menu = [{ + icon: 'fas fa-copy', + text: i18n.locale.copyContent, + action: copyContent + }, { + icon: 'fas fa-link', + text: i18n.locale.copyLink, + action: copyLink + }, (appearNote.url || appearNote.uri) ? { + icon: 'fas fa-external-link-square-alt', + text: i18n.locale.showOnRemote, + action: () => { + window.open(appearNote.url || appearNote.uri, '_blank'); + } + } : undefined] + .filter(x => x !== undefined); + } + + if (noteActions.length > 0) { + menu = menu.concat([null, ...noteActions.map(action => ({ + icon: 'fas fa-plug', + text: action.title, + action: () => { + action.handler(appearNote); + } + }))]); + } + + return menu; +} diff --git a/packages/client/src/scripts/use-note-capture.ts b/packages/client/src/scripts/use-note-capture.ts new file mode 100644 index 0000000000..bb00e464e3 --- /dev/null +++ b/packages/client/src/scripts/use-note-capture.ts @@ -0,0 +1,123 @@ +import { onUnmounted, Ref } from 'vue'; +import * as misskey from 'misskey-js'; +import { stream } from '@/stream'; +import { $i } from '@/account'; + +export function useNoteCapture(props: { + rootEl: Ref<HTMLElement>; + appearNote: Ref<misskey.entities.Note>; +}) { + const appearNote = props.appearNote; + const connection = $i ? stream : null; + + function onStreamNoteUpdated(data): void { + const { type, id, body } = data; + + if (id !== appearNote.value.id) return; + + switch (type) { + case 'reacted': { + const reaction = body.reaction; + + const updated = JSON.parse(JSON.stringify(appearNote.value)); + + if (body.emoji) { + const emojis = appearNote.value.emojis || []; + if (!emojis.includes(body.emoji)) { + updated.emojis = [...emojis, body.emoji]; + } + } + + // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる + const currentCount = (appearNote.value.reactions || {})[reaction] || 0; + + updated.reactions[reaction] = currentCount + 1; + + if ($i && (body.userId === $i.id)) { + updated.myReaction = reaction; + } + + appearNote.value = updated; + break; + } + + case 'unreacted': { + const reaction = body.reaction; + + const updated = JSON.parse(JSON.stringify(appearNote.value)); + + // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる + const currentCount = (appearNote.value.reactions || {})[reaction] || 0; + + updated.reactions[reaction] = Math.max(0, currentCount - 1); + + if ($i && (body.userId === $i.id)) { + updated.myReaction = null; + } + + appearNote.value = updated; + break; + } + + case 'pollVoted': { + const choice = body.choice; + + const updated = JSON.parse(JSON.stringify(appearNote.value)); + + const choices = [...appearNote.value.poll.choices]; + choices[choice] = { + ...choices[choice], + votes: choices[choice].votes + 1, + ...($i && (body.userId === $i.id) ? { + isVoted: true + } : {}) + }; + + updated.poll.choices = choices; + + appearNote.value = updated; + break; + } + + case 'deleted': { + const updated = JSON.parse(JSON.stringify(appearNote.value)); + updated.value = true; + appearNote.value = updated; + break; + } + } + } + + function capture(withHandler = false): void { + if (connection) { + // TODO: このノートがストリーミング経由で流れてきた場合のみ sr する + connection.send(document.body.contains(props.rootEl.value) ? 'sr' : 's', { id: appearNote.value.id }); + if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated); + } + } + + function decapture(withHandler = false): void { + if (connection) { + connection.send('un', { + id: appearNote.value.id, + }); + if (withHandler) connection.off('noteUpdated', onStreamNoteUpdated); + } + } + + function onStreamConnected() { + capture(false); + } + + capture(true); + if (connection) { + connection.on('_connected_', onStreamConnected); + } + + onUnmounted(() => { + decapture(true); + if (connection) { + connection.off('_connected_', onStreamConnected); + } + }); +} diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index 745d323100..a57e8ec62b 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -160,7 +160,7 @@ export const defaultStore = markRaw(new Storage('base', { }, useReactionPickerForContextMenu: { where: 'device', - default: true + default: false }, showGapBetweenNotesInTimeline: { where: 'device', From 41ece00789dedbcacfdfff885707ace15b0e2696 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 14 Jan 2022 10:29:18 +0900 Subject: [PATCH 28/81] wip: refactor(client): migrate paging components to composition api --- .../client/src/components/note-preview.vue | 18 ++++------- .../client/src/components/note-simple.vue | 30 +++++-------------- 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/packages/client/src/components/note-preview.vue b/packages/client/src/components/note-preview.vue index bdcb8d5eed..a78b499654 100644 --- a/packages/client/src/components/note-preview.vue +++ b/packages/client/src/components/note-preview.vue @@ -14,20 +14,12 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - components: { - }, - - props: { - text: { - type: String, - required: true - } - }, -}); +const props = defineProps<{ + text: string; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/note-simple.vue b/packages/client/src/components/note-simple.vue index d8236a2096..c6907787b5 100644 --- a/packages/client/src/components/note-simple.vue +++ b/packages/client/src/components/note-simple.vue @@ -16,33 +16,19 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import XNoteHeader from './note-header.vue'; import MkNoteSubNoteContent from './sub-note-content.vue'; import XCwButton from './cw-button.vue'; -import * as os from '@/os'; -export default defineComponent({ - components: { - XNoteHeader, - MkNoteSubNoteContent, - XCwButton, - }, +const props = defineProps<{ + note: misskey.entities.Note; + pinned?: boolean; +}>(); - props: { - note: { - type: Object, - required: true - } - }, - - data() { - return { - showContent: false - }; - } -}); +const showContent = $ref(false); </script> <style lang="scss" scoped> From c8a90ec7d15a8df5fe982e7daaae58e1af2f433c Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 14 Jan 2022 10:35:32 +0900 Subject: [PATCH 29/81] wip: refactor(client): migrate paging components to composition api --- .../client/src/components/reaction-icon.vue | 28 ++++-------- .../src/components/reaction-tooltip.vue | 35 +++++---------- .../components/reactions-viewer.details.vue | 45 ++++++------------- .../client/src/components/renote.details.vue | 34 +++++--------- 4 files changed, 46 insertions(+), 96 deletions(-) diff --git a/packages/client/src/components/reaction-icon.vue b/packages/client/src/components/reaction-icon.vue index c0ec955e32..5638c9a816 100644 --- a/packages/client/src/components/reaction-icon.vue +++ b/packages/client/src/components/reaction-icon.vue @@ -1,25 +1,13 @@ <template> -<MkEmoji :emoji="reaction" :custom-emojis="customEmojis" :is-reaction="true" :normal="true" :no-style="noStyle"/> +<MkEmoji :emoji="reaction" :custom-emojis="customEmojis || []" :is-reaction="true" :normal="true" :no-style="noStyle"/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - props: { - reaction: { - type: String, - required: true - }, - customEmojis: { - required: false, - default: () => [] - }, - noStyle: { - type: Boolean, - required: false, - default: false - }, - }, -}); +const props = defineProps<{ + reaction: string; + customEmojis?: any[]; // TODO + noStyle?: boolean; +}>(); </script> diff --git a/packages/client/src/components/reaction-tooltip.vue b/packages/client/src/components/reaction-tooltip.vue index dda8e7c6d7..1b2a024e21 100644 --- a/packages/client/src/components/reaction-tooltip.vue +++ b/packages/client/src/components/reaction-tooltip.vue @@ -1,5 +1,5 @@ <template> -<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="$emit('closed')"> +<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="emit('closed')"> <div class="beeadbfb"> <XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/> <div class="name">{{ reaction.replace('@.', '') }}</div> @@ -7,31 +7,20 @@ </MkTooltip> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkTooltip from './ui/tooltip.vue'; import XReactionIcon from './reaction-icon.vue'; -export default defineComponent({ - components: { - MkTooltip, - XReactionIcon, - }, - props: { - reaction: { - type: String, - required: true, - }, - emojis: { - type: Array, - required: true, - }, - source: { - required: true, - } - }, - emits: ['closed'], -}) +const props = defineProps<{ + reaction: string; + emojis: any[]; // TODO + source: any; // TODO +}>(); + +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/reactions-viewer.details.vue b/packages/client/src/components/reactions-viewer.details.vue index d6374517a2..8cec8dfa2f 100644 --- a/packages/client/src/components/reactions-viewer.details.vue +++ b/packages/client/src/components/reactions-viewer.details.vue @@ -1,5 +1,5 @@ <template> -<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="$emit('closed')"> +<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="emit('closed')"> <div class="bqxuuuey"> <div class="reaction"> <XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/> @@ -16,39 +16,22 @@ </MkTooltip> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkTooltip from './ui/tooltip.vue'; import XReactionIcon from './reaction-icon.vue'; -export default defineComponent({ - components: { - MkTooltip, - XReactionIcon - }, - props: { - reaction: { - type: String, - required: true, - }, - users: { - type: Array, - required: true, - }, - count: { - type: Number, - required: true, - }, - emojis: { - type: Array, - required: true, - }, - source: { - required: true, - } - }, - emits: ['closed'], -}) +const props = defineProps<{ + reaction: string; + users: any[]; // TODO + count: number; + emojis: any[]; // TODO + source: any; // TODO +}>(); + +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/renote.details.vue b/packages/client/src/components/renote.details.vue index e3ef15c753..cdbc71bdce 100644 --- a/packages/client/src/components/renote.details.vue +++ b/packages/client/src/components/renote.details.vue @@ -1,5 +1,5 @@ <template> -<MkTooltip ref="tooltip" :source="source" :max-width="250" @closed="$emit('closed')"> +<MkTooltip ref="tooltip" :source="source" :max-width="250" @closed="emit('closed')"> <div class="beaffaef"> <div v-for="u in users" :key="u.id" class="user"> <MkAvatar class="avatar" :user="u"/> @@ -10,29 +10,19 @@ </MkTooltip> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkTooltip from './ui/tooltip.vue'; -export default defineComponent({ - components: { - MkTooltip, - }, - props: { - users: { - type: Array, - required: true, - }, - count: { - type: Number, - required: true, - }, - source: { - required: true, - } - }, - emits: ['closed'], -}) +const props = defineProps<{ + users: any[]; // TODO + count: number; + source: any; // TODO +}>(); + +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); </script> <style lang="scss" scoped> From 45462e4a5e8b94be110968afb2985bb145d7b6b3 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 14 Jan 2022 12:02:10 +0900 Subject: [PATCH 30/81] wip: refactor(client): migrate paging components to composition api --- .../src/components/sub-note-content.vue | 38 ++++++------------- .../src/components/user-online-indicator.vue | 31 +++++++-------- packages/client/src/pages/note.vue | 8 ++-- 3 files changed, 29 insertions(+), 48 deletions(-) diff --git a/packages/client/src/components/sub-note-content.vue b/packages/client/src/components/sub-note-content.vue index efa202ce2f..d6a37d07be 100644 --- a/packages/client/src/components/sub-note-content.vue +++ b/packages/client/src/components/sub-note-content.vue @@ -21,35 +21,21 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XPoll from './poll.vue'; import XMediaList from './media-list.vue'; -import * as os from '@/os'; +import * as misskey from 'misskey-js'; -export default defineComponent({ - components: { - XPoll, - XMediaList, - }, - props: { - note: { - type: Object, - required: true - } - }, - data() { - return { - collapsed: false, - }; - }, - created() { - this.collapsed = this.note.cw == null && this.note.text && ( - (this.note.text.split('\n').length > 9) || - (this.note.text.length > 500) - ); - } -}); +const props = defineProps<{ + note: misskey.entities.Note; +}>(); + +const collapsed = $ref( + props.note.cw == null && props.note.text != null && ( + (props.note.text.split('\n').length > 9) || + (props.note.text.length > 500) + )); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/user-online-indicator.vue b/packages/client/src/components/user-online-indicator.vue index 93e9dea57b..a87b0aeff5 100644 --- a/packages/client/src/components/user-online-indicator.vue +++ b/packages/client/src/components/user-online-indicator.vue @@ -2,26 +2,21 @@ <div v-tooltip="text" class="fzgwjkgc" :class="user.onlineStatus"></div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - user: { - type: Object, - required: true - }, - }, +const props = defineProps<{ + user: misskey.entities.User; +}>(); - computed: { - text(): string { - switch (this.user.onlineStatus) { - case 'online': return this.$ts.online; - case 'active': return this.$ts.active; - case 'offline': return this.$ts.offline; - case 'unknown': return this.$ts.unknown; - } - } +const text = $computed(() => { + switch (props.user.onlineStatus) { + case 'online': return i18n.locale.online; + case 'active': return i18n.locale.active; + case 'offline': return i18n.locale.offline; + case 'unknown': return i18n.locale.unknown; } }); </script> diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue index 2249fa2b91..72ac85ee90 100644 --- a/packages/client/src/pages/note.vue +++ b/packages/client/src/pages/note.vue @@ -84,19 +84,19 @@ export default defineComponent({ prev: { endpoint: 'users/notes' as const, limit: 10, - params: init => ({ + params: computed(() => ({ userId: this.note.userId, untilId: this.note.id, - }) + })), }, next: { reversed: true, endpoint: 'users/notes' as const, limit: 10, - params: init => ({ + params: computed(() => ({ userId: this.note.userId, sinceId: this.note.id, - }) + })), }, }; }, From 7f4fc20f9860008cc44b7c1f5e2642823d675f59 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 14 Jan 2022 23:23:08 +0900 Subject: [PATCH 31/81] wip: refactor(client): migrate components to composition api --- packages/client/src/pages/drive.vue | 28 ++---- packages/client/src/pages/federation.vue | 93 +++++++---------- packages/client/src/pages/notifications.vue | 106 +++++++++----------- 3 files changed, 95 insertions(+), 132 deletions(-) diff --git a/packages/client/src/pages/drive.vue b/packages/client/src/pages/drive.vue index f30000367f..1e17bea0cc 100644 --- a/packages/client/src/pages/drive.vue +++ b/packages/client/src/pages/drive.vue @@ -4,27 +4,21 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XDrive from '@/components/drive.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XDrive - }, +let folder = $ref(null); - data() { - return { - [symbols.PAGE_INFO]: { - title: computed(() => this.folder ? this.folder.name : this.$ts.drive), - icon: 'fas fa-cloud', - bg: 'var(--bg)', - hideHeader: true, - }, - folder: null, - }; - }, +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: folder ? folder.name : i18n.locale.drive, + icon: 'fas fa-cloud', + bg: 'var(--bg)', + hideHeader: true, + })), }); </script> diff --git a/packages/client/src/pages/federation.vue b/packages/client/src/pages/federation.vue index 610c9233a3..6a4a28b6b4 100644 --- a/packages/client/src/pages/federation.vue +++ b/packages/client/src/pages/federation.vue @@ -95,8 +95,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -104,64 +104,41 @@ import MkPagination from '@/components/ui/pagination.vue'; import FormSplit from '@/components/form/split.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkInput, - MkSelect, - MkPagination, - FormSplit, +let host = $ref(''); +let state = $ref('federating'); +let sort = $ref('+pubSub'); +const pagination = { + endpoint: 'federation/instances' as const, + limit: 10, + offsetMode: true, + params: computed(() => ({ + sort: sort, + host: host != '' ? host : null, + ...( + state === 'federating' ? { federating: true } : + state === 'subscribing' ? { subscribing: true } : + state === 'publishing' ? { publishing: true } : + state === 'suspended' ? { suspended: true } : + state === 'blocked' ? { blocked: true } : + state === 'notResponding' ? { notResponding: true } : + {}) + })) +}; + +function getStatus(instance) { + if (instance.isSuspended) return 'suspended'; + if (instance.isNotResponding) return 'error'; + return 'alive'; +}; + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.federation, + icon: 'fas fa-globe', + bg: 'var(--bg)', }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.federation, - icon: 'fas fa-globe', - bg: 'var(--bg)', - }, - host: '', - state: 'federating', - sort: '+pubSub', - pagination: { - endpoint: 'federation/instances' as const, - limit: 10, - offsetMode: true, - params: computed(() => ({ - sort: this.sort, - host: this.host != '' ? this.host : null, - ...( - this.state === 'federating' ? { federating: true } : - this.state === 'subscribing' ? { subscribing: true } : - this.state === 'publishing' ? { publishing: true } : - this.state === 'suspended' ? { suspended: true } : - this.state === 'blocked' ? { blocked: true } : - this.state === 'notResponding' ? { notResponding: true } : - {}) - })) - }, - } - }, - - watch: { - host() { - this.$refs.instances.reload(); - }, - state() { - this.$refs.instances.reload(); - } - }, - - methods: { - getStatus(instance) { - if (instance.isSuspended) return 'suspended'; - if (instance.isNotResponding) return 'error'; - return 'alive'; - }, - } }); </script> diff --git a/packages/client/src/pages/notifications.vue b/packages/client/src/pages/notifications.vue index 695c54a535..090e80f99a 100644 --- a/packages/client/src/pages/notifications.vue +++ b/packages/client/src/pages/notifications.vue @@ -6,70 +6,62 @@ </MkSpacer> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XNotifications from '@/components/notifications.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { notificationTypes } from 'misskey-js'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotifications - }, +let tab = $ref('all'); +let includeTypes = $ref<string[] | null>(null); - data() { - return { - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.notifications, - icon: 'fas fa-bell', - bg: 'var(--bg)', - actions: [{ - text: this.$ts.filter, - icon: 'fas fa-filter', - highlighted: this.includeTypes != null, - handler: this.setFilter, - }, { - text: this.$ts.markAllAsRead, - icon: 'fas fa-check', - handler: () => { - os.apiWithDialog('notifications/mark-all-as-read'); - }, - }], - tabs: [{ - active: this.tab === 'all', - title: this.$ts.all, - onClick: () => { this.tab = 'all'; }, - }, { - active: this.tab === 'unread', - title: this.$ts.unread, - onClick: () => { this.tab = 'unread'; }, - },] - })), - tab: 'all', - includeTypes: null, - }; - }, - - methods: { - setFilter(ev) { - const typeItems = notificationTypes.map(t => ({ - text: this.$t(`_notification._types.${t}`), - active: this.includeTypes && this.includeTypes.includes(t), - action: () => { - this.includeTypes = [t]; - } - })); - const items = this.includeTypes != null ? [{ - icon: 'fas fa-times', - text: this.$ts.clear, - action: () => { - this.includeTypes = null; - } - }, null, ...typeItems] : typeItems; - os.popupMenu(items, ev.currentTarget || ev.target); +function setFilter(ev) { + const typeItems = notificationTypes.map(t => ({ + text: i18n.t(`_notification._types.${t}`), + active: includeTypes && includeTypes.includes(t), + action: () => { + includeTypes = [t]; } - } + })); + const items = includeTypes != null ? [{ + icon: 'fas fa-times', + text: i18n.locale.clear, + action: () => { + includeTypes = null; + } + }, null, ...typeItems] : typeItems; + os.popupMenu(items, ev.currentTarget || ev.target); +} + +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.notifications, + icon: 'fas fa-bell', + bg: 'var(--bg)', + actions: [{ + text: i18n.locale.filter, + icon: 'fas fa-filter', + highlighted: includeTypes != null, + handler: setFilter, + }, { + text: i18n.locale.markAllAsRead, + icon: 'fas fa-check', + handler: () => { + os.apiWithDialog('notifications/mark-all-as-read'); + }, + }], + tabs: [{ + active: tab === 'all', + title: i18n.locale.all, + onClick: () => { tab = 'all'; }, + }, { + active: tab === 'unread', + title: i18n.locale.unread, + onClick: () => { tab = 'unread'; }, + },] + })), }); </script> From ffc07a08d76c3b11f5ab7e5fc03ff1ec17f8daf0 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sat, 15 Jan 2022 16:40:15 +0900 Subject: [PATCH 32/81] wip: refactor(client): migrate components to composition api --- .../client/src/pages/my-antennas/create.vue | 62 +++++----- packages/client/src/pages/my-clips/index.vue | 113 ++++++++---------- packages/client/src/pages/my-lists/index.vue | 63 +++++----- 3 files changed, 107 insertions(+), 131 deletions(-) diff --git a/packages/client/src/pages/my-antennas/create.vue b/packages/client/src/pages/my-antennas/create.vue index 173807475a..427c9935c3 100644 --- a/packages/client/src/pages/my-antennas/create.vue +++ b/packages/client/src/pages/my-antennas/create.vue @@ -4,45 +4,37 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; +<script lang="ts" setup> +import { } from 'vue'; import XAntenna from './editor.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { router } from '@/router'; -export default defineComponent({ - components: { - MkButton, - XAntenna, +let draft = $ref({ + name: '', + src: 'all', + userListId: null, + userGroupId: null, + users: [], + keywords: [], + excludeKeywords: [], + withReplies: false, + caseSensitive: false, + withFile: false, + notify: false +}); + +function onAntennaCreated() { + router.push('/my/antennas'); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.manageAntennas, + icon: 'fas fa-satellite', + bg: 'var(--bg)', }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.manageAntennas, - icon: 'fas fa-satellite', - }, - draft: { - name: '', - src: 'all', - userListId: null, - userGroupId: null, - users: [], - keywords: [], - excludeKeywords: [], - withReplies: false, - caseSensitive: false, - withFile: false, - notify: false - }, - }; - }, - - methods: { - onAntennaCreated() { - this.$router.push('/my/antennas'); - }, - } }); </script> diff --git a/packages/client/src/pages/my-clips/index.vue b/packages/client/src/pages/my-clips/index.vue index ccfb9095d1..97b563f6f8 100644 --- a/packages/client/src/pages/my-clips/index.vue +++ b/packages/client/src/pages/my-clips/index.vue @@ -3,7 +3,7 @@ <div class="qtcaoidl"> <MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> - <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="list"> + <MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="list"> <MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap"> <b>{{ item.name }}</b> <div v-if="item.description" class="description">{{ item.description }}</div> @@ -13,71 +13,64 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import i18n from '@/components/global/i18n'; -export default defineComponent({ - components: { - MkPagination, - MkButton, +const pagination = { + endpoint: 'clips/list' as const, + limit: 10, +}; + +const pagingComponent = $ref<InstanceType<typeof MkPagination>>(); + +async function create() { + const { canceled, result } = await os.form(i18n.locale.createNewClip, { + name: { + type: 'string', + label: i18n.locale.name, + }, + description: { + type: 'string', + required: false, + multiline: true, + label: i18n.locale.description, + }, + isPublic: { + type: 'boolean', + label: i18n.locale.public, + default: false, + }, + }); + if (canceled) return; + + os.apiWithDialog('clips/create', result); + + pagingComponent.reload(); +} + +function onClipCreated() { + pagingComponent.reload(); +} + +function onClipDeleted() { + pagingComponent.reload(); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.clip, + icon: 'fas fa-paperclip', + bg: 'var(--bg)', + action: { + icon: 'fas fa-plus', + handler: create + }, }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.clip, - icon: 'fas fa-paperclip', - bg: 'var(--bg)', - action: { - icon: 'fas fa-plus', - handler: this.create - } - }, - pagination: { - endpoint: 'clips/list' as const, - limit: 10, - }, - draft: null, - }; - }, - - methods: { - async create() { - const { canceled, result } = await os.form(this.$ts.createNewClip, { - name: { - type: 'string', - label: this.$ts.name - }, - description: { - type: 'string', - required: false, - multiline: true, - label: this.$ts.description - }, - isPublic: { - type: 'boolean', - label: this.$ts.public, - default: false - } - }); - if (canceled) return; - - os.apiWithDialog('clips/create', result); - }, - - onClipCreated() { - this.$refs.list.reload(); - this.draft = null; - }, - - onClipDeleted() { - this.$refs.list.reload(); - }, - } }); </script> diff --git a/packages/client/src/pages/my-lists/index.vue b/packages/client/src/pages/my-lists/index.vue index 1b1b7e2d02..e6fcba1b34 100644 --- a/packages/client/src/pages/my-lists/index.vue +++ b/packages/client/src/pages/my-lists/index.vue @@ -3,7 +3,7 @@ <div class="qkcjvfiv"> <MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.createList }}</MkButton> - <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="lists _content"> + <MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="lists _content"> <MkA v-for="list in items" :key="list.id" class="list _panel" :to="`/my/lists/${ list.id }`"> <div class="name">{{ list.name }}</div> <MkAvatars :user-ids="list.userIds"/> @@ -13,50 +13,41 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkButton from '@/components/ui/button.vue'; import MkAvatars from '@/components/avatars.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkPagination, - MkButton, - MkAvatars, - }, +const pagingComponent = $ref<InstanceType<typeof MkPagination>>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.manageLists, - icon: 'fas fa-list-ul', - bg: 'var(--bg)', - action: { - icon: 'fas fa-plus', - handler: this.create - }, - }, - pagination: { - endpoint: 'users/lists/list' as const, - limit: 10, - }, - }; - }, +const pagination = { + endpoint: 'users/lists/list' as const, + limit: 10, +}; - methods: { - async create() { - const { canceled, result: name } = await os.inputText({ - title: this.$ts.enterListName, - }); - if (canceled) return; - await os.api('users/lists/create', { name: name }); - this.$refs.list.reload(); - os.success(); +async function create() { + const { canceled, result: name } = await os.inputText({ + title: i18n.locale.enterListName, + }); + if (canceled) return; + await os.apiWithDialog('users/lists/create', { name: name }); + pagingComponent.reload(); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.manageLists, + icon: 'fas fa-list-ul', + bg: 'var(--bg)', + action: { + icon: 'fas fa-plus', + handler: create, }, - } + }, }); </script> From 41e18aa993c4644c5329e37ff9035a82dad0de9e Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sat, 15 Jan 2022 17:58:35 +0900 Subject: [PATCH 33/81] wip: refactor(client): migrate components to composition api --- .../client/src/pages/settings/mute-block.vue | 53 +++----- .../src/pages/settings/theme.install.vue | 123 ++++++++---------- packages/client/src/ui/_common_/upload.vue | 14 +- .../client/src/widgets/server-metric/disk.vue | 33 ++--- .../client/src/widgets/server-metric/pie.vue | 33 ++--- 5 files changed, 100 insertions(+), 156 deletions(-) diff --git a/packages/client/src/pages/settings/mute-block.vue b/packages/client/src/pages/settings/mute-block.vue index 903d32d08c..f4f9ebf8dd 100644 --- a/packages/client/src/pages/settings/mute-block.vue +++ b/packages/client/src/pages/settings/mute-block.vue @@ -27,8 +27,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkTab from '@/components/tab.vue'; import FormInfo from '@/components/ui/info.vue'; @@ -36,38 +36,25 @@ import FormLink from '@/components/form/link.vue'; import { userPage } from '@/filters/user'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkPagination, - MkTab, - FormInfo, - FormLink, +let tab = $ref('mute'); + +const mutingPagination = { + endpoint: 'mute/list' as const, + limit: 10, +}; + +const blockingPagination = { + endpoint: 'blocking/list' as const, + limit: 10, +}; + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.muteAndBlock, + icon: 'fas fa-ban', + bg: 'var(--bg)', }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.muteAndBlock, - icon: 'fas fa-ban', - bg: 'var(--bg)', - }, - tab: 'mute', - mutingPagination: { - endpoint: 'mute/list' as const, - limit: 10, - }, - blockingPagination: { - endpoint: 'blocking/list' as const, - limit: 10, - }, - } - }, - - methods: { - userPage - } }); </script> diff --git a/packages/client/src/pages/settings/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue index deab77c615..e2a3f042b9 100644 --- a/packages/client/src/pages/settings/theme.install.vue +++ b/packages/client/src/pages/settings/theme.install.vue @@ -1,18 +1,18 @@ <template> <div class="_formRoot"> <FormTextarea v-model="installThemeCode" class="_formBlock"> - <template #label>{{ $ts._theme.code }}</template> + <template #label>{{ i18n.locale._theme.code }}</template> </FormTextarea> <div class="_formBlock" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> - <FormButton :disabled="installThemeCode == null" inline @click="() => preview(installThemeCode)"><i class="fas fa-eye"></i> {{ $ts.preview }}</FormButton> - <FormButton :disabled="installThemeCode == null" primary inline @click="() => install(installThemeCode)"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton> + <FormButton :disabled="installThemeCode == null" inline @click="() => preview(installThemeCode)"><i class="fas fa-eye"></i> {{ i18n.locale.preview }}</FormButton> + <FormButton :disabled="installThemeCode == null" primary inline @click="() => install(installThemeCode)"><i class="fas fa-check"></i> {{ i18n.locale.install }}</FormButton> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import * as JSON5 from 'json5'; import FormTextarea from '@/components/form/textarea.vue'; import FormButton from '@/components/ui/button.vue'; @@ -20,71 +20,60 @@ import { applyTheme, validateTheme } from '@/scripts/theme'; import * as os from '@/os'; import { addTheme, getThemes } from '@/theme-store'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormTextarea, - FormButton, - }, +let installThemeCode = $ref(null); - emits: ['info'], +function parseThemeCode(code: string) { + let theme; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._theme.install, - icon: 'fas fa-download', - bg: 'var(--bg)', - }, - installThemeCode: null, - } - }, - - methods: { - parseThemeCode(code) { - let theme; - - try { - theme = JSON5.parse(code); - } catch (e) { - os.alert({ - type: 'error', - text: this.$ts._theme.invalid - }); - return false; - } - if (!validateTheme(theme)) { - os.alert({ - type: 'error', - text: this.$ts._theme.invalid - }); - return false; - } - if (getThemes().some(t => t.id === theme.id)) { - os.alert({ - type: 'info', - text: this.$ts._theme.alreadyInstalled - }); - return false; - } - - return theme; - }, - - preview(code) { - const theme = this.parseThemeCode(code); - if (theme) applyTheme(theme, false); - }, - - async install(code) { - const theme = this.parseThemeCode(code); - if (!theme) return; - await addTheme(theme); - os.alert({ - type: 'success', - text: this.$t('_theme.installed', { name: theme.name }) - }); - }, + try { + theme = JSON5.parse(code); + } catch (e) { + os.alert({ + type: 'error', + text: i18n.locale._theme.invalid + }); + return false; } + if (!validateTheme(theme)) { + os.alert({ + type: 'error', + text: i18n.locale._theme.invalid + }); + return false; + } + if (getThemes().some(t => t.id === theme.id)) { + os.alert({ + type: 'info', + text: i18n.locale._theme.alreadyInstalled + }); + return false; + } + + return theme; +} + +function preview(code: string): void { + const theme = parseThemeCode(code); + if (theme) applyTheme(theme, false); +} + +async function install(code: string): Promise<void> { + const theme = parseThemeCode(code); + if (!theme) return; + await addTheme(theme); + os.alert({ + type: 'success', + text: i18n.t('_theme.installed', { name: theme.name }) + }); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale._theme.install, + icon: 'fas fa-download', + bg: 'var(--bg)', + }, }); </script> diff --git a/packages/client/src/ui/_common_/upload.vue b/packages/client/src/ui/_common_/upload.vue index a1c5dcdecc..ab7678a505 100644 --- a/packages/client/src/ui/_common_/upload.vue +++ b/packages/client/src/ui/_common_/upload.vue @@ -17,18 +17,12 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import * as os from '@/os'; -export default defineComponent({ - data() { - return { - uploads: os.uploads, - zIndex: os.claimZIndex('high'), - }; - }, -}); +const uploads = os.uploads; +const zIndex = os.claimZIndex('high'); </script> <style lang="scss" scoped> diff --git a/packages/client/src/widgets/server-metric/disk.vue b/packages/client/src/widgets/server-metric/disk.vue index 650101b0ee..052991b554 100644 --- a/packages/client/src/widgets/server-metric/disk.vue +++ b/packages/client/src/widgets/server-metric/disk.vue @@ -10,32 +10,19 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XPie from './pie.vue'; import bytes from '@/filters/bytes'; -export default defineComponent({ - components: { - XPie - }, - props: { - meta: { - required: true, - } - }, - data() { - return { - usage: this.meta.fs.used / this.meta.fs.total, - total: this.meta.fs.total, - used: this.meta.fs.used, - available: this.meta.fs.total - this.meta.fs.used, - }; - }, - methods: { - bytes - } -}); +const props = defineProps<{ + meta: any; // TODO +}>(); + +const usage = $computed(() => props.meta.fs.used / props.meta.fs.total); +const total = $computed(() => props.meta.fs.total); +const used = $computed(() => props.meta.fs.used); +const available = $computed(() => props.meta.fs.total - props.meta.fs.used); </script> <style lang="scss" scoped> diff --git a/packages/client/src/widgets/server-metric/pie.vue b/packages/client/src/widgets/server-metric/pie.vue index 38dcf6fcd9..868dbc0484 100644 --- a/packages/client/src/widgets/server-metric/pie.vue +++ b/packages/client/src/widgets/server-metric/pie.vue @@ -20,30 +20,17 @@ </svg> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - props: { - value: { - type: Number, - required: true - } - }, - data() { - return { - r: 0.45 - }; - }, - computed: { - color(): string { - return `hsl(${180 - (this.value * 180)}, 80%, 70%)`; - }, - strokeDashoffset(): number { - return (1 - this.value) * (Math.PI * (this.r * 2)); - } - } -}); +const props = defineProps<{ + value: number; +}>(); + +const r = 0.45; + +const color = $computed(() => `hsl(${180 - (props.value * 180)}, 80%, 70%)`); +const strokeDashoffset = $computed(() => (1 - props.value) * (Math.PI * (r * 2))); </script> <style lang="scss" scoped> From daba865a94d19124594caa56254b2b5cd9a26f0a Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sat, 15 Jan 2022 20:35:10 +0900 Subject: [PATCH 34/81] wip: refactor(client): migrate components to composition api --- .../client/src/pages/settings/privacy.vue | 92 +++++++------------ 1 file changed, 34 insertions(+), 58 deletions(-) diff --git a/packages/client/src/pages/settings/privacy.vue b/packages/client/src/pages/settings/privacy.vue index 593ca75105..dd13ba4bd0 100644 --- a/packages/client/src/pages/settings/privacy.vue +++ b/packages/client/src/pages/settings/privacy.vue @@ -47,8 +47,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormSelect from '@/components/form/select.vue'; import FormSection from '@/components/form/section.vue'; @@ -56,63 +56,39 @@ import FormGroup from '@/components/form/group.vue'; import * as os from '@/os'; import { defaultStore } from '@/store'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - FormSelect, - FormSection, - FormGroup, - FormSwitch, +let isLocked = $ref($i.isLocked); +let autoAcceptFollowed = $ref($i.autoAcceptFollowed); +let noCrawle = $ref($i.noCrawle); +let isExplorable = $ref($i.isExplorable); +let hideOnlineStatus = $ref($i.hideOnlineStatus); +let publicReactions = $ref($i.publicReactions); +let ffVisibility = $ref($i.ffVisibility); + +let defaultNoteVisibility = $computed(defaultStore.makeGetterSetter('defaultNoteVisibility')); +let defaultNoteLocalOnly = $computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly')); +let rememberNoteVisibility = $computed(defaultStore.makeGetterSetter('rememberNoteVisibility')); +let keepCw = $computed(defaultStore.makeGetterSetter('keepCw')); + +function save() { + os.api('i/update', { + isLocked: !!isLocked, + autoAcceptFollowed: !!autoAcceptFollowed, + noCrawle: !!noCrawle, + isExplorable: !!isExplorable, + hideOnlineStatus: !!hideOnlineStatus, + publicReactions: !!publicReactions, + ffVisibility: ffVisibility, + }); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.privacy, + icon: 'fas fa-lock-open', + bg: 'var(--bg)', }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.privacy, - icon: 'fas fa-lock-open', - bg: 'var(--bg)', - }, - isLocked: false, - autoAcceptFollowed: false, - noCrawle: false, - isExplorable: false, - hideOnlineStatus: false, - publicReactions: false, - ffVisibility: 'public', - } - }, - - computed: { - defaultNoteVisibility: defaultStore.makeGetterSetter('defaultNoteVisibility'), - defaultNoteLocalOnly: defaultStore.makeGetterSetter('defaultNoteLocalOnly'), - rememberNoteVisibility: defaultStore.makeGetterSetter('rememberNoteVisibility'), - keepCw: defaultStore.makeGetterSetter('keepCw'), - }, - - created() { - this.isLocked = this.$i.isLocked; - this.autoAcceptFollowed = this.$i.autoAcceptFollowed; - this.noCrawle = this.$i.noCrawle; - this.isExplorable = this.$i.isExplorable; - this.hideOnlineStatus = this.$i.hideOnlineStatus; - this.publicReactions = this.$i.publicReactions; - this.ffVisibility = this.$i.ffVisibility; - }, - - methods: { - save() { - os.api('i/update', { - isLocked: !!this.isLocked, - autoAcceptFollowed: !!this.autoAcceptFollowed, - noCrawle: !!this.noCrawle, - isExplorable: !!this.isExplorable, - hideOnlineStatus: !!this.hideOnlineStatus, - publicReactions: !!this.publicReactions, - ffVisibility: this.ffVisibility, - }); - } - } }); </script> From 21c9705a0f947bf6e3900052987695c67f2d7510 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sat, 15 Jan 2022 20:42:30 +0900 Subject: [PATCH 35/81] wip: refactor(client): migrate components to composition api --- .../client/src/components/instance-ticker.vue | 41 +++++-------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/packages/client/src/components/instance-ticker.vue b/packages/client/src/components/instance-ticker.vue index 1ce5a1c2c1..77fd8bb344 100644 --- a/packages/client/src/components/instance-ticker.vue +++ b/packages/client/src/components/instance-ticker.vue @@ -1,41 +1,22 @@ <template> <div class="hpaizdrt" :style="bg"> - <img v-if="info.faviconUrl" class="icon" :src="info.faviconUrl"/> - <span class="name">{{ info.name }}</span> + <img v-if="instance.faviconUrl" class="icon" :src="instance.faviconUrl"/> + <span class="name">{{ instance.name }}</span> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import { instanceName } from '@/config'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - props: { - instance: { - type: Object, - required: false - }, - }, +const props = defineProps<{ + instance: any; // TODO +}>(); - data() { - return { - info: this.instance || { - faviconUrl: '/favicon.ico', - name: instanceName, - themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content - } - } - }, +const themeColor = props.instance.themeColor || '#777777'; - computed: { - bg(): any { - const themeColor = this.info.themeColor || '#777777'; - return { - background: `linear-gradient(90deg, ${themeColor}, ${themeColor + '00'})` - }; - } - } -}); +const bg = { + background: `linear-gradient(90deg, ${themeColor}, ${themeColor + '00'})` +}; </script> <style lang="scss" scoped> From c17e8fa8a4be4cc7b20e18adb37605a444823318 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 01:46:25 +0900 Subject: [PATCH 36/81] wip: refactor(client): migrate components to composition api --- packages/client/src/components/post-form.vue | 1170 +++++++++--------- packages/client/src/scripts/autocomplete.ts | 26 +- 2 files changed, 564 insertions(+), 632 deletions(-) diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue index 24f35da2e9..7b2f79e389 100644 --- a/packages/client/src/components/post-form.vue +++ b/packages/client/src/components/post-form.vue @@ -9,7 +9,7 @@ <header> <button v-if="!fixed" class="cancel _button" @click="cancel"><i class="fas fa-times"></i></button> <div> - <span class="text-count" :class="{ over: textLength > max }">{{ max - textLength }}</span> + <span class="text-count" :class="{ over: textLength > maxTextLength }">{{ maxTextLength - textLength }}</span> <span v-if="localOnly" class="local-only"><i class="fas fa-biohazard"></i></span> <button ref="visibilityButton" v-tooltip="$ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> <span v-if="visibility === 'public'"><i class="fas fa-globe"></i></span> @@ -36,9 +36,9 @@ </div> </div> <MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ $ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ $ts.add }}</button></MkInfo> - <input v-show="useCw" ref="cw" v-model="cw" class="cw" :placeholder="$ts.annotation" @keydown="onKeydown"> - <textarea ref="text" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> - <input v-show="withHashtags" ref="hashtags" v-model="hashtags" class="hashtags" :placeholder="$ts.hashtags" list="hashtags"> + <input v-show="useCw" ref="cwInputEl" v-model="cw" class="cw" :placeholder="$ts.annotation" @keydown="onKeydown"> + <textarea ref="textareaEl" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> + <input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" class="hashtags" :placeholder="$ts.hashtags" list="hashtags"> <XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> <XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/> <XNotePreview v-if="showPreview" class="preview" :text="text"/> @@ -58,667 +58,603 @@ </div> </template> -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +<script lang="ts" setup> +import { inject, watch, nextTick, onMounted } from 'vue'; +import * as mfm from 'mfm-js'; +import * as misskey from 'misskey-js'; import insertTextAtCursor from 'insert-text-at-cursor'; import { length } from 'stringz'; import { toASCII } from 'punycode/'; import XNoteSimple from './note-simple.vue'; import XNotePreview from './note-preview.vue'; -import * as mfm from 'mfm-js'; +import XPostFormAttaches from './post-form-attaches.vue'; +import XPollEditor from './poll-editor.vue'; import { host, url } from '@/config'; import { erase, unique } from '@/scripts/array'; import { extractMentions } from '@/scripts/extract-mentions'; import * as Acct from 'misskey-js/built/acct'; import { formatTimeString } from '@/scripts/format-time-string'; import { Autocomplete } from '@/scripts/autocomplete'; -import { noteVisibilities } from 'misskey-js'; import * as os from '@/os'; import { stream } from '@/stream'; import { selectFiles } from '@/scripts/select-file'; import { defaultStore, notePostInterruptors, postFormActions } from '@/store'; import { throttle } from 'throttle-debounce'; import MkInfo from '@/components/ui/info.vue'; +import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - XNoteSimple, - XNotePreview, - XPostFormAttaches: defineAsyncComponent(() => import('./post-form-attaches.vue')), - XPollEditor: defineAsyncComponent(() => import('./poll-editor.vue')), - MkInfo, - }, +const modal = inject('modal'); - inject: ['modal'], +const props = withDefaults(defineProps<{ + reply?: misskey.entities.Note; + renote?: misskey.entities.Note; + channel?: any; // TODO + mention?: misskey.entities.User; + specified?: misskey.entities.User; + initialText?: string; + initialVisibility?: typeof misskey.noteVisibilities; + initialFiles?: misskey.entities.DriveFile[]; + initialLocalOnly?: boolean; + initialVisibleUsers?: misskey.entities.User[]; + initialNote?: misskey.entities.Note; + share?: boolean; + fixed?: boolean; + autofocus?: boolean; +}>(), { + initialVisibleUsers: [], + autofocus: true, +}); - props: { - reply: { - type: Object, - required: false - }, - renote: { - type: Object, - required: false - }, - channel: { - type: Object, - required: false - }, - mention: { - type: Object, - required: false - }, - specified: { - type: Object, - required: false - }, - initialText: { - type: String, - required: false - }, - initialVisibility: { - type: String, - required: false - }, - initialFiles: { - type: Array, - required: false - }, - initialLocalOnly: { - type: Boolean, - required: false - }, - initialVisibleUsers: { - type: Array, - required: false, - default: () => [] - }, - initialNote: { - type: Object, - required: false - }, - share: { - type: Boolean, - required: false, - default: false - }, - fixed: { - type: Boolean, - required: false, - default: false - }, - autofocus: { - type: Boolean, - required: false, - default: true - }, - }, +const emit = defineEmits<{ + (e: 'posted'): void; + (e: 'cancel'): void; + (e: 'esc'): void; +}>(); - emits: ['posted', 'cancel', 'esc'], +const textareaEl = $ref<HTMLTextAreaElement | null>(null); +const cwInputEl = $ref<HTMLInputElement | null>(null); +const hashtagsInputEl = $ref<HTMLInputElement | null>(null); +const visibilityButton = $ref<HTMLElement | null>(null); - data() { - return { - posting: false, - text: '', - files: [], - poll: null, - useCw: false, - showPreview: false, - cw: null, - localOnly: this.$store.state.rememberNoteVisibility ? this.$store.state.localOnly : this.$store.state.defaultNoteLocalOnly, - visibility: (this.$store.state.rememberNoteVisibility ? this.$store.state.visibility : this.$store.state.defaultNoteVisibility) as typeof noteVisibilities[number], - visibleUsers: [], - autocomplete: null, - draghover: false, - quoteId: null, - hasNotSpecifiedMentions: false, - recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'), - imeText: '', - typing: throttle(3000, () => { - if (this.channel) { - stream.send('typingOnChannel', { channel: this.channel.id }); - } - }), - postFormActions, - }; - }, +let posting = $ref(false); +let text = $ref(props.initialText ?? ''); +let files = $ref(props.initialFiles ?? []); +let poll = $ref<{ + choices: string[]; + multiple: boolean; + expiresAt: string; + expiredAfter: string; +} | null>(null); +let useCw = $ref(false); +let showPreview = $ref(false); +let cw = $ref<string | null>(null); +let localOnly = $ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly); +let visibility = $ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof misskey.noteVisibilities[number]); +let visibleUsers = $ref(props.initialVisibleUsers ?? []); +let autocomplete = $ref(null); +let draghover = $ref(false); +let quoteId = $ref(null); +let hasNotSpecifiedMentions = $ref(false); +let recentHashtags = $ref(JSON.parse(localStorage.getItem('hashtags') || '[]')); +let imeText = $ref(''); - computed: { - draftKey(): string { - let key = this.channel ? `channel:${this.channel.id}` : ''; +const typing = throttle(3000, () => { + if (props.channel) { + stream.send('typingOnChannel', { channel: props.channel.id }); + } +}); - if (this.renote) { - key += `renote:${this.renote.id}`; - } else if (this.reply) { - key += `reply:${this.reply.id}`; - } else { - key += 'note'; - } +const draftKey = $computed((): string => { + let key = props.channel ? `channel:${props.channel.id}` : ''; - return key; - }, + if (props.renote) { + key += `renote:${props.renote.id}`; + } else if (props.reply) { + key += `reply:${props.reply.id}`; + } else { + key += 'note'; + } - placeholder(): string { - if (this.renote) { - return this.$ts._postForm.quotePlaceholder; - } else if (this.reply) { - return this.$ts._postForm.replyPlaceholder; - } else if (this.channel) { - return this.$ts._postForm.channelPlaceholder; - } else { - const xs = [ - this.$ts._postForm._placeholders.a, - this.$ts._postForm._placeholders.b, - this.$ts._postForm._placeholders.c, - this.$ts._postForm._placeholders.d, - this.$ts._postForm._placeholders.e, - this.$ts._postForm._placeholders.f - ]; - return xs[Math.floor(Math.random() * xs.length)]; - } - }, + return key; +}); - submitText(): string { - return this.renote - ? this.$ts.quote - : this.reply - ? this.$ts.reply - : this.$ts.note; - }, +const placeholder = $computed((): string => { + if (props.renote) { + return i18n.locale._postForm.quotePlaceholder; + } else if (props.reply) { + return i18n.locale._postForm.replyPlaceholder; + } else if (props.channel) { + return i18n.locale._postForm.channelPlaceholder; + } else { + const xs = [ + i18n.locale._postForm._placeholders.a, + i18n.locale._postForm._placeholders.b, + i18n.locale._postForm._placeholders.c, + i18n.locale._postForm._placeholders.d, + i18n.locale._postForm._placeholders.e, + i18n.locale._postForm._placeholders.f + ]; + return xs[Math.floor(Math.random() * xs.length)]; + } +}); - textLength(): number { - return length((this.text + this.imeText).trim()); - }, +const submitText = $computed((): string => { + return props.renote + ? i18n.locale.quote + : props.reply + ? i18n.locale.reply + : i18n.locale.note; +}); - canPost(): boolean { - return !this.posting && - (1 <= this.textLength || 1 <= this.files.length || !!this.poll || !!this.renote) && - (this.textLength <= this.max) && - (!this.poll || this.poll.choices.length >= 2); - }, +const textLength = $computed((): number => { + return length((text + imeText).trim()); +}); - max(): number { - return this.$instance ? this.$instance.maxNoteTextLength : 1000; - }, +const maxTextLength = $computed((): number => { + return instance ? instance.maxNoteTextLength : 1000; +}); - withHashtags: defaultStore.makeGetterSetter('postFormWithHashtags'), - hashtags: defaultStore.makeGetterSetter('postFormHashtags'), - }, +const canPost = $computed((): boolean => { + return !posting && + (1 <= textLength || 1 <= files.length || !!poll || !!props.renote) && + (textLength <= maxTextLength) && + (!poll || poll.choices.length >= 2); +}); - watch: { - text() { - this.checkMissingMention(); - }, - visibleUsers: { - handler() { - this.checkMissingMention(); - }, - deep: true - } - }, +const withHashtags = $computed(defaultStore.makeGetterSetter('postFormWithHashtags')); +const hashtags = $computed(defaultStore.makeGetterSetter('postFormHashtags')); - mounted() { - if (this.initialText) { - this.text = this.initialText; - } +watch($$(text), () => { + checkMissingMention(); +}); - if (this.initialVisibility) { - this.visibility = this.initialVisibility; - } +watch($$(visibleUsers), () => { + checkMissingMention(); +}, { + deep: true, +}); - if (this.initialFiles) { - this.files = this.initialFiles; - } +if (props.mention) { + text = props.mention.host ? `@${props.mention.username}@${toASCII(props.mention.host)}` : `@${props.mention.username}`; + text += ' '; +} - if (typeof this.initialLocalOnly === 'boolean') { - this.localOnly = this.initialLocalOnly; - } +if (props.reply && (props.reply.user.username != $i.username || (props.reply.user.host != null && props.reply.user.host != host))) { + text = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `; +} - if (this.initialVisibleUsers) { - this.visibleUsers = this.initialVisibleUsers; - } +if (props.reply && props.reply.text != null) { + const ast = mfm.parse(props.reply.text); + const otherHost = props.reply.user.host; - if (this.mention) { - this.text = this.mention.host ? `@${this.mention.username}@${toASCII(this.mention.host)}` : `@${this.mention.username}`; - this.text += ' '; - } + for (const x of extractMentions(ast)) { + const mention = x.host ? + `@${x.username}@${toASCII(x.host)}` : + (otherHost == null || otherHost == host) ? + `@${x.username}` : + `@${x.username}@${toASCII(otherHost)}`; - if (this.reply && (this.reply.user.username != this.$i.username || (this.reply.user.host != null && this.reply.user.host != host))) { - this.text = `@${this.reply.user.username}${this.reply.user.host != null ? '@' + toASCII(this.reply.user.host) : ''} `; - } + // 自分は除外 + if ($i.username == x.username && x.host == null) continue; + if ($i.username == x.username && x.host == host) continue; - if (this.reply && this.reply.text != null) { - const ast = mfm.parse(this.reply.text); - const otherHost = this.reply.user.host; + // 重複は除外 + if (text.indexOf(`${mention} `) != -1) continue; - for (const x of extractMentions(ast)) { - const mention = x.host ? - `@${x.username}@${toASCII(x.host)}` : - (otherHost == null || otherHost == host) ? - `@${x.username}` : - `@${x.username}@${toASCII(otherHost)}`; + text += `${mention} `; + } +} - // 自分は除外 - if (this.$i.username == x.username && x.host == null) continue; - if (this.$i.username == x.username && x.host == host) continue; +if (props.channel) { + visibility = 'public'; + localOnly = true; // TODO: チャンネルが連合するようになった折には消す +} - // 重複は除外 - if (this.text.indexOf(`${mention} `) != -1) continue; - - this.text += `${mention} `; - } - } - - if (this.channel) { - this.visibility = 'public'; - this.localOnly = true; // TODO: チャンネルが連合するようになった折には消す - } - - // 公開以外へのリプライ時は元の公開範囲を引き継ぐ - if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) { - this.visibility = this.reply.visibility; - if (this.reply.visibility === 'specified') { - os.api('users/show', { - userIds: this.reply.visibleUserIds.filter(uid => uid !== this.$i.id && uid !== this.reply.userId) - }).then(users => { - this.visibleUsers.push(...users); - }); - - if (this.reply.userId !== this.$i.id) { - os.api('users/show', { userId: this.reply.userId }).then(user => { - this.visibleUsers.push(user); - }); - } - } - } - - if (this.specified) { - this.visibility = 'specified'; - this.visibleUsers.push(this.specified); - } - - // keep cw when reply - if (this.$store.state.keepCw && this.reply && this.reply.cw) { - this.useCw = true; - this.cw = this.reply.cw; - } - - if (this.autofocus) { - this.focus(); - - this.$nextTick(() => { - this.focus(); - }); - } - - // TODO: detach when unmount - new Autocomplete(this.$refs.text, this, { model: 'text' }); - new Autocomplete(this.$refs.cw, this, { model: 'cw' }); - new Autocomplete(this.$refs.hashtags, this, { model: 'hashtags' }); - - this.$nextTick(() => { - // 書きかけの投稿を復元 - if (!this.share && !this.mention && !this.specified) { - const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftKey]; - if (draft) { - this.text = draft.data.text; - this.useCw = draft.data.useCw; - this.cw = draft.data.cw; - this.visibility = draft.data.visibility; - this.localOnly = draft.data.localOnly; - this.files = (draft.data.files || []).filter(e => e); - if (draft.data.poll) { - this.poll = draft.data.poll; - } - } - } - - // 削除して編集 - if (this.initialNote) { - const init = this.initialNote; - this.text = init.text ? init.text : ''; - this.files = init.files; - this.cw = init.cw; - this.useCw = init.cw != null; - if (init.poll) { - this.poll = { - choices: init.poll.choices.map(x => x.text), - multiple: init.poll.multiple, - expiresAt: init.poll.expiresAt, - expiredAfter: init.poll.expiredAfter, - }; - } - this.visibility = init.visibility; - this.localOnly = init.localOnly; - this.quoteId = init.renote ? init.renote.id : null; - } - - this.$nextTick(() => this.watch()); +// 公開以外へのリプライ時は元の公開範囲を引き継ぐ +if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visibility)) { + visibility = props.reply.visibility; + if (props.reply.visibility === 'specified') { + os.api('users/show', { + userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId) + }).then(users => { + visibleUsers.push(...users); }); - }, - methods: { - watch() { - this.$watch('text', () => this.saveDraft()); - this.$watch('useCw', () => this.saveDraft()); - this.$watch('cw', () => this.saveDraft()); - this.$watch('poll', () => this.saveDraft()); - this.$watch('files', () => this.saveDraft(), { deep: true }); - this.$watch('visibility', () => this.saveDraft()); - this.$watch('localOnly', () => this.saveDraft()); - }, - - checkMissingMention() { - if (this.visibility === 'specified') { - const ast = mfm.parse(this.text); - - for (const x of extractMentions(ast)) { - if (!this.visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { - this.hasNotSpecifiedMentions = true; - return; - } - } - this.hasNotSpecifiedMentions = false; - } - }, - - addMissingMention() { - const ast = mfm.parse(this.text); - - for (const x of extractMentions(ast)) { - if (!this.visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { - os.api('users/show', { username: x.username, host: x.host }).then(user => { - this.visibleUsers.push(user); - }); - } - } - }, - - togglePoll() { - if (this.poll) { - this.poll = null; - } else { - this.poll = { - choices: ['', ''], - multiple: false, - expiresAt: null, - expiredAfter: null, - }; - } - }, - - addTag(tag: string) { - insertTextAtCursor(this.$refs.text, ` #${tag} `); - }, - - focus() { - (this.$refs.text as any).focus(); - }, - - chooseFileFrom(ev) { - selectFiles(ev.currentTarget || ev.target, this.$ts.attachFile).then(files => { - for (const file of files) { - this.files.push(file); - } + if (props.reply.userId !== $i.id) { + os.api('users/show', { userId: props.reply.userId }).then(user => { + visibleUsers.push(user); }); - }, - - detachFile(id) { - this.files = this.files.filter(x => x.id != id); - }, - - updateFiles(files) { - this.files = files; - }, - - updateFileSensitive(file, sensitive) { - this.files[this.files.findIndex(x => x.id === file.id)].isSensitive = sensitive; - }, - - updateFileName(file, name) { - this.files[this.files.findIndex(x => x.id === file.id)].name = name; - }, - - upload(file: File, name?: string) { - os.upload(file, this.$store.state.uploadFolder, name).then(res => { - this.files.push(res); - }); - }, - - onPollUpdate(poll) { - this.poll = poll; - this.saveDraft(); - }, - - setVisibility() { - if (this.channel) { - // TODO: information dialog - return; - } - - os.popup(import('./visibility-picker.vue'), { - currentVisibility: this.visibility, - currentLocalOnly: this.localOnly, - src: this.$refs.visibilityButton - }, { - changeVisibility: visibility => { - this.visibility = visibility; - if (this.$store.state.rememberNoteVisibility) { - this.$store.set('visibility', visibility); - } - }, - changeLocalOnly: localOnly => { - this.localOnly = localOnly; - if (this.$store.state.rememberNoteVisibility) { - this.$store.set('localOnly', localOnly); - } - } - }, 'closed'); - }, - - addVisibleUser() { - os.selectUser().then(user => { - this.visibleUsers.push(user); - }); - }, - - removeVisibleUser(user) { - this.visibleUsers = erase(user, this.visibleUsers); - }, - - clear() { - this.text = ''; - this.files = []; - this.poll = null; - this.quoteId = null; - }, - - onKeydown(e: KeyboardEvent) { - if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post(); - if (e.which === 27) this.$emit('esc'); - this.typing(); - }, - - onCompositionUpdate(e: CompositionEvent) { - this.imeText = e.data; - this.typing(); - }, - - onCompositionEnd(e: CompositionEvent) { - this.imeText = ''; - }, - - async onPaste(e: ClipboardEvent) { - for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) { - if (item.kind == 'file') { - const file = item.getAsFile(); - const lio = file.name.lastIndexOf('.'); - const ext = lio >= 0 ? file.name.slice(lio) : ''; - const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`; - this.upload(file, formatted); - } - } - - const paste = e.clipboardData.getData('text'); - - if (!this.renote && !this.quoteId && paste.startsWith(url + '/notes/')) { - e.preventDefault(); - - os.confirm({ - type: 'info', - text: this.$ts.quoteQuestion, - }).then(({ canceled }) => { - if (canceled) { - insertTextAtCursor(this.$refs.text, paste); - return; - } - - this.quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1]; - }); - } - }, - - onDragover(e) { - if (!e.dataTransfer.items[0]) return; - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - if (isFile || isDriveFile) { - e.preventDefault(); - this.draghover = true; - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } - }, - - onDragenter(e) { - this.draghover = true; - }, - - onDragleave(e) { - this.draghover = false; - }, - - onDrop(e): void { - this.draghover = false; - - // ファイルだったら - if (e.dataTransfer.files.length > 0) { - e.preventDefault(); - for (const x of Array.from(e.dataTransfer.files)) this.upload(x); - return; - } - - //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - this.files.push(file); - e.preventDefault(); - } - //#endregion - }, - - saveDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - - data[this.draftKey] = { - updatedAt: new Date(), - data: { - text: this.text, - useCw: this.useCw, - cw: this.cw, - visibility: this.visibility, - localOnly: this.localOnly, - files: this.files, - poll: this.poll - } - }; - - localStorage.setItem('drafts', JSON.stringify(data)); - }, - - deleteDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - - delete data[this.draftKey]; - - localStorage.setItem('drafts', JSON.stringify(data)); - }, - - async post() { - let data = { - text: this.text == '' ? undefined : this.text, - fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, - replyId: this.reply ? this.reply.id : undefined, - renoteId: this.renote ? this.renote.id : this.quoteId ? this.quoteId : undefined, - channelId: this.channel ? this.channel.id : undefined, - poll: this.poll, - cw: this.useCw ? this.cw || '' : undefined, - localOnly: this.localOnly, - visibility: this.visibility, - visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined, - }; - - if (this.withHashtags && this.hashtags && this.hashtags.trim() !== '') { - const hashtags = this.hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); - data.text = data.text ? `${data.text} ${hashtags}` : hashtags; - } - - // plugin - if (notePostInterruptors.length > 0) { - for (const interruptor of notePostInterruptors) { - data = await interruptor.handler(JSON.parse(JSON.stringify(data))); - } - } - - this.posting = true; - os.api('notes/create', data).then(() => { - this.clear(); - this.$nextTick(() => { - this.deleteDraft(); - this.$emit('posted'); - if (data.text && data.text != '') { - const hashtags = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); - const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; - localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); - } - this.posting = false; - }); - }).catch(err => { - this.posting = false; - os.alert({ - type: 'error', - text: err.message + '\n' + (err as any).id, - }); - }); - }, - - cancel() { - this.$emit('cancel'); - }, - - insertMention() { - os.selectUser().then(user => { - insertTextAtCursor(this.$refs.text, '@' + Acct.toString(user) + ' '); - }); - }, - - async insertEmoji(ev) { - os.openEmojiPicker(ev.currentTarget || ev.target, {}, this.$refs.text); - }, - - showActions(ev) { - os.popupMenu(postFormActions.map(action => ({ - text: action.title, - action: () => { - action.handler({ - text: this.text - }, (key, value) => { - if (key === 'text') { this.text = value; } - }); - } - })), ev.currentTarget || ev.target); } } +} + +if (props.specified) { + visibility = 'specified'; + visibleUsers.push(props.specified); +} + +// keep cw when reply +if (defaultStore.state.keepCw && props.reply && props.reply.cw) { + useCw = true; + cw = props.reply.cw; +} + +function watchForDraft() { + watch($$(text), () => saveDraft()); + watch($$(useCw), () => saveDraft()); + watch($$(cw), () => saveDraft()); + watch($$(poll), () => saveDraft()); + watch($$(files), () => saveDraft(), { deep: true }); + watch($$(visibility), () => saveDraft()); + watch($$(localOnly), () => saveDraft()); +} + +function checkMissingMention() { + if (visibility === 'specified') { + const ast = mfm.parse(text); + + for (const x of extractMentions(ast)) { + if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { + hasNotSpecifiedMentions = true; + return; + } + } + hasNotSpecifiedMentions = false; + } +} + +function addMissingMention() { + const ast = mfm.parse(text); + + for (const x of extractMentions(ast)) { + if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { + os.api('users/show', { username: x.username, host: x.host }).then(user => { + visibleUsers.push(user); + }); + } + } +} + +function togglePoll() { + if (poll) { + poll = null; + } else { + poll = { + choices: ['', ''], + multiple: false, + expiresAt: null, + expiredAfter: null, + }; + } +} + +function addTag(tag: string) { + insertTextAtCursor(textareaEl, ` #${tag} `); +} + +function focus() { + textareaEl.focus(); +} + +function chooseFileFrom(ev) { + selectFiles(ev.currentTarget || ev.target, i18n.locale.attachFile).then(files => { + for (const file of files) { + files.push(file); + } + }); +} + +function detachFile(id) { + files = files.filter(x => x.id != id); +} + +function updateFiles(files) { + files = files; +} + +function updateFileSensitive(file, sensitive) { + files[files.findIndex(x => x.id === file.id)].isSensitive = sensitive; +} + +function updateFileName(file, name) { + files[files.findIndex(x => x.id === file.id)].name = name; +} + +function upload(file: File, name?: string) { + os.upload(file, defaultStore.state.uploadFolder, name).then(res => { + files.push(res); + }); +} + +function onPollUpdate(poll) { + poll = poll; + saveDraft(); +} + +function setVisibility() { + if (props.channel) { + // TODO: information dialog + return; + } + + os.popup(import('./visibility-picker.vue'), { + currentVisibility: visibility, + currentLocalOnly: localOnly, + src: visibilityButton, + }, { + changeVisibility: visibility => { + visibility = visibility; + if (defaultStore.state.rememberNoteVisibility) { + defaultStore.set('visibility', visibility); + } + }, + changeLocalOnly: localOnly => { + localOnly = localOnly; + if (defaultStore.state.rememberNoteVisibility) { + defaultStore.set('localOnly', localOnly); + } + } + }, 'closed'); +} + +function addVisibleUser() { + os.selectUser().then(user => { + visibleUsers.push(user); + }); +} + +function removeVisibleUser(user) { + visibleUsers = erase(user, visibleUsers); +} + +function clear() { + text = ''; + files = []; + poll = null; + quoteId = null; +} + +function onKeydown(e: KeyboardEvent) { + if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && canPost) post(); + if (e.which === 27) emit('esc'); + typing(); +} + +function onCompositionUpdate(e: CompositionEvent) { + imeText = e.data; + typing(); +} + +function onCompositionEnd(e: CompositionEvent) { + imeText = ''; +} + +async function onPaste(e: ClipboardEvent) { + for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) { + if (item.kind == 'file') { + const file = item.getAsFile(); + const lio = file.name.lastIndexOf('.'); + const ext = lio >= 0 ? file.name.slice(lio) : ''; + const formatted = `${formatTimeString(new Date(file.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`; + upload(file, formatted); + } + } + + const paste = e.clipboardData.getData('text'); + + if (!props.renote && !quoteId && paste.startsWith(url + '/notes/')) { + e.preventDefault(); + + os.confirm({ + type: 'info', + text: i18n.locale.quoteQuestion, + }).then(({ canceled }) => { + if (canceled) { + insertTextAtCursor(textareaEl, paste); + return; + } + + quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1]; + }); + } +} + +function onDragover(e) { + if (!e.dataTransfer.items[0]) return; + const isFile = e.dataTransfer.items[0].kind == 'file'; + const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + if (isFile || isDriveFile) { + e.preventDefault(); + draghover = true; + e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + } +} + +function onDragenter(e) { + draghover = true; +} + +function onDragleave(e) { + draghover = false; +} + +function onDrop(e): void { + draghover = false; + + // ファイルだったら + if (e.dataTransfer.files.length > 0) { + e.preventDefault(); + for (const x of Array.from(e.dataTransfer.files)) upload(x); + return; + } + + //#region ドライブのファイル + const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile != '') { + const file = JSON.parse(driveFile); + files.push(file); + e.preventDefault(); + } + //#endregion +} + +function saveDraft() { + const data = JSON.parse(localStorage.getItem('drafts') || '{}'); + + data[draftKey] = { + updatedAt: new Date(), + data: { + text: text, + useCw: useCw, + cw: cw, + visibility: visibility, + localOnly: localOnly, + files: files, + poll: poll + } + }; + + localStorage.setItem('drafts', JSON.stringify(data)); +} + +function deleteDraft() { + const data = JSON.parse(localStorage.getItem('drafts') || '{}'); + + delete data[draftKey]; + + localStorage.setItem('drafts', JSON.stringify(data)); +} + +async function post() { + let data = { + text: text == '' ? undefined : text, + fileIds: files.length > 0 ? files.map(f => f.id) : undefined, + replyId: props.reply ? props.reply.id : undefined, + renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined, + channelId: props.channel ? props.channel.id : undefined, + poll: poll, + cw: useCw ? cw || '' : undefined, + localOnly: localOnly, + visibility: visibility, + visibleUserIds: visibility == 'specified' ? visibleUsers.map(u => u.id) : undefined, + }; + + if (withHashtags && hashtags && hashtags.trim() !== '') { + const hashtags = hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); + data.text = data.text ? `${data.text} ${hashtags}` : hashtags; + } + + // plugin + if (notePostInterruptors.length > 0) { + for (const interruptor of notePostInterruptors) { + data = await interruptor.handler(JSON.parse(JSON.stringify(data))); + } + } + + posting = true; + os.api('notes/create', data).then(() => { + clear(); + nextTick(() => { + deleteDraft(); + emit('posted'); + if (data.text && data.text != '') { + const hashtags = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); + const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; + localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); + } + posting = false; + }); + }).catch(err => { + posting = false; + os.alert({ + type: 'error', + text: err.message + '\n' + (err as any).id, + }); + }); +} + +function cancel() { + emit('cancel'); +} + +function insertMention() { + os.selectUser().then(user => { + insertTextAtCursor(textareaEl, '@' + Acct.toString(user) + ' '); + }); +} + +async function insertEmoji(ev) { + os.openEmojiPicker(ev.currentTarget || ev.target, {}, textareaEl); +} + +function showActions(ev) { + os.popupMenu(postFormActions.map(action => ({ + text: action.title, + action: () => { + action.handler({ + text: text + }, (key, value) => { + if (key === 'text') { text = value; } + }); + } + })), ev.currentTarget || ev.target); +} + +onMounted(() => { + if (props.autofocus) { + focus(); + + nextTick(() => { + focus(); + }); + } + + // TODO: detach when unmount + new Autocomplete(textareaEl, $$(text)); + new Autocomplete(cwInputEl, $$(cw)); + new Autocomplete(hashtagsInputEl, $$(hashtags)); + + nextTick(() => { + // 書きかけの投稿を復元 + if (!props.share && !props.mention && !props.specified) { + const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[draftKey]; + if (draft) { + text = draft.data.text; + useCw = draft.data.useCw; + cw = draft.data.cw; + visibility = draft.data.visibility; + localOnly = draft.data.localOnly; + files = (draft.data.files || []).filter(e => e); + if (draft.data.poll) { + poll = draft.data.poll; + } + } + } + + // 削除して編集 + if (props.initialNote) { + const init = props.initialNote; + text = init.text ? init.text : ''; + files = init.files; + cw = init.cw; + useCw = init.cw != null; + if (init.poll) { + poll = { + choices: init.poll.choices.map(x => x.text), + multiple: init.poll.multiple, + expiresAt: init.poll.expiresAt, + expiredAfter: init.poll.expiredAfter, + }; + } + visibility = init.visibility; + localOnly = init.localOnly; + quoteId = init.renote ? init.renote.id : null; + } + + nextTick(() => watchForDraft()); + }); }); </script> diff --git a/packages/client/src/scripts/autocomplete.ts b/packages/client/src/scripts/autocomplete.ts index f2d5806484..f4a3a4c0fc 100644 --- a/packages/client/src/scripts/autocomplete.ts +++ b/packages/client/src/scripts/autocomplete.ts @@ -1,4 +1,4 @@ -import { Ref, ref } from 'vue'; +import { nextTick, Ref, ref } from 'vue'; import * as getCaretCoordinates from 'textarea-caret'; import { toASCII } from 'punycode/'; import { popup } from '@/os'; @@ -10,26 +10,23 @@ export class Autocomplete { q: Ref<string | null>; close: Function; } | null; - private textarea: any; - private vm: any; + private textarea: HTMLInputElement | HTMLTextAreaElement; private currentType: string; - private opts: { - model: string; - }; + private textRef: Ref<string>; private opening: boolean; private get text(): string { - return this.vm[this.opts.model]; + return this.textRef.value; } private set text(text: string) { - this.vm[this.opts.model] = text; + this.textRef.value = text; } /** * 対象のテキストエリアを与えてインスタンスを初期化します。 */ - constructor(textarea, vm, opts) { + constructor(textarea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) { //#region BIND this.onInput = this.onInput.bind(this); this.complete = this.complete.bind(this); @@ -38,8 +35,7 @@ export class Autocomplete { this.suggestion = null; this.textarea = textarea; - this.vm = vm; - this.opts = opts; + this.textRef = textRef; this.opening = false; this.attach(); @@ -218,7 +214,7 @@ export class Autocomplete { this.text = `${trimmedBefore}@${acct} ${after}`; // キャレットを戻す - this.vm.$nextTick(() => { + nextTick(() => { this.textarea.focus(); const pos = trimmedBefore.length + (acct.length + 2); this.textarea.setSelectionRange(pos, pos); @@ -234,7 +230,7 @@ export class Autocomplete { this.text = `${trimmedBefore}#${value} ${after}`; // キャレットを戻す - this.vm.$nextTick(() => { + nextTick(() => { this.textarea.focus(); const pos = trimmedBefore.length + (value.length + 2); this.textarea.setSelectionRange(pos, pos); @@ -250,7 +246,7 @@ export class Autocomplete { this.text = trimmedBefore + value + after; // キャレットを戻す - this.vm.$nextTick(() => { + nextTick(() => { this.textarea.focus(); const pos = trimmedBefore.length + value.length; this.textarea.setSelectionRange(pos, pos); @@ -266,7 +262,7 @@ export class Autocomplete { this.text = `${trimmedBefore}$[${value} ]${after}`; // キャレットを戻す - this.vm.$nextTick(() => { + nextTick(() => { this.textarea.focus(); const pos = trimmedBefore.length + (value.length + 3); this.textarea.setSelectionRange(pos, pos); From 9693dfb09df5cca54b866c7632eccadb093a0e2c Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 06:59:35 +0900 Subject: [PATCH 37/81] wip: refactor(client): migrate components to composition api --- .../client/src/components/image-viewer.vue | 36 +++----- .../src/components/img-with-blurhash.vue | 86 +++++++------------ .../client/src/components/signin-dialog.vue | 46 +++++----- .../client/src/components/signup-dialog.vue | 52 +++++------ 4 files changed, 88 insertions(+), 132 deletions(-) diff --git a/packages/client/src/components/image-viewer.vue b/packages/client/src/components/image-viewer.vue index 8584b91a61..c39076df16 100644 --- a/packages/client/src/components/image-viewer.vue +++ b/packages/client/src/components/image-viewer.vue @@ -1,8 +1,8 @@ <template> -<MkModal ref="modal" :z-priority="'middle'" @click="$refs.modal.close()" @closed="$emit('closed')"> +<MkModal ref="modal" :z-priority="'middle'" @click="modal.close()" @closed="emit('closed')"> <div class="xubzgfga"> <header>{{ image.name }}</header> - <img :src="image.url" :alt="image.comment" :title="image.comment" @click="$refs.modal.close()"/> + <img :src="image.url" :alt="image.comment" :title="image.comment" @click="modal.close()"/> <footer> <span>{{ image.type }}</span> <span>{{ bytes(image.size) }}</span> @@ -12,31 +12,23 @@ </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import bytes from '@/filters/bytes'; import number from '@/filters/number'; import MkModal from '@/components/ui/modal.vue'; -export default defineComponent({ - components: { - MkModal, - }, - - props: { - image: { - type: Object, - required: true - }, - }, - - emits: ['closed'], - - methods: { - bytes, - number, - } +const props = withDefaults(defineProps<{ + image: misskey.entities.DriveFile; +}>(), { }); + +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); + +const modal = $ref<InstanceType<typeof MkModal>>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/img-with-blurhash.vue b/packages/client/src/components/img-with-blurhash.vue index a000c699b6..06ad764403 100644 --- a/packages/client/src/components/img-with-blurhash.vue +++ b/packages/client/src/components/img-with-blurhash.vue @@ -5,67 +5,43 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; import { decode } from 'blurhash'; -export default defineComponent({ - props: { - src: { - type: String, - required: false, - default: null - }, - hash: { - type: String, - required: true - }, - alt: { - type: String, - required: false, - default: '', - }, - title: { - type: String, - required: false, - default: null, - }, - size: { - type: Number, - required: false, - default: 64 - }, - cover: { - type: Boolean, - required: false, - default: true, - } - }, +const props = withDefaults(defineProps<{ + src?: string | null; + hash: string; + alt?: string; + title?: string | null; + size?: number; + cover?: boolean; +}>(), { + src: null, + alt: '', + title: null, + size: 64, + cover: true, +}); - data() { - return { - loaded: false, - }; - }, +const canvas = $ref<HTMLCanvasElement>(); +let loaded = $ref(false); - mounted() { - this.draw(); - }, +function draw() { + if (props.hash == null) return; + const pixels = decode(props.hash, props.size, props.size); + const ctx = canvas.getContext('2d'); + const imageData = ctx!.createImageData(props.size, props.size); + imageData.data.set(pixels); + ctx!.putImageData(imageData, 0, 0); +} - methods: { - draw() { - if (this.hash == null) return; - const pixels = decode(this.hash, this.size, this.size); - const ctx = (this.$refs.canvas as HTMLCanvasElement).getContext('2d'); - const imageData = ctx!.createImageData(this.size, this.size); - imageData.data.set(pixels); - ctx!.putImageData(imageData, 0, 0); - }, +function onLoad() { + loaded = true; +} - onLoad() { - this.loaded = true; - } - } +onMounted(() => { + draw(); }); </script> diff --git a/packages/client/src/components/signin-dialog.vue b/packages/client/src/components/signin-dialog.vue index 2edd10f539..5c2048e7b0 100644 --- a/packages/client/src/components/signin-dialog.vue +++ b/packages/client/src/components/signin-dialog.vue @@ -2,8 +2,8 @@ <XModalWindow ref="dialog" :width="370" :height="400" - @close="$refs.dialog.close()" - @closed="$emit('closed')" + @close="dialog.close()" + @closed="emit('closed')" > <template #header>{{ $ts.login }}</template> @@ -11,32 +11,26 @@ </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import MkSignin from './signin.vue'; -export default defineComponent({ - components: { - MkSignin, - XModalWindow, - }, - - props: { - autoSet: { - type: Boolean, - required: false, - default: false, - } - }, - - emits: ['done', 'closed'], - - methods: { - onLogin(res) { - this.$emit('done', res); - this.$refs.dialog.close(); - } - } +const props = withDefaults(defineProps<{ + autoSet?: boolean; +}>(), { + autoSet: false, }); + +const emit = defineEmits<{ + (e: 'done'): void; + (e: 'closed'): void; +}>(); + +const dialog = $ref<InstanceType<typeof XModalWindow>>(); + +function onLogin(res) { + emit('done', res); + dialog.close(); +} </script> diff --git a/packages/client/src/components/signup-dialog.vue b/packages/client/src/components/signup-dialog.vue index 30fe3bf7d3..bda2495ba7 100644 --- a/packages/client/src/components/signup-dialog.vue +++ b/packages/client/src/components/signup-dialog.vue @@ -2,7 +2,7 @@ <XModalWindow ref="dialog" :width="366" :height="500" - @close="$refs.dialog.close()" + @close="dialog.close()" @closed="$emit('closed')" > <template #header>{{ $ts.signup }}</template> @@ -15,36 +15,30 @@ </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import XSignup from './signup.vue'; -export default defineComponent({ - components: { - XSignup, - XModalWindow, - }, - - props: { - autoSet: { - type: Boolean, - required: false, - default: false, - } - }, - - emits: ['done', 'closed'], - - methods: { - onSignup(res) { - this.$emit('done', res); - this.$refs.dialog.close(); - }, - - onSignupEmailPending() { - this.$refs.dialog.close(); - } - } +const props = withDefaults(defineProps<{ + autoSet?: boolean; +}>(), { + autoSet: false, }); + +const emit = defineEmits<{ + (e: 'done'): void; + (e: 'closed'): void; +}>(); + +const dialog = $ref<InstanceType<typeof XModalWindow>>(); + +function onSignup(res) { + emit('done', res); + dialog.close(); +} + +function onSignupEmailPending() { + dialog.close(); +} </script> From 7cbeef21e119b705ef9cbb996b630f55fba9b262 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 07:47:28 +0900 Subject: [PATCH 38/81] wip: refactor(client): migrate components to composition api --- packages/client/src/components/global/a.vue | 207 ++++++++---------- .../client/src/components/global/avatar.vue | 94 ++++---- .../client/src/components/global/loading.vue | 30 +-- .../global/misskey-flavored-markdown.vue | 22 +- .../client/src/components/global/time.vue | 114 +++++----- .../src/components/global/user-name.vue | 21 +- 6 files changed, 214 insertions(+), 274 deletions(-) diff --git a/packages/client/src/components/global/a.vue b/packages/client/src/components/global/a.vue index 77ee7525a4..d3bc2235b8 100644 --- a/packages/client/src/components/global/a.vue +++ b/packages/client/src/components/global/a.vue @@ -4,130 +4,113 @@ </a> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { inject } from 'vue'; import * as os from '@/os'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import { router } from '@/router'; import { url } from '@/config'; -import { popout } from '@/scripts/popout'; -import { ColdDeviceStorage } from '@/store'; +import { popout as popout_ } from '@/scripts/popout'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; -export default defineComponent({ - inject: { - navHook: { - default: null - }, - sideViewHook: { - default: null +const props = withDefaults(defineProps<{ + to: string; + activeClass?: null | string; + behavior?: null | 'window' | 'browser' | 'modalWindow'; +}>(), { + activeClass: null, + behavior: null, +}); + +const navHook = inject('navHook', null); +const sideViewHook = inject('sideViewHook', null); + +const active = $computed(() => { + if (props.activeClass == null) return false; + const resolved = router.resolve(props.to); + if (resolved.path === router.currentRoute.value.path) return true; + if (resolved.name == null) return false; + if (router.currentRoute.value.name == null) return false; + return resolved.name === router.currentRoute.value.name; +}); + +function onContextmenu(ev) { + if (window.getSelection().toString() !== '') return; + os.contextMenu([{ + type: 'label', + text: props.to, + }, { + icon: 'fas fa-window-maximize', + text: i18n.locale.openInWindow, + action: () => { + os.pageWindow(props.to); } - }, - - props: { - to: { - type: String, - required: true, - }, - activeClass: { - type: String, - required: false, - }, - behavior: { - type: String, - required: false, - }, - }, - - computed: { - active() { - if (this.activeClass == null) return false; - const resolved = router.resolve(this.to); - if (resolved.path == this.$route.path) return true; - if (resolved.name == null) return false; - if (this.$route.name == null) return false; - return resolved.name == this.$route.name; + }, sideViewHook ? { + icon: 'fas fa-columns', + text: i18n.locale.openInSideView, + action: () => { + sideViewHook(props.to); } - }, + } : undefined, { + icon: 'fas fa-expand-alt', + text: i18n.locale.showInPage, + action: () => { + router.push(props.to); + } + }, null, { + icon: 'fas fa-external-link-alt', + text: i18n.locale.openInNewTab, + action: () => { + window.open(props.to, '_blank'); + } + }, { + icon: 'fas fa-link', + text: i18n.locale.copyLink, + action: () => { + copyToClipboard(`${url}${props.to}`); + } + }], ev); +} - methods: { - onContextmenu(e) { - if (window.getSelection().toString() !== '') return; - os.contextMenu([{ - type: 'label', - text: this.to, - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(this.to); - } - }, this.sideViewHook ? { - icon: 'fas fa-columns', - text: this.$ts.openInSideView, - action: () => { - this.sideViewHook(this.to); - } - } : undefined, { - icon: 'fas fa-expand-alt', - text: this.$ts.showInPage, - action: () => { - this.$router.push(this.to); - } - }, null, { - icon: 'fas fa-external-link-alt', - text: this.$ts.openInNewTab, - action: () => { - window.open(this.to, '_blank'); - } - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: () => { - copyToClipboard(`${url}${this.to}`); - } - }], e); - }, +function window() { + os.pageWindow(props.to); +} - window() { - os.pageWindow(this.to); - }, +function modalWindow() { + os.modalPageWindow(props.to); +} - modalWindow() { - os.modalPageWindow(this.to); - }, +function popout() { + popout_(props.to); +} - popout() { - popout(this.to); - }, +function nav() { + if (props.behavior === 'browser') { + location.href = props.to; + return; + } - nav() { - if (this.behavior === 'browser') { - location.href = this.to; - return; - } - - if (this.behavior) { - if (this.behavior === 'window') { - return this.window(); - } else if (this.behavior === 'modalWindow') { - return this.modalWindow(); - } - } - - if (this.navHook) { - this.navHook(this.to); - } else { - if (this.$store.state.defaultSideView && this.sideViewHook && this.to !== '/') { - return this.sideViewHook(this.to); - } - - if (this.$router.currentRoute.value.path === this.to) { - window.scroll({ top: 0, behavior: 'smooth' }); - } else { - this.$router.push(this.to); - } - } + if (props.behavior) { + if (props.behavior === 'window') { + return window(); + } else if (props.behavior === 'modalWindow') { + return modalWindow(); } } -}); + + if (navHook) { + navHook(props.to); + } else { + if (defaultStore.state.defaultSideView && sideViewHook && props.to !== '/') { + return sideViewHook(props.to); + } + + if (router.currentRoute.value.path === props.to) { + window.scroll({ top: 0, behavior: 'smooth' }); + } else { + router.push(props.to); + } + } +} </script> diff --git a/packages/client/src/components/global/avatar.vue b/packages/client/src/components/global/avatar.vue index 300e5e079f..9e8979fe56 100644 --- a/packages/client/src/components/global/avatar.vue +++ b/packages/client/src/components/global/avatar.vue @@ -1,74 +1,54 @@ <template> -<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat, square: $store.state.squareAvatars }" :title="acct(user)" @click="onClick"> +<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :title="acct(user)" @click="onClick"> <img class="inner" :src="url" decoding="async"/> <MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/> </span> -<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat, square: $store.state.squareAvatars }" :to="userPage(user)" :title="acct(user)" :target="target"> +<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target"> <img class="inner" :src="url" decoding="async"/> <MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/> </MkA> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, watch } from 'vue'; +import * as misskey from 'misskey-js'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; import { acct, userPage } from '@/filters/user'; import MkUserOnlineIndicator from '@/components/user-online-indicator.vue'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - MkUserOnlineIndicator - }, - props: { - user: { - type: Object, - required: true - }, - target: { - required: false, - default: null - }, - disableLink: { - required: false, - default: false - }, - disablePreview: { - required: false, - default: false - }, - showIndicator: { - required: false, - default: false - } - }, - emits: ['click'], - computed: { - cat(): boolean { - return this.user.isCat; - }, - url(): string { - return this.$store.state.disableShowingAnimatedImages - ? getStaticImageUrl(this.user.avatarUrl) - : this.user.avatarUrl; - }, - }, - watch: { - 'user.avatarBlurhash'() { - if (this.$el == null) return; - this.$el.style.color = extractAvgColorFromBlurhash(this.user.avatarBlurhash); - } - }, - mounted() { - this.$el.style.color = extractAvgColorFromBlurhash(this.user.avatarBlurhash); - }, - methods: { - onClick(e) { - this.$emit('click', e); - }, - acct, - userPage - } +const props = withDefaults(defineProps<{ + user: misskey.entities.User; + target?: string | null; + disableLink?: boolean; + disablePreview?: boolean; + showIndicator?: boolean; +}>(), { + target: null, + disableLink: false, + disablePreview: false, + showIndicator: false, +}); + +const emit = defineEmits<{ + (e: 'click', ev: MouseEvent): void; +}>(); + +const url = defaultStore.state.disableShowingAnimatedImages + ? getStaticImageUrl(props.user.avatarUrl) + : props.user.avatarUrl; + +function onClick(ev: MouseEvent) { + emit('click', ev); +} + +let color = $ref(); + +watch(() => props.user.avatarBlurhash, () => { + color = extractAvgColorFromBlurhash(props.user.avatarBlurhash); +}, { + immediate: true, }); </script> diff --git a/packages/client/src/components/global/loading.vue b/packages/client/src/components/global/loading.vue index 7bde53c12e..43ea1395ed 100644 --- a/packages/client/src/components/global/loading.vue +++ b/packages/client/src/components/global/loading.vue @@ -4,27 +4,17 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - props: { - inline: { - type: Boolean, - required: false, - default: false - }, - colored: { - type: Boolean, - required: false, - default: true - }, - mini: { - type: Boolean, - required: false, - default: false - }, - } +const props = withDefaults(defineProps<{ + inline?: boolean; + colored?: boolean; + mini?: boolean; +}>(), { + inline: false, + colored: true, + mini: false, }); </script> diff --git a/packages/client/src/components/global/misskey-flavored-markdown.vue b/packages/client/src/components/global/misskey-flavored-markdown.vue index ab20404909..243d8614ba 100644 --- a/packages/client/src/components/global/misskey-flavored-markdown.vue +++ b/packages/client/src/components/global/misskey-flavored-markdown.vue @@ -1,15 +1,23 @@ <template> -<mfm-core v-bind="$attrs" class="havbbuyv" :class="{ nowrap: $attrs['nowrap'] }"/> +<MfmCore :text="text" :plain="plain" :nowrap="nowrap" :author="author" :customEmojis="customEmojis" :isNote="isNote" class="havbbuyv" :class="{ nowrap }"/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MfmCore from '@/components/mfm'; -export default defineComponent({ - components: { - MfmCore - } +const props = withDefaults(defineProps<{ + text: string; + plain?: boolean; + nowrap?: boolean; + author?: any; + customEmojis?: any; + isNote?: boolean; +}>(), { + plain: false, + nowrap: false, + author: null, + isNote: true, }); </script> diff --git a/packages/client/src/components/global/time.vue b/packages/client/src/components/global/time.vue index 6a330a2307..d2788264c5 100644 --- a/packages/client/src/components/global/time.vue +++ b/packages/client/src/components/global/time.vue @@ -1,73 +1,57 @@ <template> <time :title="absolute"> - <template v-if="mode == 'relative'">{{ relative }}</template> - <template v-else-if="mode == 'absolute'">{{ absolute }}</template> - <template v-else-if="mode == 'detail'">{{ absolute }} ({{ relative }})</template> + <template v-if="mode === 'relative'">{{ relative }}</template> + <template v-else-if="mode === 'absolute'">{{ absolute }}</template> + <template v-else-if="mode === 'detail'">{{ absolute }} ({{ relative }})</template> </time> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onUnmounted } from 'vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - time: { - type: [Date, String], - required: true - }, - mode: { - type: String, - default: 'relative' - } - }, - data() { - return { - tickId: null, - now: new Date() - }; - }, - computed: { - _time(): Date { - return typeof this.time == 'string' ? new Date(this.time) : this.time; - }, - absolute(): string { - return this._time.toLocaleString(); - }, - relative(): string { - const time = this._time; - const ago = (this.now.getTime() - time.getTime()) / 1000/*ms*/; - return ( - ago >= 31536000 ? this.$t('_ago.yearsAgo', { n: (~~(ago / 31536000)).toString() }) : - ago >= 2592000 ? this.$t('_ago.monthsAgo', { n: (~~(ago / 2592000)).toString() }) : - ago >= 604800 ? this.$t('_ago.weeksAgo', { n: (~~(ago / 604800)).toString() }) : - ago >= 86400 ? this.$t('_ago.daysAgo', { n: (~~(ago / 86400)).toString() }) : - ago >= 3600 ? this.$t('_ago.hoursAgo', { n: (~~(ago / 3600)).toString() }) : - ago >= 60 ? this.$t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : - ago >= 10 ? this.$t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : - ago >= -1 ? this.$ts._ago.justNow : - ago < -1 ? this.$ts._ago.future : - this.$ts._ago.unknown); - } - }, - created() { - if (this.mode == 'relative' || this.mode == 'detail') { - this.tickId = window.requestAnimationFrame(this.tick); - } - }, - unmounted() { - if (this.mode === 'relative' || this.mode === 'detail') { - window.clearTimeout(this.tickId); - } - }, - methods: { - tick() { - // TODO: パフォーマンス向上のため、このコンポーネントが画面内に表示されている場合のみ更新する - this.now = new Date(); - - this.tickId = setTimeout(() => { - window.requestAnimationFrame(this.tick); - }, 10000); - } - } +const props = withDefaults(defineProps<{ + time: Date | string; + mode?: 'relative' | 'absolute' | 'detail'; +}>(), { + mode: 'relative', }); + +const _time = typeof props.time == 'string' ? new Date(props.time) : props.time; +const absolute = _time.toLocaleString(); + +let now = $ref(new Date()); +const relative = $computed(() => { + const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/; + return ( + ago >= 31536000 ? i18n.t('_ago.yearsAgo', { n: (~~(ago / 31536000)).toString() }) : + ago >= 2592000 ? i18n.t('_ago.monthsAgo', { n: (~~(ago / 2592000)).toString() }) : + ago >= 604800 ? i18n.t('_ago.weeksAgo', { n: (~~(ago / 604800)).toString() }) : + ago >= 86400 ? i18n.t('_ago.daysAgo', { n: (~~(ago / 86400)).toString() }) : + ago >= 3600 ? i18n.t('_ago.hoursAgo', { n: (~~(ago / 3600)).toString() }) : + ago >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : + ago >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : + ago >= -1 ? i18n.locale._ago.justNow : + ago < -1 ? i18n.locale._ago.future : + i18n.locale._ago.unknown); +}); + +function tick() { + // TODO: パフォーマンス向上のため、このコンポーネントが画面内に表示されている場合のみ更新する + now = new Date(); + + tickId = window.setTimeout(() => { + window.requestAnimationFrame(tick); + }, 10000); +} + +let tickId: number; + +if (props.mode === 'relative' || props.mode === 'detail') { + tickId = window.requestAnimationFrame(tick); + + onUnmounted(() => { + window.clearTimeout(tickId); + }); +} </script> diff --git a/packages/client/src/components/global/user-name.vue b/packages/client/src/components/global/user-name.vue index bc93a8ea30..090de3df30 100644 --- a/packages/client/src/components/global/user-name.vue +++ b/packages/client/src/components/global/user-name.vue @@ -2,19 +2,14 @@ <Mfm :text="user.name || user.username" :plain="true" :nowrap="nowrap" :custom-emojis="user.emojis"/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; -export default defineComponent({ - props: { - user: { - type: Object, - required: true - }, - nowrap: { - type: Boolean, - default: true - }, - } +const props = withDefaults(defineProps<{ + user: misskey.entities.User; + nowrap?: boolean; +}>(), { + nowrap: true, }); </script> From bf5145064764c5459a9a41edd4620138cdd83784 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 07:55:19 +0900 Subject: [PATCH 39/81] wip: refactor(client): migrate components to composition api --- packages/client/src/pages/_error_.vue | 89 ++++++++++++--------------- 1 file changed, 41 insertions(+), 48 deletions(-) diff --git a/packages/client/src/pages/_error_.vue b/packages/client/src/pages/_error_.vue index 2f8f08b5cf..7540995707 100644 --- a/packages/client/src/pages/_error_.vue +++ b/packages/client/src/pages/_error_.vue @@ -1,68 +1,61 @@ <template> -<MkLoading v-if="!loaded" /> +<MkLoading v-if="!loaded"/> <transition :name="$store.state.animation ? 'zoom' : ''" appear> <div v-show="loaded" class="mjndxjch"> <img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> - <p><b><i class="fas fa-exclamation-triangle"></i> {{ $ts.pageLoadError }}</b></p> - <p v-if="version === meta.version">{{ $ts.pageLoadErrorDescription }}</p> - <p v-else-if="serverIsDead">{{ $ts.serverIsDead }}</p> + <p><b><i class="fas fa-exclamation-triangle"></i> {{ i18n.locale.pageLoadError }}</b></p> + <p v-if="meta && (version === meta.version)">{{ i18n.locale.pageLoadErrorDescription }}</p> + <p v-else-if="serverIsDead">{{ i18n.locale.serverIsDead }}</p> <template v-else> - <p>{{ $ts.newVersionOfClientAvailable }}</p> - <p>{{ $ts.youShouldUpgradeClient }}</p> - <MkButton class="button primary" @click="reload">{{ $ts.reload }}</MkButton> + <p>{{ i18n.locale.newVersionOfClientAvailable }}</p> + <p>{{ i18n.locale.youShouldUpgradeClient }}</p> + <MkButton class="button primary" @click="reload">{{ i18n.locale.reload }}</MkButton> </template> - <p><MkA to="/docs/general/troubleshooting" class="_link">{{ $ts.troubleshooting }}</MkA></p> + <p><MkA to="/docs/general/troubleshooting" class="_link">{{ i18n.locale.troubleshooting }}</MkA></p> <p v-if="error" class="error">ERROR: {{ error }}</p> </div> </transition> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import MkButton from '@/components/ui/button.vue'; import * as symbols from '@/symbols'; import { version } from '@/config'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - }, - props: { - error: { - required: false, - } - }, - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.error, - icon: 'fas fa-exclamation-triangle' - }, - loaded: false, - serverIsDead: false, - meta: {} as any, - version, - }; - }, - created() { - os.api('meta', { - detail: false - }).then(meta => { - this.loaded = true; - this.serverIsDead = false; - this.meta = meta; - localStorage.setItem('v', meta.version); - }, () => { - this.loaded = true; - this.serverIsDead = true; - }); - }, - methods: { - reload() { - unisonReload(); - }, +const props = withDefaults(defineProps<{ + error?: Error; +}>(), { +}); + +let loaded = $ref(false); +let serverIsDead = $ref(false); +let meta = $ref<misskey.entities.LiteInstanceMetadata | null>(null); + +os.api('meta', { + detail: false, +}).then(res => { + loaded = true; + serverIsDead = false; + meta = res; + localStorage.setItem('v', res.version); +}, () => { + loaded = true; + serverIsDead = true; +}); + +function reload() { + unisonReload(); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.error, + icon: 'fas fa-exclamation-triangle', }, }); </script> From ed5c918d707a2f37b58128d11e98a15dcd3abb98 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 08:24:53 +0900 Subject: [PATCH 40/81] wip: refactor(client): migrate components to composition api --- packages/client/src/components/global/a.vue | 7 +- .../client/src/components/media-banner.vue | 46 +++--- .../client/src/components/url-preview.vue | 156 +++++++----------- 3 files changed, 86 insertions(+), 123 deletions(-) diff --git a/packages/client/src/components/global/a.vue b/packages/client/src/components/global/a.vue index d3bc2235b8..cf7385ca22 100644 --- a/packages/client/src/components/global/a.vue +++ b/packages/client/src/components/global/a.vue @@ -36,7 +36,8 @@ const active = $computed(() => { }); function onContextmenu(ev) { - if (window.getSelection().toString() !== '') return; + const selection = window.getSelection(); + if (selection && selection.toString() !== '') return; os.contextMenu([{ type: 'label', text: props.to, @@ -73,7 +74,7 @@ function onContextmenu(ev) { }], ev); } -function window() { +function openWindow() { os.pageWindow(props.to); } @@ -93,7 +94,7 @@ function nav() { if (props.behavior) { if (props.behavior === 'window') { - return window(); + return openWindow(); } else if (props.behavior === 'modalWindow') { return modalWindow(); } diff --git a/packages/client/src/components/media-banner.vue b/packages/client/src/components/media-banner.vue index 9dbfe3d0c6..5093f11e97 100644 --- a/packages/client/src/components/media-banner.vue +++ b/packages/client/src/components/media-banner.vue @@ -6,7 +6,7 @@ <span>{{ $ts.clickToShow }}</span> </div> <div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" class="audio"> - <audio ref="audio" + <audio ref="audioEl" class="audio" :src="media.url" :title="media.name" @@ -25,34 +25,26 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onMounted } from 'vue'; +import * as misskey from 'misskey-js'; import { ColdDeviceStorage } from '@/store'; -export default defineComponent({ - props: { - media: { - type: Object, - required: true - } - }, - data() { - return { - hide: true, - }; - }, - mounted() { - const audioTag = this.$refs.audio as HTMLAudioElement; - if (audioTag) audioTag.volume = ColdDeviceStorage.get('mediaVolume'); - }, - methods: { - volumechange() { - const audioTag = this.$refs.audio as HTMLAudioElement; - ColdDeviceStorage.set('mediaVolume', audioTag.volume); - }, - }, -}) +const props = withDefaults(defineProps<{ + media: misskey.entities.DriveFile; +}>(), { +}); + +const audioEl = $ref<HTMLAudioElement | null>(); +let hide = $ref(true); + +function volumechange() { + if (audioEl) ColdDeviceStorage.set('mediaVolume', audioEl.volume); +} + +onMounted(() => { + if (audioEl) audioEl.volume = ColdDeviceStorage.get('mediaVolume'); +}); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/url-preview.vue b/packages/client/src/components/url-preview.vue index dff74800ed..bf3b358797 100644 --- a/packages/client/src/components/url-preview.vue +++ b/packages/client/src/components/url-preview.vue @@ -4,7 +4,7 @@ <iframe :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen /> </div> <div v-else-if="tweetId && tweetExpanded" ref="twitter" class="twitter"> - <iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', left: `${tweetLeft}px`, width: `${tweetLeft < 0 ? 'auto' : '100%'}`, height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${$store.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"></iframe> + <iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${$store.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"></iframe> </div> <div v-else v-size="{ max: [400, 350] }" class="mk-url-preview"> <transition name="zoom" mode="out-in"> @@ -32,110 +32,80 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted } from 'vue'; import { url as local, lang } from '@/config'; -import * as os from '@/os'; -export default defineComponent({ - props: { - url: { - type: String, - require: true - }, +const props = withDefaults(defineProps<{ + url: string; + detail?: boolean; + compact?: boolean; +}>(), { + detail: false, + compact: false, +}); - detail: { - type: Boolean, - required: false, - default: false - }, +const self = props.url.startsWith(local); +const attr = self ? 'to' : 'href'; +const target = self ? null : '_blank'; +let fetching = $ref(true); +let title = $ref<string | null>(null); +let description = $ref<string | null>(null); +let thumbnail = $ref<string | null>(null); +let icon = $ref<string | null>(null); +let sitename = $ref<string | null>(null); +let player = $ref({ + url: null, + width: null, + height: null +}); +let playerEnabled = $ref(false); +let tweetId = $ref<string | null>(null); +let tweetExpanded = $ref(props.detail); +const embedId = `embed${Math.random().toString().replace(/\D/,'')}`; +let tweetHeight = $ref(150); - compact: { - type: Boolean, - required: false, - default: false - }, - }, +const requestUrl = new URL(props.url); - data() { - const self = this.url.startsWith(local); - return { - local, - fetching: true, - title: null, - description: null, - thumbnail: null, - icon: null, - sitename: null, - player: { - url: null, - width: null, - height: null - }, - tweetId: null, - tweetExpanded: this.detail, - embedId: `embed${Math.random().toString().replace(/\D/,'')}`, - tweetHeight: 150, - tweetLeft: 0, - playerEnabled: false, - self: self, - attr: self ? 'to' : 'href', - target: self ? null : '_blank', - }; - }, +if (requestUrl.hostname == 'twitter.com') { + const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/); + if (m) tweetId = m[1]; +} - created() { - const requestUrl = new URL(this.url); +if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) { + requestUrl.hostname = 'www.youtube.com'; +} - if (requestUrl.hostname == 'twitter.com') { - const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/); - if (m) this.tweetId = m[1]; - } +const requestLang = (lang || 'ja-JP').replace('ja-KS', 'ja-JP'); - if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) { - requestUrl.hostname = 'www.youtube.com'; - } +requestUrl.hash = ''; - const requestLang = (lang || 'ja-JP').replace('ja-KS', 'ja-JP'); +fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => { + res.json().then(info => { + if (info.url == null) return; + title = info.title; + description = info.description; + thumbnail = info.thumbnail; + icon = info.icon; + sitename = info.sitename; + fetching = false; + player = info.player; + }) +}); - requestUrl.hash = ''; +function adjustTweetHeight(message: any) { + if (message.origin !== 'https://platform.twitter.com') return; + const embed = message.data?.['twttr.embed']; + if (embed?.method !== 'twttr.private.resize') return; + if (embed?.id !== embedId) return; + const height = embed?.params[0]?.height; + if (height) tweetHeight = height; +} - fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => { - res.json().then(info => { - if (info.url == null) return; - this.title = info.title; - this.description = info.description; - this.thumbnail = info.thumbnail; - this.icon = info.icon; - this.sitename = info.sitename; - this.fetching = false; - this.player = info.player; - }) - }); +(window as any).addEventListener('message', adjustTweetHeight); - (window as any).addEventListener('message', this.adjustTweetHeight); - }, - - mounted() { - // 300pxないと絶対右にはみ出るので左に移動してしまう - const areaWidth = (this.$el as any)?.clientWidth; - if (areaWidth && areaWidth < 300) this.tweetLeft = areaWidth - 241; - }, - - beforeUnmount() { - (window as any).removeEventListener('message', this.adjustTweetHeight); - }, - - methods: { - adjustTweetHeight(message: any) { - if (message.origin !== 'https://platform.twitter.com') return; - const embed = message.data?.['twttr.embed']; - if (embed?.method !== 'twttr.private.resize') return; - if (embed?.id !== this.embedId) return; - const height = embed?.params[0]?.height; - if (height) this.tweetHeight = height; - }, - }, +onUnmounted(() => { + (window as any).removeEventListener('message', adjustTweetHeight); }); </script> From 133b5c6391dd7dd63494dbbab24c84414401eb88 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 08:38:55 +0900 Subject: [PATCH 41/81] wip: refactor(client): migrate components to composition api --- packages/client/src/components/link.vue | 84 +++++-------------- .../src/ui/_common_/stream-indicator.vue | 51 +++++------ 2 files changed, 39 insertions(+), 96 deletions(-) diff --git a/packages/client/src/components/link.vue b/packages/client/src/components/link.vue index 8b8cde6510..317c931cec 100644 --- a/packages/client/src/components/link.vue +++ b/packages/client/src/components/link.vue @@ -1,82 +1,36 @@ <template> -<component :is="self ? 'MkA' : 'a'" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" +<component :is="self ? 'MkA' : 'a'" ref="el" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" :title="url" - @mouseover="onMouseover" - @mouseleave="onMouseleave" > <slot></slot> <i v-if="target === '_blank'" class="fas fa-external-link-square-alt icon"></i> </component> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import { url as local } from '@/config'; -import { isTouchUsing } from '@/scripts/touch'; +import { useTooltip } from '@/scripts/use-tooltip'; import * as os from '@/os'; -export default defineComponent({ - props: { - url: { - type: String, - required: true, - }, - rel: { - type: String, - required: false, - } - }, - data() { - const self = this.url.startsWith(local); - return { - local, - self: self, - attr: self ? 'to' : 'href', - target: self ? null : '_blank', - showTimer: null, - hideTimer: null, - checkTimer: null, - close: null, - }; - }, - methods: { - async showPreview() { - if (!document.body.contains(this.$el)) return; - if (this.close) return; +const props = withDefaults(defineProps<{ + url: string; + rel?: null | string; +}>(), { +}); - const { dispose } = await os.popup(import('@/components/url-preview-popup.vue'), { - url: this.url, - source: this.$el - }); +const self = props.url.startsWith(local); +const attr = self ? 'to' : 'href'; +const target = self ? null : '_blank'; - this.close = () => { - dispose(); - }; +const el = $ref(); - this.checkTimer = setInterval(() => { - if (!document.body.contains(this.$el)) this.closePreview(); - }, 1000); - }, - closePreview() { - if (this.close) { - clearInterval(this.checkTimer); - this.close(); - this.close = null; - } - }, - onMouseover() { - if (isTouchUsing) return; - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.showTimer = setTimeout(this.showPreview, 500); - }, - onMouseleave() { - if (isTouchUsing) return; - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.hideTimer = setTimeout(this.closePreview, 500); - } - } +useTooltip($$(el), (showing) => { + os.popup(import('@/components/url-preview-popup.vue'), { + showing, + url: props.url, + source: el, + }, {}, 'closed'); }); </script> diff --git a/packages/client/src/ui/_common_/stream-indicator.vue b/packages/client/src/ui/_common_/stream-indicator.vue index c75c6d1c0a..5e811e1b88 100644 --- a/packages/client/src/ui/_common_/stream-indicator.vue +++ b/packages/client/src/ui/_common_/stream-indicator.vue @@ -8,39 +8,28 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onUnmounted } from 'vue'; import { stream } from '@/stream'; -export default defineComponent({ - data() { - return { - hasDisconnected: false, - } - }, - computed: { - stream() { - return stream; - }, - }, - created() { - stream.on('_disconnected_', this.onDisconnected); - }, - beforeUnmount() { - stream.off('_disconnected_', this.onDisconnected); - }, - methods: { - onDisconnected() { - this.hasDisconnected = true; - }, - resetDisconnected() { - this.hasDisconnected = false; - }, - reload() { - location.reload(); - }, - } +let hasDisconnected = $ref(false); + +function onDisconnected() { + hasDisconnected = true; +} + +function resetDisconnected() { + hasDisconnected = false; +} + +function reload() { + location.reload(); +} + +stream.on('_disconnected_', onDisconnected); + +onUnmounted(() => { + stream.off('_disconnected_', onDisconnected); }); </script> From 689e75e10a333411366ab1c48dbd5f70ebee8f42 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 08:49:27 +0900 Subject: [PATCH 42/81] wip: refactor(client): migrate components to composition api --- packages/client/src/components/post-form.vue | 8 +- .../src/components/visibility-picker.vue | 81 +++++++++---------- 2 files changed, 40 insertions(+), 49 deletions(-) diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue index 7b2f79e389..3fcb1d906b 100644 --- a/packages/client/src/components/post-form.vue +++ b/packages/client/src/components/post-form.vue @@ -384,14 +384,14 @@ function setVisibility() { currentLocalOnly: localOnly, src: visibilityButton, }, { - changeVisibility: visibility => { - visibility = visibility; + changeVisibility: v => { + visibility = v; if (defaultStore.state.rememberNoteVisibility) { defaultStore.set('visibility', visibility); } }, - changeLocalOnly: localOnly => { - localOnly = localOnly; + changeLocalOnly: v => { + localOnly = v; if (defaultStore.state.rememberNoteVisibility) { defaultStore.set('localOnly', localOnly); } diff --git a/packages/client/src/components/visibility-picker.vue b/packages/client/src/components/visibility-picker.vue index 4200f4354e..4b20063a51 100644 --- a/packages/client/src/components/visibility-picker.vue +++ b/packages/client/src/components/visibility-picker.vue @@ -1,28 +1,28 @@ <template> -<MkModal ref="modal" :z-priority="'high'" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')"> +<MkModal ref="modal" :z-priority="'high'" :src="src" @click="modal.close()" @closed="emit('closed')"> <div class="gqyayizv _popup"> - <button key="public" class="_button" :class="{ active: v == 'public' }" data-index="1" @click="choose('public')"> + <button key="public" class="_button" :class="{ active: v === 'public' }" data-index="1" @click="choose('public')"> <div><i class="fas fa-globe"></i></div> <div> <span>{{ $ts._visibility.public }}</span> <span>{{ $ts._visibility.publicDescription }}</span> </div> </button> - <button key="home" class="_button" :class="{ active: v == 'home' }" data-index="2" @click="choose('home')"> + <button key="home" class="_button" :class="{ active: v === 'home' }" data-index="2" @click="choose('home')"> <div><i class="fas fa-home"></i></div> <div> <span>{{ $ts._visibility.home }}</span> <span>{{ $ts._visibility.homeDescription }}</span> </div> </button> - <button key="followers" class="_button" :class="{ active: v == 'followers' }" data-index="3" @click="choose('followers')"> + <button key="followers" class="_button" :class="{ active: v === 'followers' }" data-index="3" @click="choose('followers')"> <div><i class="fas fa-unlock"></i></div> <div> <span>{{ $ts._visibility.followers }}</span> <span>{{ $ts._visibility.followersDescription }}</span> </div> </button> - <button key="specified" :disabled="localOnly" class="_button" :class="{ active: v == 'specified' }" data-index="4" @click="choose('specified')"> + <button key="specified" :disabled="localOnly" class="_button" :class="{ active: v === 'specified' }" data-index="4" @click="choose('specified')"> <div><i class="fas fa-envelope"></i></div> <div> <span>{{ $ts._visibility.specified }}</span> @@ -42,49 +42,40 @@ </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { nextTick, watch } from 'vue'; +import * as misskey from 'misskey-js'; import MkModal from '@/components/ui/modal.vue'; -export default defineComponent({ - components: { - MkModal, - }, - props: { - currentVisibility: { - type: String, - required: true - }, - currentLocalOnly: { - type: Boolean, - required: true - }, - src: { - required: false - }, - }, - emits: ['change-visibility', 'change-local-only', 'closed'], - data() { - return { - v: this.currentVisibility, - localOnly: this.currentLocalOnly, - } - }, - watch: { - localOnly() { - this.$emit('change-local-only', this.localOnly); - } - }, - methods: { - choose(visibility) { - this.v = visibility; - this.$emit('change-visibility', visibility); - this.$nextTick(() => { - this.$refs.modal.close(); - }); - }, - } +const modal = $ref<InstanceType<typeof MkModal>>(); + +const props = withDefaults(defineProps<{ + currentVisibility: typeof misskey.noteVisibilities[number]; + currentLocalOnly: boolean; + src?: HTMLElement; +}>(), { }); + +const emit = defineEmits<{ + (e: 'changeVisibility', v: typeof misskey.noteVisibilities[number]): void; + (e: 'changeLocalOnly', v: boolean): void; + (e: 'closed'): void; +}>(); + +let v = $ref(props.currentVisibility); +let localOnly = $ref(props.currentLocalOnly); + +watch($$(localOnly), () => { + emit('changeLocalOnly', localOnly); +}); + +function choose(visibility: typeof misskey.noteVisibilities[number]): void { + v = visibility; + emit('changeVisibility', visibility); + nextTick(() => { + modal.close(); + }); +} </script> <style lang="scss" scoped> From b312846ff64fe072b6a3eed6bd9eb430a6e9fe21 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 08:50:55 +0900 Subject: [PATCH 43/81] clean up --- packages/client/package.json | 1 - packages/client/yarn.lock | 5 ----- 2 files changed, 6 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index e790998c36..e105ffcae7 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -21,7 +21,6 @@ "@types/katex": "0.11.1", "@types/matter-js": "0.17.6", "@types/mocha": "8.2.3", - "@types/node": "16.11.12", "@types/oauth": "0.9.1", "@types/parse5": "6.0.3", "@types/punycode": "2.1.0", diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index c18910edfb..0d08448ded 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -415,11 +415,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50" integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA== -"@types/node@16.11.12": - version "16.11.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.12.tgz#ac7fb693ac587ee182c3780c26eb65546a1a3c10" - integrity sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw== - "@types/node@^14.11.8", "@types/node@^14.14.31": version "14.17.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.9.tgz#b97c057e6138adb7b720df2bd0264b03c9f504fd" From 8322c908340fa720d4f1afc781ef34e9d16ae6b0 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 10:14:14 +0900 Subject: [PATCH 44/81] refactor(client): specify global scope --- .../client/src/components/analog-clock.vue | 2 +- packages/client/src/components/captcha.vue | 2 +- packages/client/src/components/form/input.vue | 4 +-- .../client/src/components/form/select.vue | 4 +-- .../components/global/sticky-container.vue | 2 +- packages/client/src/components/mini-chart.vue | 4 +-- .../src/components/notification-toast.vue | 2 +- packages/client/src/components/ripple.vue | 2 +- packages/client/src/components/toast.vue | 2 +- packages/client/src/components/ui/button.vue | 6 ++-- packages/client/src/components/ui/modal.vue | 2 +- packages/client/src/directives/anim.ts | 2 +- packages/client/src/directives/tooltip.ts | 18 +++++------ .../client/src/directives/user-preview.ts | 30 +++++++++---------- packages/client/src/os.ts | 6 ++-- .../src/pages/messaging/messaging-room.vue | 6 ++-- packages/client/src/pages/share.vue | 2 +- packages/client/src/pizzax.ts | 2 +- packages/client/src/router.ts | 6 ++-- packages/client/src/scripts/physics.ts | 4 +-- packages/client/src/scripts/theme.ts | 4 +-- packages/client/src/ui/deck/column.vue | 2 +- packages/client/src/widgets/calendar.vue | 4 +-- packages/client/src/widgets/digital-clock.vue | 6 ++-- packages/client/src/widgets/federation.vue | 4 +-- packages/client/src/widgets/memo.vue | 4 +-- packages/client/src/widgets/online-users.vue | 4 +-- packages/client/src/widgets/rss.vue | 4 +-- packages/client/src/widgets/slideshow.vue | 6 ++-- packages/client/src/widgets/trends.vue | 4 +-- 30 files changed, 75 insertions(+), 75 deletions(-) diff --git a/packages/client/src/components/analog-clock.vue b/packages/client/src/components/analog-clock.vue index 9ca511b6e9..59b8e97304 100644 --- a/packages/client/src/components/analog-clock.vue +++ b/packages/client/src/components/analog-clock.vue @@ -90,7 +90,7 @@ onMounted(() => { const update = () => { if (enabled.value) { tick(); - setTimeout(update, 1000); + window.setTimeout(update, 1000); } }; update(); diff --git a/packages/client/src/components/captcha.vue b/packages/client/src/components/captcha.vue index 2a4181255f..7fe499dc86 100644 --- a/packages/client/src/components/captcha.vue +++ b/packages/client/src/components/captcha.vue @@ -90,7 +90,7 @@ function requestRender() { 'error-callback': callback, }); } else { - setTimeout(requestRender, 1); + window.setTimeout(requestRender, 1); } } diff --git a/packages/client/src/components/form/input.vue b/packages/client/src/components/form/input.vue index 3533f4f27b..7165671af3 100644 --- a/packages/client/src/components/form/input.vue +++ b/packages/client/src/components/form/input.vue @@ -167,7 +167,7 @@ export default defineComponent({ // このコンポーネントが作成された時、非表示状態である場合がある // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する - const clock = setInterval(() => { + const clock = window.setInterval(() => { if (prefixEl.value) { if (prefixEl.value.offsetWidth) { inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; @@ -181,7 +181,7 @@ export default defineComponent({ }, 100); onUnmounted(() => { - clearInterval(clock); + window.clearInterval(clock); }); }); }); diff --git a/packages/client/src/components/form/select.vue b/packages/client/src/components/form/select.vue index afc53ca9c8..87196027a8 100644 --- a/packages/client/src/components/form/select.vue +++ b/packages/client/src/components/form/select.vue @@ -117,7 +117,7 @@ export default defineComponent({ // このコンポーネントが作成された時、非表示状態である場合がある // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する - const clock = setInterval(() => { + const clock = window.setInterval(() => { if (prefixEl.value) { if (prefixEl.value.offsetWidth) { inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; @@ -131,7 +131,7 @@ export default defineComponent({ }, 100); onUnmounted(() => { - clearInterval(clock); + window.clearInterval(clock); }); }); }); diff --git a/packages/client/src/components/global/sticky-container.vue b/packages/client/src/components/global/sticky-container.vue index 859b2c1d73..89d397f082 100644 --- a/packages/client/src/components/global/sticky-container.vue +++ b/packages/client/src/components/global/sticky-container.vue @@ -45,7 +45,7 @@ export default defineComponent({ calc(); const observer = new MutationObserver(() => { - setTimeout(() => { + window.setTimeout(() => { calc(); }, 100); }); diff --git a/packages/client/src/components/mini-chart.vue b/packages/client/src/components/mini-chart.vue index 2eb9ae8cbe..8c74eae876 100644 --- a/packages/client/src/components/mini-chart.vue +++ b/packages/client/src/components/mini-chart.vue @@ -63,10 +63,10 @@ export default defineComponent({ this.draw(); // Vueが何故かWatchを発動させない場合があるので - this.clock = setInterval(this.draw, 1000); + this.clock = window.setInterval(this.draw, 1000); }, beforeUnmount() { - clearInterval(this.clock); + window.clearInterval(this.clock); }, methods: { draw() { diff --git a/packages/client/src/components/notification-toast.vue b/packages/client/src/components/notification-toast.vue index 5449409ccc..fbd8467a6e 100644 --- a/packages/client/src/components/notification-toast.vue +++ b/packages/client/src/components/notification-toast.vue @@ -29,7 +29,7 @@ export default defineComponent({ }; }, mounted() { - setTimeout(() => { + window.setTimeout(() => { this.showing = false; }, 6000); } diff --git a/packages/client/src/components/ripple.vue b/packages/client/src/components/ripple.vue index 272eacbc6e..401e78e304 100644 --- a/packages/client/src/components/ripple.vue +++ b/packages/client/src/components/ripple.vue @@ -94,7 +94,7 @@ export default defineComponent({ } onMounted(() => { - setTimeout(() => { + window.setTimeout(() => { context.emit('end'); }, 1100); }); diff --git a/packages/client/src/components/toast.vue b/packages/client/src/components/toast.vue index 869182d8e1..031aa45633 100644 --- a/packages/client/src/components/toast.vue +++ b/packages/client/src/components/toast.vue @@ -26,7 +26,7 @@ const showing = ref(true); const zIndex = os.claimZIndex('high'); onMounted(() => { - setTimeout(() => { + window.setTimeout(() => { showing.value = false; }, 4000); }); diff --git a/packages/client/src/components/ui/button.vue b/packages/client/src/components/ui/button.vue index 804a2e2720..c7b6c8ba96 100644 --- a/packages/client/src/components/ui/button.vue +++ b/packages/client/src/components/ui/button.vue @@ -117,14 +117,14 @@ export default defineComponent({ const scale = calcCircleScale(e.target.clientWidth, e.target.clientHeight, circleCenterX, circleCenterY); - setTimeout(() => { + window.setTimeout(() => { ripple.style.transform = 'scale(' + (scale / 2) + ')'; }, 1); - setTimeout(() => { + window.setTimeout(() => { ripple.style.transition = 'all 1s ease'; ripple.style.opacity = '0'; }, 1000); - setTimeout(() => { + window.setTimeout(() => { if (this.$refs.ripples) this.$refs.ripples.removeChild(ripple); }, 2000); } diff --git a/packages/client/src/components/ui/modal.vue b/packages/client/src/components/ui/modal.vue index 3e2e59b27c..c691c8c6d0 100644 --- a/packages/client/src/components/ui/modal.vue +++ b/packages/client/src/components/ui/modal.vue @@ -211,7 +211,7 @@ export default defineComponent({ contentClicking = true; window.addEventListener('mouseup', e => { // click イベントより先に mouseup イベントが発生するかもしれないのでちょっと待つ - setTimeout(() => { + window.setTimeout(() => { contentClicking = false; }, 100); }, { passive: true, once: true }); diff --git a/packages/client/src/directives/anim.ts b/packages/client/src/directives/anim.ts index 1ceef984d8..04e1c6a404 100644 --- a/packages/client/src/directives/anim.ts +++ b/packages/client/src/directives/anim.ts @@ -10,7 +10,7 @@ export default { }, mounted(src, binding, vn) { - setTimeout(() => { + window.setTimeout(() => { src.style.opacity = '1'; src.style.transform = 'none'; }, 1); diff --git a/packages/client/src/directives/tooltip.ts b/packages/client/src/directives/tooltip.ts index e14ee81dff..fffde14874 100644 --- a/packages/client/src/directives/tooltip.ts +++ b/packages/client/src/directives/tooltip.ts @@ -21,7 +21,7 @@ export default { self.close = () => { if (self._close) { - clearInterval(self.checkTimer); + window.clearInterval(self.checkTimer); self._close(); self._close = null; } @@ -61,19 +61,19 @@ export default { }); el.addEventListener(start, () => { - clearTimeout(self.showTimer); - clearTimeout(self.hideTimer); - self.showTimer = setTimeout(self.show, delay); + window.clearTimeout(self.showTimer); + window.clearTimeout(self.hideTimer); + self.showTimer = window.setTimeout(self.show, delay); }, { passive: true }); el.addEventListener(end, () => { - clearTimeout(self.showTimer); - clearTimeout(self.hideTimer); - self.hideTimer = setTimeout(self.close, delay); + window.clearTimeout(self.showTimer); + window.clearTimeout(self.hideTimer); + self.hideTimer = window.setTimeout(self.close, delay); }, { passive: true }); el.addEventListener('click', () => { - clearTimeout(self.showTimer); + window.clearTimeout(self.showTimer); self.close(); }); }, @@ -85,6 +85,6 @@ export default { unmounted(el, binding, vn) { const self = el._tooltipDirective_; - clearInterval(self.checkTimer); + window.clearInterval(self.checkTimer); }, } as Directive; diff --git a/packages/client/src/directives/user-preview.ts b/packages/client/src/directives/user-preview.ts index 68d9e2816c..cdd2afa194 100644 --- a/packages/client/src/directives/user-preview.ts +++ b/packages/client/src/directives/user-preview.ts @@ -30,11 +30,11 @@ export class UserPreview { source: this.el }, { mouseover: () => { - clearTimeout(this.hideTimer); + window.clearTimeout(this.hideTimer); }, mouseleave: () => { - clearTimeout(this.showTimer); - this.hideTimer = setTimeout(this.close, 500); + window.clearTimeout(this.showTimer); + this.hideTimer = window.setTimeout(this.close, 500); }, }, 'closed'); @@ -44,10 +44,10 @@ export class UserPreview { } }; - this.checkTimer = setInterval(() => { + this.checkTimer = window.setInterval(() => { if (!document.body.contains(this.el)) { - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); + window.clearTimeout(this.showTimer); + window.clearTimeout(this.hideTimer); this.close(); } }, 1000); @@ -56,7 +56,7 @@ export class UserPreview { @autobind private close() { if (this.promise) { - clearInterval(this.checkTimer); + window.clearInterval(this.checkTimer); this.promise.cancel(); this.promise = null; } @@ -64,21 +64,21 @@ export class UserPreview { @autobind private onMouseover() { - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.showTimer = setTimeout(this.show, 500); + window.clearTimeout(this.showTimer); + window.clearTimeout(this.hideTimer); + this.showTimer = window.setTimeout(this.show, 500); } @autobind private onMouseleave() { - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.hideTimer = setTimeout(this.close, 500); + window.clearTimeout(this.showTimer); + window.clearTimeout(this.hideTimer); + this.hideTimer = window.setTimeout(this.close, 500); } @autobind private onClick() { - clearTimeout(this.showTimer); + window.clearTimeout(this.showTimer); this.close(); } @@ -94,7 +94,7 @@ export class UserPreview { this.el.removeEventListener('mouseover', this.onMouseover); this.el.removeEventListener('mouseleave', this.onMouseleave); this.el.removeEventListener('click', this.onClick); - clearInterval(this.checkTimer); + window.clearInterval(this.checkTimer); } } diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index e6dd4567f7..dd7fdea4bd 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -83,7 +83,7 @@ export function promiseDialog<T extends Promise<any>>( onSuccess(res); } else { success.value = true; - setTimeout(() => { + window.setTimeout(() => { showing.value = false; }, 1000); } @@ -139,7 +139,7 @@ export async function popup(component: Component | typeof import('*.vue') | Prom const id = ++popupIdCount; const dispose = () => { // このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ? - setTimeout(() => { + window.setTimeout(() => { popups.value = popups.value.filter(popup => popup.id !== id); }, 0); }; @@ -329,7 +329,7 @@ export function select(props: { export function success() { return new Promise((resolve, reject) => { const showing = ref(true); - setTimeout(() => { + window.setTimeout(() => { showing.value = false; }, 1000); popup(import('@/components/waiting-dialog.vue'), { diff --git a/packages/client/src/pages/messaging/messaging-room.vue b/packages/client/src/pages/messaging/messaging-room.vue index 1bcee01d29..9a34551ddd 100644 --- a/packages/client/src/pages/messaging/messaging-room.vue +++ b/packages/client/src/pages/messaging/messaging-room.vue @@ -162,7 +162,7 @@ const Component = defineComponent({ // もっと見るの交差検知を発火させないためにfetchは // スクロールが終わるまでfalseにしておく // scrollendのようなイベントはないのでsetTimeoutで - setTimeout(() => this.fetching = false, 300); + window.setTimeout(() => this.fetching = false, 300); }); }, @@ -300,9 +300,9 @@ const Component = defineComponent({ this.showIndicator = false; }); - if (this.timer) clearTimeout(this.timer); + if (this.timer) window.clearTimeout(this.timer); - this.timer = setTimeout(() => { + this.timer = window.setTimeout(() => { this.showIndicator = false; }, 4000); }, diff --git a/packages/client/src/pages/share.vue b/packages/client/src/pages/share.vue index bdd8500ee4..5df6256fb2 100644 --- a/packages/client/src/pages/share.vue +++ b/packages/client/src/pages/share.vue @@ -169,7 +169,7 @@ export default defineComponent({ window.close(); // 閉じなければ100ms後タイムラインに - setTimeout(() => { + window.setTimeout(() => { this.$router.push('/'); }, 100); } diff --git a/packages/client/src/pizzax.ts b/packages/client/src/pizzax.ts index dbbbfc228a..fa35df5511 100644 --- a/packages/client/src/pizzax.ts +++ b/packages/client/src/pizzax.ts @@ -54,7 +54,7 @@ export class Storage<T extends StateDef> { if ($i) { // なぜかsetTimeoutしないとapi関数内でエラーになる(おそらく循環参照してることに原因がありそう) - setTimeout(() => { + window.setTimeout(() => { api('i/registry/get-all', { scope: ['client', this.key] }).then(kvs => { const cache = {}; for (const [k, v] of Object.entries(def)) { diff --git a/packages/client/src/router.ts b/packages/client/src/router.ts index 499afefbfe..ec48b76fdf 100644 --- a/packages/client/src/router.ts +++ b/packages/client/src/router.ts @@ -115,11 +115,11 @@ export const router = createRouter({ window._scroll = () => { // さらにHacky if (to.name === 'index') { window.scroll({ top: indexScrollPos, behavior: 'instant' }); - const i = setInterval(() => { + const i = window.setInterval(() => { window.scroll({ top: indexScrollPos, behavior: 'instant' }); }, 10); - setTimeout(() => { - clearInterval(i); + window.setTimeout(() => { + window.clearInterval(i); }, 500); } else { window.scroll({ top: 0, behavior: 'instant' }); diff --git a/packages/client/src/scripts/physics.ts b/packages/client/src/scripts/physics.ts index 445b6296eb..36e476b6f9 100644 --- a/packages/client/src/scripts/physics.ts +++ b/packages/client/src/scripts/physics.ts @@ -136,7 +136,7 @@ export function physics(container: HTMLElement) { } // 奈落に落ちたオブジェクトは消す - const intervalId = setInterval(() => { + const intervalId = window.setInterval(() => { for (const obj of objs) { if (obj.position.y > (containerHeight + 1024)) Matter.World.remove(world, obj); } @@ -146,7 +146,7 @@ export function physics(container: HTMLElement) { stop: () => { stop = true; Matter.Runner.stop(runner); - clearInterval(intervalId); + window.clearInterval(intervalId); } }; } diff --git a/packages/client/src/scripts/theme.ts b/packages/client/src/scripts/theme.ts index 3b7f003d0f..85c087331b 100644 --- a/packages/client/src/scripts/theme.ts +++ b/packages/client/src/scripts/theme.ts @@ -34,11 +34,11 @@ export const builtinThemes = [ let timeout = null; export function applyTheme(theme: Theme, persist = true) { - if (timeout) clearTimeout(timeout); + if (timeout) window.clearTimeout(timeout); document.documentElement.classList.add('_themeChanging_'); - timeout = setTimeout(() => { + timeout = window.setTimeout(() => { document.documentElement.classList.remove('_themeChanging_'); }, 1000); diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue index d3c7cf8213..1982d92ad3 100644 --- a/packages/client/src/ui/deck/column.vue +++ b/packages/client/src/ui/deck/column.vue @@ -224,7 +224,7 @@ export default defineComponent({ // Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう // SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately - setTimeout(() => { + window.setTimeout(() => { this.dragging = true; }, 10); }, diff --git a/packages/client/src/widgets/calendar.vue b/packages/client/src/widgets/calendar.vue index d16d3424b6..b0e3edcb12 100644 --- a/packages/client/src/widgets/calendar.vue +++ b/packages/client/src/widgets/calendar.vue @@ -104,9 +104,9 @@ const tick = () => { tick(); -const intervalId = setInterval(tick, 1000); +const intervalId = window.setInterval(tick, 1000); onUnmounted(() => { - clearInterval(intervalId); + window.clearInterval(intervalId); }); defineExpose<WidgetComponentExpose>({ diff --git a/packages/client/src/widgets/digital-clock.vue b/packages/client/src/widgets/digital-clock.vue index 637b0368be..62f052a692 100644 --- a/packages/client/src/widgets/digital-clock.vue +++ b/packages/client/src/widgets/digital-clock.vue @@ -67,12 +67,12 @@ const tick = () => { tick(); watch(() => widgetProps.showMs, () => { - if (intervalId) clearInterval(intervalId); - intervalId = setInterval(tick, widgetProps.showMs ? 10 : 1000); + if (intervalId) window.clearInterval(intervalId); + intervalId = window.setInterval(tick, widgetProps.showMs ? 10 : 1000); }, { immediate: true }); onUnmounted(() => { - clearInterval(intervalId); + window.clearInterval(intervalId); }); defineExpose<WidgetComponentExpose>({ diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue index 5d53b683b4..ed7350188e 100644 --- a/packages/client/src/widgets/federation.vue +++ b/packages/client/src/widgets/federation.vue @@ -66,9 +66,9 @@ const fetch = async () => { onMounted(() => { fetch(); - const intervalId = setInterval(fetch, 1000 * 60); + const intervalId = window.setInterval(fetch, 1000 * 60); onUnmounted(() => { - clearInterval(intervalId); + window.clearInterval(intervalId); }); }); diff --git a/packages/client/src/widgets/memo.vue b/packages/client/src/widgets/memo.vue index 3dfc6eb5fa..450598f65a 100644 --- a/packages/client/src/widgets/memo.vue +++ b/packages/client/src/widgets/memo.vue @@ -51,8 +51,8 @@ const saveMemo = () => { const onChange = () => { changed.value = true; - clearTimeout(timeoutId); - timeoutId = setTimeout(saveMemo, 1000); + window.clearTimeout(timeoutId); + timeoutId = window.setTimeout(saveMemo, 1000); }; watch(() => defaultStore.reactiveState.memo, newText => { diff --git a/packages/client/src/widgets/online-users.vue b/packages/client/src/widgets/online-users.vue index 2d47688697..1746a8314e 100644 --- a/packages/client/src/widgets/online-users.vue +++ b/packages/client/src/widgets/online-users.vue @@ -45,9 +45,9 @@ const tick = () => { onMounted(() => { tick(); - const intervalId = setInterval(tick, 1000 * 15); + const intervalId = window.setInterval(tick, 1000 * 15); onUnmounted(() => { - clearInterval(intervalId); + window.clearInterval(intervalId); }); }); diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue index 7a2272d744..9e2e503602 100644 --- a/packages/client/src/widgets/rss.vue +++ b/packages/client/src/widgets/rss.vue @@ -62,9 +62,9 @@ watch(() => widgetProps.url, tick); onMounted(() => { tick(); - const intervalId = setInterval(tick, 60000); + const intervalId = window.setInterval(tick, 60000); onUnmounted(() => { - clearInterval(intervalId); + window.clearInterval(intervalId); }); }); diff --git a/packages/client/src/widgets/slideshow.vue b/packages/client/src/widgets/slideshow.vue index ac0c6c9e07..7b2e539685 100644 --- a/packages/client/src/widgets/slideshow.vue +++ b/packages/client/src/widgets/slideshow.vue @@ -59,7 +59,7 @@ const change = () => { slideB.value.style.backgroundImage = img; slideB.value.classList.add('anime'); - setTimeout(() => { + window.setTimeout(() => { // 既にこのウィジェットがunmountされていたら要素がない if (slideA.value == null) return; @@ -101,9 +101,9 @@ onMounted(() => { fetch(); } - const intervalId = setInterval(change, 10000); + const intervalId = window.setInterval(change, 10000); onUnmounted(() => { - clearInterval(intervalId); + window.clearInterval(intervalId); }); }); diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue index 3905daa673..5768a8d5d1 100644 --- a/packages/client/src/widgets/trends.vue +++ b/packages/client/src/widgets/trends.vue @@ -60,9 +60,9 @@ const fetch = () => { onMounted(() => { fetch(); - const intervalId = setInterval(fetch, 1000 * 60); + const intervalId = window.setInterval(fetch, 1000 * 60); onUnmounted(() => { - clearInterval(intervalId); + window.clearInterval(intervalId); }); }); From cc6b2d578ff173ef5200273700211441cd55be3d Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 10:20:23 +0900 Subject: [PATCH 45/81] refactor: disallow some variable names --- packages/client/.eslintrc.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index 8e4ff6e455..e0113019ac 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -14,6 +14,10 @@ module.exports = { "plugin:vue/vue3-recommended" ], rules: { + // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため + // data の禁止理由: 抽象的すぎるため + // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため + "id-denylist": ["error", "window", "data", "e"], "vue/attributes-order": ["error", { "alphabetical": false }], From 2c36844d34c41266005dadc58b79e71f1d1a92f8 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 10:45:48 +0900 Subject: [PATCH 46/81] refactor: more common name --- .../backend/src/misc/{gen-avatar.ts => gen-identicon.ts} | 7 ++++--- packages/backend/src/models/repositories/user.ts | 2 +- packages/backend/src/server/index.ts | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) rename packages/backend/src/misc/{gen-avatar.ts => gen-identicon.ts} (90%) diff --git a/packages/backend/src/misc/gen-avatar.ts b/packages/backend/src/misc/gen-identicon.ts similarity index 90% rename from packages/backend/src/misc/gen-avatar.ts rename to packages/backend/src/misc/gen-identicon.ts index 8838ec8d15..5cedd7afaf 100644 --- a/packages/backend/src/misc/gen-avatar.ts +++ b/packages/backend/src/misc/gen-identicon.ts @@ -1,5 +1,6 @@ /** - * Random avatar generator + * Identicon generator + * https://en.wikipedia.org/wiki/Identicon */ import * as p from 'pureimage'; @@ -34,9 +35,9 @@ const cellSize = actualSize / n; const sideN = Math.floor(n / 2); /** - * Generate buffer of random avatar by seed + * Generate buffer of an identicon by seed */ -export function genAvatar(seed: string, stream: WriteStream): Promise<void> { +export function genIdenticon(seed: string, stream: WriteStream): Promise<void> { const rand = gen.create(seed); const canvas = p.make(size, size); const ctx = canvas.getContext('2d'); diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 3dc7c67ec2..85141cdc41 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -159,7 +159,7 @@ export class UserRepository extends Repository<User> { if (user.avatarUrl) { return user.avatarUrl; } else { - return `${config.url}/random-avatar/${user.id}`; + return `${config.url}/identicon/${user.id}`; } } diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 85fe21accb..764306c7d8 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -23,7 +23,7 @@ import Logger from '@/services/logger'; import { envOption } from '../env'; import { UserProfiles, Users } from '@/models/index'; import { networkChart } from '@/services/chart/index'; -import { genAvatar } from '@/misc/gen-avatar'; +import { genIdenticon } from '@/misc/gen-identicon'; import { createTemp } from '@/misc/create-temp'; import { publishMainStream } from '@/services/stream'; import * as Acct from 'misskey-js/built/acct'; @@ -84,9 +84,9 @@ router.get('/avatar/@:acct', async ctx => { } }); -router.get('/random-avatar/:x', async ctx => { +router.get('/identicon/:x', async ctx => { const [temp] = await createTemp(); - await genAvatar(ctx.params.x, fs.createWriteStream(temp)); + await genIdenticon(ctx.params.x, fs.createWriteStream(temp)); ctx.set('Content-Type', 'image/png'); ctx.body = fs.createReadStream(temp); }); From df61e173c197894e03af02d1b36375d282ed1d63 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 11:02:27 +0900 Subject: [PATCH 47/81] wip: refactor(client): migrate components to composition api --- packages/client/src/components/global/ad.vue | 6 +- packages/client/src/pages/reset-password.vue | 69 ++++++++----------- packages/client/src/pages/signup-complete.vue | 58 ++++++---------- 3 files changed, 54 insertions(+), 79 deletions(-) diff --git a/packages/client/src/components/global/ad.vue b/packages/client/src/components/global/ad.vue index 49046b00a7..180dabb2a2 100644 --- a/packages/client/src/components/global/ad.vue +++ b/packages/client/src/components/global/ad.vue @@ -20,7 +20,7 @@ <script lang="ts"> import { defineComponent, ref } from 'vue'; -import { Instance, instance } from '@/instance'; +import { instance } from '@/instance'; import { host } from '@/config'; import MkButton from '@/components/ui/button.vue'; import { defaultStore } from '@/store'; @@ -48,9 +48,9 @@ export default defineComponent({ showMenu.value = !showMenu.value; }; - const choseAd = (): Instance['ads'][number] | null => { + const choseAd = (): (typeof instance)['ads'][number] | null => { if (props.specify) { - return props.specify as Instance['ads'][number]; + return props.specify as (typeof instance)['ads'][number]; } const allAds = instance.ads.map(ad => defaultStore.state.mutedAds.includes(ad.id) ? { diff --git a/packages/client/src/pages/reset-password.vue b/packages/client/src/pages/reset-password.vue index e0608654c7..8ef73858f6 100644 --- a/packages/client/src/pages/reset-password.vue +++ b/packages/client/src/pages/reset-password.vue @@ -3,62 +3,51 @@ <div class="_formRoot"> <FormInput v-model="password" type="password" class="_formBlock"> <template #prefix><i class="fas fa-lock"></i></template> - <template #label>{{ $ts.newPassword }}</template> + <template #label>{{ i18n.locale.newPassword }}</template> </FormInput> - <FormButton primary class="_formBlock" @click="save">{{ $ts.save }}</FormButton> + <FormButton primary class="_formBlock" @click="save">{{ i18n.locale.save }}</FormButton> </div> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { router } from '@/router'; -export default defineComponent({ - components: { - FormInput, - FormButton, - }, +const props = defineProps<{ + token?: string; +}>(); - props: { - token: { - type: String, - required: false - } - }, +let password = $ref(''); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.resetPassword, - icon: 'fas fa-lock', - bg: 'var(--bg)', - }, - password: '', - } - }, +async function save() { + await os.apiWithDialog('reset-password', { + token: props.token, + password: password, + }); + router.push('/'); +} - mounted() { - if (this.token == null) { - os.popup(import('@/components/forgot-password.vue'), {}, {}, 'closed'); - this.$router.push('/'); - } - }, - - methods: { - async save() { - await os.apiWithDialog('reset-password', { - token: this.token, - password: this.password, - }); - this.$router.push('/'); - } +onMounted(() => { + if (props.token == null) { + os.popup(import('@/components/forgot-password.vue'), {}, {}, 'closed'); + router.push('/'); } }); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.resetPassword, + icon: 'fas fa-lock', + bg: 'var(--bg)', + }, +}); </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/signup-complete.vue b/packages/client/src/pages/signup-complete.vue index 89375e05d2..a10af1a4cc 100644 --- a/packages/client/src/pages/signup-complete.vue +++ b/packages/client/src/pages/signup-complete.vue @@ -1,50 +1,36 @@ <template> <div> - {{ $ts.processing }} + {{ i18n.locale.processing }} </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { login } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { +const props = defineProps<{ + code: string; +}>(); +onMounted(async () => { + await os.alert({ + type: 'info', + text: i18n.t('clickToFinishEmailVerification', { ok: i18n.locale.gotIt }), + }); + const res = await os.apiWithDialog('signup-pending', { + code: props.code, + }); + login(res.i, '/'); +}); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.signup, + icon: 'fas fa-user', }, - - props: { - code: { - type: String, - required: true - } - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.signup, - icon: 'fas fa-user' - }, - } - }, - - async mounted() { - await os.alert({ - type: 'info', - text: this.$t('clickToFinishEmailVerification', { ok: this.$ts.gotIt }), - }); - const res = await os.apiWithDialog('signup-pending', { - code: this.code, - }); - login(res.i, '/'); - }, - - methods: { - - } }); </script> From 3e9677904d25df368bb1038963a914ffa717077d Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 15:02:15 +0900 Subject: [PATCH 48/81] wip: refactor(client): migrate components to composition api --- packages/client/src/pages/theme-editor.vue | 318 ++++++++---------- packages/client/src/pages/timeline.vue | 254 +++++++------- .../client/src/scripts/use-leave-guard.ts | 34 ++ packages/client/src/store.ts | 2 +- 4 files changed, 292 insertions(+), 316 deletions(-) create mode 100644 packages/client/src/scripts/use-leave-guard.ts diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue index c4917e2270..80b8c7806c 100644 --- a/packages/client/src/pages/theme-editor.vue +++ b/packages/client/src/pages/theme-editor.vue @@ -2,7 +2,7 @@ <MkSpacer :content-max="800" :margin-min="16" :margin-max="32"> <div class="cwepdizn _formRoot"> <FormFolder :default-open="true" class="_formBlock"> - <template #label>{{ $ts.backgroundColor }}</template> + <template #label>{{ i18n.locale.backgroundColor }}</template> <div class="cwepdizn-colors"> <div class="row"> <button v-for="color in bgColors.filter(x => x.kind === 'light')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)"> @@ -18,7 +18,7 @@ </FormFolder> <FormFolder :default-open="true" class="_formBlock"> - <template #label>{{ $ts.accentColor }}</template> + <template #label>{{ i18n.locale.accentColor }}</template> <div class="cwepdizn-colors"> <div class="row"> <button v-for="color in accentColors" :key="color" class="color rounded _button" :class="{ active: theme.props.accent === color }" @click="setAccentColor(color)"> @@ -29,7 +29,7 @@ </FormFolder> <FormFolder :default-open="true" class="_formBlock"> - <template #label>{{ $ts.textColor }}</template> + <template #label>{{ i18n.locale.textColor }}</template> <div class="cwepdizn-colors"> <div class="row"> <button v-for="color in fgColors" :key="color" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }" @click="setFgColor(color)"> @@ -41,22 +41,22 @@ <FormFolder :default-open="false" class="_formBlock"> <template #icon><i class="fas fa-code"></i></template> - <template #label>{{ $ts.editCode }}</template> + <template #label>{{ i18n.locale.editCode }}</template> <div class="_formRoot"> <FormTextarea v-model="themeCode" tall class="_formBlock"> - <template #label>{{ $ts._theme.code }}</template> + <template #label>{{ i18n.locale._theme.code }}</template> </FormTextarea> - <FormButton primary class="_formBlock" @click="applyThemeCode">{{ $ts.apply }}</FormButton> + <FormButton primary class="_formBlock" @click="applyThemeCode">{{ i18n.locale.apply }}</FormButton> </div> </FormFolder> <FormFolder :default-open="false" class="_formBlock"> - <template #label>{{ $ts.addDescription }}</template> + <template #label>{{ i18n.locale.addDescription }}</template> <div class="_formRoot"> <FormTextarea v-model="description"> - <template #label>{{ $ts._theme.description }}</template> + <template #label>{{ i18n.locale._theme.description }}</template> </FormTextarea> </div> </FormFolder> @@ -64,8 +64,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { watch } from 'vue'; import { toUnicode } from 'punycode/'; import * as tinycolor from 'tinycolor2'; import { v4 as uuid} from 'uuid'; @@ -78,181 +78,147 @@ import FormFolder from '@/components/form/folder.vue'; import { Theme, applyTheme, darkTheme, lightTheme } from '@/scripts/theme'; import { host } from '@/config'; import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; +import { ColdDeviceStorage, defaultStore } from '@/store'; import { addTheme } from '@/theme-store'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { useLeaveGuard } from '@/scripts/use-leave-guard'; -export default defineComponent({ - components: { - FormButton, - FormTextarea, - FormFolder, - }, +const bgColors = [ + { color: '#f5f5f5', kind: 'light', forPreview: '#f5f5f5' }, + { color: '#f0eee9', kind: 'light', forPreview: '#f3e2b9' }, + { color: '#e9eff0', kind: 'light', forPreview: '#bfe3e8' }, + { color: '#f0e9ee', kind: 'light', forPreview: '#f1d1e8' }, + { color: '#dce2e0', kind: 'light', forPreview: '#a4dccc' }, + { color: '#e2e0dc', kind: 'light', forPreview: '#d8c7a5' }, + { color: '#d5dbe0', kind: 'light', forPreview: '#b0cae0' }, + { color: '#dad5d5', kind: 'light', forPreview: '#d6afaf' }, + { color: '#2b2b2b', kind: 'dark', forPreview: '#444444' }, + { color: '#362e29', kind: 'dark', forPreview: '#735c4d' }, + { color: '#303629', kind: 'dark', forPreview: '#506d2f' }, + { color: '#293436', kind: 'dark', forPreview: '#258192' }, + { color: '#2e2936', kind: 'dark', forPreview: '#504069' }, + { color: '#252722', kind: 'dark', forPreview: '#3c462f' }, + { color: '#212525', kind: 'dark', forPreview: '#303e3e' }, + { color: '#191919', kind: 'dark', forPreview: '#272727' }, +] as const; +const accentColors = ['#e36749', '#f29924', '#98c934', '#34c9a9', '#34a1c9', '#606df7', '#8d34c9', '#e84d83']; +const fgColors = [ + { color: 'none', forLight: '#5f5f5f', forDark: '#dadada', forPreview: null }, + { color: 'red', forLight: '#7f6666', forDark: '#e4d1d1', forPreview: '#ca4343' }, + { color: 'yellow', forLight: '#736955', forDark: '#e0d5c0', forPreview: '#d49923' }, + { color: 'green', forLight: '#586d5b', forDark: '#d1e4d4', forPreview: '#4cbd5c' }, + { color: 'cyan', forLight: '#5d7475', forDark: '#d1e3e4', forPreview: '#2abdc3' }, + { color: 'blue', forLight: '#676880', forDark: '#d1d2e4', forPreview: '#7275d8' }, + { color: 'pink', forLight: '#84667d', forDark: '#e4d1e0', forPreview: '#b12390' }, +]; - async beforeRouteLeave(to, from) { - if (this.changed && !(await this.leaveConfirm())) { - return false; - } - }, +const theme = $ref<Partial<Theme>>({ + base: 'light', + props: lightTheme.props, +}); +let description = $ref<string | null>(null); +let themeCode = $ref<string | null>(null); +let changed = $ref(false); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.themeEditor, - icon: 'fas fa-palette', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-eye', - text: this.$ts.preview, - handler: this.showPreview, - }, { - asFullButton: true, - icon: 'fas fa-check', - text: this.$ts.saveAs, - handler: this.saveAs, - }], - }, - theme: { - base: 'light', - props: lightTheme.props - } as Theme, - description: null, - themeCode: null, - bgColors: [ - { color: '#f5f5f5', kind: 'light', forPreview: '#f5f5f5' }, - { color: '#f0eee9', kind: 'light', forPreview: '#f3e2b9' }, - { color: '#e9eff0', kind: 'light', forPreview: '#bfe3e8' }, - { color: '#f0e9ee', kind: 'light', forPreview: '#f1d1e8' }, - { color: '#dce2e0', kind: 'light', forPreview: '#a4dccc' }, - { color: '#e2e0dc', kind: 'light', forPreview: '#d8c7a5' }, - { color: '#d5dbe0', kind: 'light', forPreview: '#b0cae0' }, - { color: '#dad5d5', kind: 'light', forPreview: '#d6afaf' }, - { color: '#2b2b2b', kind: 'dark', forPreview: '#444444' }, - { color: '#362e29', kind: 'dark', forPreview: '#735c4d' }, - { color: '#303629', kind: 'dark', forPreview: '#506d2f' }, - { color: '#293436', kind: 'dark', forPreview: '#258192' }, - { color: '#2e2936', kind: 'dark', forPreview: '#504069' }, - { color: '#252722', kind: 'dark', forPreview: '#3c462f' }, - { color: '#212525', kind: 'dark', forPreview: '#303e3e' }, - { color: '#191919', kind: 'dark', forPreview: '#272727' }, - ], - accentColors: ['#e36749', '#f29924', '#98c934', '#34c9a9', '#34a1c9', '#606df7', '#8d34c9', '#e84d83'], - fgColors: [ - { color: 'none', forLight: '#5f5f5f', forDark: '#dadada', forPreview: null }, - { color: 'red', forLight: '#7f6666', forDark: '#e4d1d1', forPreview: '#ca4343' }, - { color: 'yellow', forLight: '#736955', forDark: '#e0d5c0', forPreview: '#d49923' }, - { color: 'green', forLight: '#586d5b', forDark: '#d1e4d4', forPreview: '#4cbd5c' }, - { color: 'cyan', forLight: '#5d7475', forDark: '#d1e3e4', forPreview: '#2abdc3' }, - { color: 'blue', forLight: '#676880', forDark: '#d1d2e4', forPreview: '#7275d8' }, - { color: 'pink', forLight: '#84667d', forDark: '#e4d1e0', forPreview: '#b12390' }, - ], - changed: false, - } - }, +useLeaveGuard($$(changed)); - created() { - this.$watch('theme', this.apply, { deep: true }); - window.addEventListener('beforeunload', this.beforeunload); - }, +function showPreview() { + os.pageWindow('preview'); +} - beforeUnmount() { - window.removeEventListener('beforeunload', this.beforeunload); - }, - - methods: { - beforeunload(e: BeforeUnloadEvent) { - if (this.changed) { - e.preventDefault(); - e.returnValue = ''; - } - }, - - async leaveConfirm(): Promise<boolean> { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$ts.leaveConfirm, - }); - return !canceled; - }, - - showPreview() { - os.pageWindow('preview'); - }, - - setBgColor(color) { - if (this.theme.base != color.kind) { - const base = color.kind === 'dark' ? darkTheme : lightTheme; - for (const prop of Object.keys(base.props)) { - if (prop === 'accent') continue; - if (prop === 'fg') continue; - this.theme.props[prop] = base.props[prop]; - } - } - this.theme.base = color.kind; - this.theme.props.bg = color.color; - - if (this.theme.props.fg) { - const matchedFgColor = this.fgColors.find(x => [tinycolor(x.forLight).toRgbString(), tinycolor(x.forDark).toRgbString()].includes(tinycolor(this.theme.props.fg).toRgbString())); - if (matchedFgColor) this.setFgColor(matchedFgColor); - } - }, - - setAccentColor(color) { - this.theme.props.accent = color; - }, - - setFgColor(color) { - this.theme.props.fg = this.theme.base === 'light' ? color.forLight : color.forDark; - }, - - apply() { - this.themeCode = JSON5.stringify(this.theme, null, '\t'); - applyTheme(this.theme, false); - this.changed = true; - }, - - applyThemeCode() { - let parsed; - - try { - parsed = JSON5.parse(this.themeCode); - } catch (e) { - os.alert({ - type: 'error', - text: this.$ts._theme.invalid - }); - return; - } - - this.theme = parsed; - }, - - async saveAs() { - const { canceled, result: name } = await os.inputText({ - title: this.$ts.name, - allowEmpty: false - }); - if (canceled) return; - - this.theme.id = uuid(); - this.theme.name = name; - this.theme.author = `@${this.$i.username}@${toUnicode(host)}`; - if (this.description) this.theme.desc = this.description; - addTheme(this.theme); - applyTheme(this.theme); - if (this.$store.state.darkMode) { - ColdDeviceStorage.set('darkTheme', this.theme); - } else { - ColdDeviceStorage.set('lightTheme', this.theme); - } - this.changed = false; - os.alert({ - type: 'success', - text: this.$t('_theme.installed', { name: this.theme.name }) - }); +function setBgColor(color: typeof bgColors[number]) { + if (theme.base != color.kind) { + const base = color.kind === 'dark' ? darkTheme : lightTheme; + for (const prop of Object.keys(base.props)) { + if (prop === 'accent') continue; + if (prop === 'fg') continue; + theme.props[prop] = base.props[prop]; } } + theme.base = color.kind; + theme.props.bg = color.color; + + if (theme.props.fg) { + const matchedFgColor = fgColors.find(x => [tinycolor(x.forLight).toRgbString(), tinycolor(x.forDark).toRgbString()].includes(tinycolor(theme.props.fg).toRgbString())); + if (matchedFgColor) setFgColor(matchedFgColor); + } +} + +function setAccentColor(color) { + theme.props.accent = color; +} + +function setFgColor(color) { + theme.props.fg = theme.base === 'light' ? color.forLight : color.forDark; +} + +function apply() { + themeCode = JSON5.stringify(theme, null, '\t'); + applyTheme(theme, false); + changed = true; +} + +function applyThemeCode() { + let parsed; + + try { + parsed = JSON5.parse(themeCode); + } catch (err) { + os.alert({ + type: 'error', + text: i18n.locale._theme.invalid, + }); + return; + } + + theme = parsed; +} + +async function saveAs() { + const { canceled, result: name } = await os.inputText({ + title: i18n.locale.name, + allowEmpty: false, + }); + if (canceled) return; + + theme.id = uuid(); + theme.name = name; + theme.author = `@${$i.username}@${toUnicode(host)}`; + if (description) theme.desc = description; + addTheme(theme); + applyTheme(theme); + if (defaultStore.state.darkMode) { + ColdDeviceStorage.set('darkTheme', theme); + } else { + ColdDeviceStorage.set('lightTheme', theme); + } + changed = false; + os.alert({ + type: 'success', + text: i18n.t('_theme.installed', { name: theme.name }), + }); +} + +watch($$(theme), apply, { deep: true }); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.themeEditor, + icon: 'fas fa-palette', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-eye', + text: i18n.locale.preview, + handler: showPreview, + }, { + asFullButton: true, + icon: 'fas fa-check', + text: i18n.locale.saveAs, + handler: saveAs, + }], + }, }); </script> diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue index 216b3c34ea..ecd1ae6257 100644 --- a/packages/client/src/pages/timeline.vue +++ b/packages/client/src/pages/timeline.vue @@ -1,6 +1,6 @@ <template> <MkSpacer :content-max="800"> - <div v-hotkey.global="keymap" class="cmuxhskf"> + <div ref="rootEl" v-hotkey.global="keymap" class="cmuxhskf"> <XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/> <XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" fixed/> @@ -17,163 +17,139 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent, defineAsyncComponent, computed } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent, computed, watch } from 'vue'; import XTimeline from '@/components/timeline.vue'; import XPostForm from '@/components/post-form.vue'; import { scroll } from '@/scripts/scroll'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +import { $i } from '@/account'; -export default defineComponent({ - name: 'timeline', +const XTutorial = defineAsyncComponent(() => import('./timeline.tutorial.vue')); - components: { - XTimeline, - XTutorial: defineAsyncComponent(() => import('./timeline.tutorial.vue')), - XPostForm, - }, +const isLocalTimelineAvailable = !instance.disableLocalTimeline || ($i != null && ($i.isModerator || $i.isAdmin)); +const isGlobalTimelineAvailable = !instance.disableGlobalTimeline || ($i != null && ($i.isModerator || $i.isAdmin)); +const keymap = { + 't': focus, +}; - data() { - return { - src: 'home', - queue: 0, - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.timeline, - icon: this.src === 'local' ? 'fas fa-comments' : this.src === 'social' ? 'fas fa-share-alt' : this.src === 'global' ? 'fas fa-globe' : 'fas fa-home', - bg: 'var(--bg)', - actions: [{ - icon: 'fas fa-list-ul', - text: this.$ts.lists, - handler: this.chooseList - }, { - icon: 'fas fa-satellite', - text: this.$ts.antennas, - handler: this.chooseAntenna - }, { - icon: 'fas fa-satellite-dish', - text: this.$ts.channel, - handler: this.chooseChannel - }, { - icon: 'fas fa-calendar-alt', - text: this.$ts.jumpToSpecifiedDate, - handler: this.timetravel - }], - tabs: [{ - active: this.src === 'home', - title: this.$ts._timelines.home, - icon: 'fas fa-home', - iconOnly: true, - onClick: () => { this.src = 'home'; this.saveSrc(); }, - }, ...(this.isLocalTimelineAvailable ? [{ - active: this.src === 'local', - title: this.$ts._timelines.local, - icon: 'fas fa-comments', - iconOnly: true, - onClick: () => { this.src = 'local'; this.saveSrc(); }, - }, { - active: this.src === 'social', - title: this.$ts._timelines.social, - icon: 'fas fa-share-alt', - iconOnly: true, - onClick: () => { this.src = 'social'; this.saveSrc(); }, - }] : []), ...(this.isGlobalTimelineAvailable ? [{ - active: this.src === 'global', - title: this.$ts._timelines.global, - icon: 'fas fa-globe', - iconOnly: true, - onClick: () => { this.src = 'global'; this.saveSrc(); }, - }] : [])], - })), - }; - }, +const tlComponent = $ref<InstanceType<typeof XTimeline>>(); +const rootEl = $ref<HTMLElement>(); - computed: { - keymap(): any { - return { - 't': this.focus - }; - }, +let src = $ref<'home' | 'local' | 'social' | 'global'>(defaultStore.state.tl.src); +let queue = $ref(0); - isLocalTimelineAvailable(): boolean { - return !this.$instance.disableLocalTimeline || this.$i.isModerator || this.$i.isAdmin; - }, +function queueUpdated(q: number): void { + queue = q; +} - isGlobalTimelineAvailable(): boolean { - return !this.$instance.disableGlobalTimeline || this.$i.isModerator || this.$i.isAdmin; - }, - }, +function top(): void { + scroll(rootEl, { top: 0 }); +} - watch: { - src() { - this.showNav = false; - }, - }, +async function chooseList(ev: MouseEvent): Promise<void> { + const lists = await os.api('users/lists/list'); + const items = lists.map(list => ({ + type: 'link', + text: list.name, + to: `/timeline/list/${list.id}`, + })); + os.popupMenu(items, ev.currentTarget || ev.target); +} - created() { - this.src = this.$store.state.tl.src; - }, +async function chooseAntenna(ev: MouseEvent): Promise<void> { + const antennas = await os.api('antennas/list'); + const items = antennas.map(antenna => ({ + type: 'link', + text: antenna.name, + indicate: antenna.hasUnreadNote, + to: `/timeline/antenna/${antenna.id}`, + })); + os.popupMenu(items, ev.currentTarget || ev.target); +} - methods: { - queueUpdated(q) { - this.queue = q; - }, +async function chooseChannel(ev: MouseEvent): Promise<void> { + const channels = await os.api('channels/followed'); + const items = channels.map(channel => ({ + type: 'link', + text: channel.name, + indicate: channel.hasUnreadNote, + to: `/channels/${channel.id}`, + })); + os.popupMenu(items, ev.currentTarget || ev.target); +} - top() { - scroll(this.$el, { top: 0 }); - }, +function saveSrc(): void { + defaultStore.set('tl', { + src: src, + }); +} - async chooseList(ev) { - const lists = await os.api('users/lists/list'); - const items = lists.map(list => ({ - type: 'link', - text: list.name, - to: `/timeline/list/${list.id}` - })); - os.popupMenu(items, ev.currentTarget || ev.target); - }, +async function timetravel(): Promise<void> { + const { canceled, result: date } = await os.inputDate({ + title: i18n.locale.date, + }); + if (canceled) return; - async chooseAntenna(ev) { - const antennas = await os.api('antennas/list'); - const items = antennas.map(antenna => ({ - type: 'link', - text: antenna.name, - indicate: antenna.hasUnreadNote, - to: `/timeline/antenna/${antenna.id}` - })); - os.popupMenu(items, ev.currentTarget || ev.target); - }, + tlComponent.timetravel(date); +} - async chooseChannel(ev) { - const channels = await os.api('channels/followed'); - const items = channels.map(channel => ({ - type: 'link', - text: channel.name, - indicate: channel.hasUnreadNote, - to: `/channels/${channel.id}` - })); - os.popupMenu(items, ev.currentTarget || ev.target); - }, +function focus(): void { + tlComponent.focus(); +} - saveSrc() { - this.$store.set('tl', { - src: this.src, - }); - }, - - async timetravel() { - const { canceled, result: date } = await os.inputDate({ - title: this.$ts.date, - }); - if (canceled) return; - - this.$refs.tl.timetravel(date); - }, - - focus() { - (this.$refs.tl as any).focus(); - } - } +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.timeline, + icon: src === 'local' ? 'fas fa-comments' : src === 'social' ? 'fas fa-share-alt' : src === 'global' ? 'fas fa-globe' : 'fas fa-home', + bg: 'var(--bg)', + actions: [{ + icon: 'fas fa-list-ul', + text: i18n.locale.lists, + handler: chooseList, + }, { + icon: 'fas fa-satellite', + text: i18n.locale.antennas, + handler: chooseAntenna, + }, { + icon: 'fas fa-satellite-dish', + text: i18n.locale.channel, + handler: chooseChannel, + }, { + icon: 'fas fa-calendar-alt', + text: i18n.locale.jumpToSpecifiedDate, + handler: timetravel, + }], + tabs: [{ + active: src === 'home', + title: i18n.locale._timelines.home, + icon: 'fas fa-home', + iconOnly: true, + onClick: () => { src = 'home'; saveSrc(); }, + }, ...(isLocalTimelineAvailable ? [{ + active: src === 'local', + title: i18n.locale._timelines.local, + icon: 'fas fa-comments', + iconOnly: true, + onClick: () => { src = 'local'; saveSrc(); }, + }, { + active: src === 'social', + title: i18n.locale._timelines.social, + icon: 'fas fa-share-alt', + iconOnly: true, + onClick: () => { src = 'social'; saveSrc(); }, + }] : []), ...(isGlobalTimelineAvailable ? [{ + active: src === 'global', + title: i18n.locale._timelines.global, + icon: 'fas fa-globe', + iconOnly: true, + onClick: () => { src = 'global'; saveSrc(); }, + }] : [])], + })), }); </script> diff --git a/packages/client/src/scripts/use-leave-guard.ts b/packages/client/src/scripts/use-leave-guard.ts new file mode 100644 index 0000000000..21899af59a --- /dev/null +++ b/packages/client/src/scripts/use-leave-guard.ts @@ -0,0 +1,34 @@ +import { inject, onUnmounted, Ref } from 'vue'; +import { i18n } from '@/i18n'; +import * as os from '@/os'; + +export function useLeaveGuard(enabled: Ref<boolean>) { + const setLeaveGuard = inject('setLeaveGuard'); + + if (setLeaveGuard) { + setLeaveGuard(async () => { + if (!enabled.value) return false; + + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.locale.leaveConfirm, + }); + + return canceled; + }); + } + + /* + function onBeforeLeave(ev: BeforeUnloadEvent) { + if (enabled.value) { + ev.preventDefault(); + ev.returnValue = ''; + } + } + + window.addEventListener('beforeunload', onBeforeLeave); + onUnmounted(() => { + window.removeEventListener('beforeunload', onBeforeLeave); + }); + */ +} diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index a57e8ec62b..cd358d29d0 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -97,7 +97,7 @@ export const defaultStore = markRaw(new Storage('base', { tl: { where: 'deviceAccount', default: { - src: 'home', + src: 'home' as 'home' | 'local' | 'social' | 'global', arg: null } }, From 9885c6ba6cb17c38ff723e529834ec2fff210d86 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 16 Jan 2022 21:31:09 +0900 Subject: [PATCH 49/81] wip: refactor(client): migrate components to composition api --- .../client/src/components/global/avatar.vue | 4 +- .../client/src/pages/settings/profile.vue | 367 ++++++++---------- .../client/src/scripts/use-leave-guard.ts | 12 + 3 files changed, 175 insertions(+), 208 deletions(-) diff --git a/packages/client/src/components/global/avatar.vue b/packages/client/src/components/global/avatar.vue index 9e8979fe56..27cfb6e4d4 100644 --- a/packages/client/src/components/global/avatar.vue +++ b/packages/client/src/components/global/avatar.vue @@ -35,9 +35,9 @@ const emit = defineEmits<{ (e: 'click', ev: MouseEvent): void; }>(); -const url = defaultStore.state.disableShowingAnimatedImages +const url = $computed(() => defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(props.user.avatarUrl) - : props.user.avatarUrl; + : props.user.avatarUrl); function onClick(ev: MouseEvent) { emit('click', ev); diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue index 9a2395872e..f875146a2c 100644 --- a/packages/client/src/pages/settings/profile.vue +++ b/packages/client/src/pages/settings/profile.vue @@ -3,50 +3,50 @@ <div class="llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }"> <div class="avatar _acrylic"> <MkAvatar class="avatar" :user="$i" :disable-link="true" @click="changeAvatar"/> - <MkButton primary class="avatarEdit" @click="changeAvatar">{{ $ts._profile.changeAvatar }}</MkButton> + <MkButton primary class="avatarEdit" @click="changeAvatar">{{ i18n.locale._profile.changeAvatar }}</MkButton> </div> - <MkButton primary class="bannerEdit" @click="changeBanner">{{ $ts._profile.changeBanner }}</MkButton> + <MkButton primary class="bannerEdit" @click="changeBanner">{{ i18n.locale._profile.changeBanner }}</MkButton> </div> - <FormInput v-model="name" :max="30" manual-save class="_formBlock"> - <template #label>{{ $ts._profile.name }}</template> + <FormInput v-model="profile.name" :max="30" manual-save class="_formBlock"> + <template #label>{{ i18n.locale._profile.name }}</template> </FormInput> - <FormTextarea v-model="description" :max="500" tall manual-save class="_formBlock"> - <template #label>{{ $ts._profile.description }}</template> - <template #caption>{{ $ts._profile.youCanIncludeHashtags }}</template> + <FormTextarea v-model="profile.description" :max="500" tall manual-save class="_formBlock"> + <template #label>{{ i18n.locale._profile.description }}</template> + <template #caption>{{ i18n.locale._profile.youCanIncludeHashtags }}</template> </FormTextarea> - <FormInput v-model="location" manual-save class="_formBlock"> - <template #label>{{ $ts.location }}</template> + <FormInput v-model="profile.location" manual-save class="_formBlock"> + <template #label>{{ i18n.locale.location }}</template> <template #prefix><i class="fas fa-map-marker-alt"></i></template> </FormInput> - <FormInput v-model="birthday" type="date" manual-save class="_formBlock"> - <template #label>{{ $ts.birthday }}</template> + <FormInput v-model="profile.birthday" type="date" manual-save class="_formBlock"> + <template #label>{{ i18n.locale.birthday }}</template> <template #prefix><i class="fas fa-birthday-cake"></i></template> </FormInput> - <FormSelect v-model="lang" class="_formBlock"> - <template #label>{{ $ts.language }}</template> + <FormSelect v-model="profile.lang" class="_formBlock"> + <template #label>{{ i18n.locale.language }}</template> <option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option> </FormSelect> <FormSlot> - <MkButton @click="editMetadata">{{ $ts._profile.metadataEdit }}</MkButton> - <template #caption>{{ $ts._profile.metadataDescription }}</template> + <MkButton @click="editMetadata">{{ i18n.locale._profile.metadataEdit }}</MkButton> + <template #caption>{{ i18n.locale._profile.metadataDescription }}</template> </FormSlot> - <FormSwitch v-model="isCat" class="_formBlock">{{ $ts.flagAsCat }}<template #caption>{{ $ts.flagAsCatDescription }}</template></FormSwitch> + <FormSwitch v-model="profile.isCat" class="_formBlock">{{ i18n.locale.flagAsCat }}<template #caption>{{ i18n.locale.flagAsCatDescription }}</template></FormSwitch> - <FormSwitch v-model="isBot" class="_formBlock">{{ $ts.flagAsBot }}<template #caption>{{ $ts.flagAsBotDescription }}</template></FormSwitch> + <FormSwitch v-model="profile.isBot" class="_formBlock">{{ i18n.locale.flagAsBot }}<template #caption>{{ i18n.locale.flagAsBotDescription }}</template></FormSwitch> - <FormSwitch v-model="alwaysMarkNsfw" class="_formBlock">{{ $ts.alwaysMarkSensitive }}</FormSwitch> + <FormSwitch v-model="profile.alwaysMarkNsfw" class="_formBlock">{{ i18n.locale.alwaysMarkSensitive }}</FormSwitch> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineComponent, reactive, watch } from 'vue'; import MkButton from '@/components/ui/button.vue'; import FormInput from '@/components/form/input.vue'; import FormTextarea from '@/components/form/textarea.vue'; @@ -57,194 +57,149 @@ import { host, langs } from '@/config'; import { selectFile } from '@/scripts/select-file'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - MkButton, - FormInput, - FormTextarea, - FormSwitch, - FormSelect, - FormSlot, +const profile = reactive({ + name: $i.name, + description: $i.description, + location: $i.location, + birthday: $i.birthday, + lang: $i.lang, + isBot: $i.isBot, + isCat: $i.isCat, + alwaysMarkNsfw: $i.alwaysMarkNsfw, +}); + +const additionalFields = reactive({ + fieldName0: $i.fields[0] ? $i.fields[0].name : null, + fieldValue0: $i.fields[0] ? $i.fields[0].value : null, + fieldName1: $i.fields[1] ? $i.fields[1].name : null, + fieldValue1: $i.fields[1] ? $i.fields[1].value : null, + fieldName2: $i.fields[2] ? $i.fields[2].name : null, + fieldValue2: $i.fields[2] ? $i.fields[2].value : null, + fieldName3: $i.fields[3] ? $i.fields[3].name : null, + fieldValue3: $i.fields[3] ? $i.fields[3].value : null, +}); + +watch(() => profile, () => { + save(); +}, { + deep: true, +}); + +function save() { + os.apiWithDialog('i/update', { + name: profile.name || null, + description: profile.description || null, + location: profile.location || null, + birthday: profile.birthday || null, + lang: profile.lang || null, + isBot: !!profile.isBot, + isCat: !!profile.isCat, + alwaysMarkNsfw: !!profile.alwaysMarkNsfw, + }); +} + +function changeAvatar(ev) { + selectFile(ev.currentTarget || ev.target, i18n.locale.avatar).then(async (file) => { + const i = await os.apiWithDialog('i/update', { + avatarId: file.id, + }); + $i.avatarId = i.avatarId; + $i.avatarUrl = i.avatarUrl; + }); +} + +function changeBanner(ev) { + selectFile(ev.currentTarget || ev.target, i18n.locale.banner).then(async (file) => { + const i = await os.apiWithDialog('i/update', { + bannerId: file.id, + }); + $i.bannerId = i.bannerId; + $i.bannerUrl = i.bannerUrl; + }); +} + +async function editMetadata() { + const { canceled, result } = await os.form(i18n.locale._profile.metadata, { + fieldName0: { + type: 'string', + label: i18n.locale._profile.metadataLabel + ' 1', + default: additionalFields.fieldName0, + }, + fieldValue0: { + type: 'string', + label: i18n.locale._profile.metadataContent + ' 1', + default: additionalFields.fieldValue0, + }, + fieldName1: { + type: 'string', + label: i18n.locale._profile.metadataLabel + ' 2', + default: additionalFields.fieldName1, + }, + fieldValue1: { + type: 'string', + label: i18n.locale._profile.metadataContent + ' 2', + default: additionalFields.fieldValue1, + }, + fieldName2: { + type: 'string', + label: i18n.locale._profile.metadataLabel + ' 3', + default: additionalFields.fieldName2, + }, + fieldValue2: { + type: 'string', + label: i18n.locale._profile.metadataContent + ' 3', + default: additionalFields.fieldValue2, + }, + fieldName3: { + type: 'string', + label: i18n.locale._profile.metadataLabel + ' 4', + default: additionalFields.fieldName3, + }, + fieldValue3: { + type: 'string', + label: i18n.locale._profile.metadataContent + ' 4', + default: additionalFields.fieldValue3, + }, + }); + if (canceled) return; + + additionalFields.fieldName0 = result.fieldName0; + additionalFields.fieldValue0 = result.fieldValue0; + additionalFields.fieldName1 = result.fieldName1; + additionalFields.fieldValue1 = result.fieldValue1; + additionalFields.fieldName2 = result.fieldName2; + additionalFields.fieldValue2 = result.fieldValue2; + additionalFields.fieldName3 = result.fieldName3; + additionalFields.fieldValue3 = result.fieldValue3; + + const fields = [ + { name: additionalFields.fieldName0, value: additionalFields.fieldValue0 }, + { name: additionalFields.fieldName1, value: additionalFields.fieldValue1 }, + { name: additionalFields.fieldName2, value: additionalFields.fieldValue2 }, + { name: additionalFields.fieldName3, value: additionalFields.fieldValue3 }, + ]; + + os.api('i/update', { + fields, + }).then(i => { + os.success(); + }).catch(err => { + os.alert({ + type: 'error', + text: err.id + }); + }); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.profile, + icon: 'fas fa-user', + bg: 'var(--bg)', }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.profile, - icon: 'fas fa-user', - bg: 'var(--bg)', - }, - host, - langs, - name: null, - description: null, - birthday: null, - lang: null, - location: null, - fieldName0: null, - fieldValue0: null, - fieldName1: null, - fieldValue1: null, - fieldName2: null, - fieldValue2: null, - fieldName3: null, - fieldValue3: null, - avatarId: null, - bannerId: null, - isBot: false, - isCat: false, - alwaysMarkNsfw: false, - saving: false, - } - }, - - created() { - this.name = this.$i.name; - this.description = this.$i.description; - this.location = this.$i.location; - this.birthday = this.$i.birthday; - this.lang = this.$i.lang; - this.avatarId = this.$i.avatarId; - this.bannerId = this.$i.bannerId; - this.isBot = this.$i.isBot; - this.isCat = this.$i.isCat; - this.alwaysMarkNsfw = this.$i.alwaysMarkNsfw; - - this.fieldName0 = this.$i.fields[0] ? this.$i.fields[0].name : null; - this.fieldValue0 = this.$i.fields[0] ? this.$i.fields[0].value : null; - this.fieldName1 = this.$i.fields[1] ? this.$i.fields[1].name : null; - this.fieldValue1 = this.$i.fields[1] ? this.$i.fields[1].value : null; - this.fieldName2 = this.$i.fields[2] ? this.$i.fields[2].name : null; - this.fieldValue2 = this.$i.fields[2] ? this.$i.fields[2].value : null; - this.fieldName3 = this.$i.fields[3] ? this.$i.fields[3].name : null; - this.fieldValue3 = this.$i.fields[3] ? this.$i.fields[3].value : null; - - this.$watch('name', this.save); - this.$watch('description', this.save); - this.$watch('location', this.save); - this.$watch('birthday', this.save); - this.$watch('lang', this.save); - this.$watch('isBot', this.save); - this.$watch('isCat', this.save); - this.$watch('alwaysMarkNsfw', this.save); - }, - - methods: { - changeAvatar(e) { - selectFile(e.currentTarget || e.target, this.$ts.avatar).then(file => { - os.api('i/update', { - avatarId: file.id, - }); - }); - }, - - changeBanner(e) { - selectFile(e.currentTarget || e.target, this.$ts.banner).then(file => { - os.api('i/update', { - bannerId: file.id, - }); - }); - }, - - async editMetadata() { - const { canceled, result } = await os.form(this.$ts._profile.metadata, { - fieldName0: { - type: 'string', - label: this.$ts._profile.metadataLabel + ' 1', - default: this.fieldName0, - }, - fieldValue0: { - type: 'string', - label: this.$ts._profile.metadataContent + ' 1', - default: this.fieldValue0, - }, - fieldName1: { - type: 'string', - label: this.$ts._profile.metadataLabel + ' 2', - default: this.fieldName1, - }, - fieldValue1: { - type: 'string', - label: this.$ts._profile.metadataContent + ' 2', - default: this.fieldValue1, - }, - fieldName2: { - type: 'string', - label: this.$ts._profile.metadataLabel + ' 3', - default: this.fieldName2, - }, - fieldValue2: { - type: 'string', - label: this.$ts._profile.metadataContent + ' 3', - default: this.fieldValue2, - }, - fieldName3: { - type: 'string', - label: this.$ts._profile.metadataLabel + ' 4', - default: this.fieldName3, - }, - fieldValue3: { - type: 'string', - label: this.$ts._profile.metadataContent + ' 4', - default: this.fieldValue3, - }, - }); - if (canceled) return; - - this.fieldName0 = result.fieldName0; - this.fieldValue0 = result.fieldValue0; - this.fieldName1 = result.fieldName1; - this.fieldValue1 = result.fieldValue1; - this.fieldName2 = result.fieldName2; - this.fieldValue2 = result.fieldValue2; - this.fieldName3 = result.fieldName3; - this.fieldValue3 = result.fieldValue3; - - const fields = [ - { name: this.fieldName0, value: this.fieldValue0 }, - { name: this.fieldName1, value: this.fieldValue1 }, - { name: this.fieldName2, value: this.fieldValue2 }, - { name: this.fieldName3, value: this.fieldValue3 }, - ]; - - os.api('i/update', { - fields, - }).then(i => { - os.success(); - }).catch(err => { - os.alert({ - type: 'error', - text: err.id - }); - }); - }, - - save() { - this.saving = true; - - os.apiWithDialog('i/update', { - name: this.name || null, - description: this.description || null, - location: this.location || null, - birthday: this.birthday || null, - lang: this.lang || null, - isBot: !!this.isBot, - isCat: !!this.isCat, - alwaysMarkNsfw: !!this.alwaysMarkNsfw, - }).then(i => { - this.saving = false; - this.$i.avatarId = i.avatarId; - this.$i.avatarUrl = i.avatarUrl; - this.$i.bannerId = i.bannerId; - this.$i.bannerUrl = i.bannerUrl; - }).catch(err => { - this.saving = false; - }); - }, - } }); </script> diff --git a/packages/client/src/scripts/use-leave-guard.ts b/packages/client/src/scripts/use-leave-guard.ts index 21899af59a..3984256251 100644 --- a/packages/client/src/scripts/use-leave-guard.ts +++ b/packages/client/src/scripts/use-leave-guard.ts @@ -1,4 +1,5 @@ import { inject, onUnmounted, Ref } from 'vue'; +import { onBeforeRouteLeave } from 'vue-router'; import { i18n } from '@/i18n'; import * as os from '@/os'; @@ -16,6 +17,17 @@ export function useLeaveGuard(enabled: Ref<boolean>) { return canceled; }); + } else { + onBeforeRouteLeave(async (to, from) => { + if (!enabled.value) return true; + + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.locale.leaveConfirm, + }); + + return !canceled; + }); } /* From 7588397fb8ecc62e7e494479e1e190c4aa01aa7c Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Tue, 18 Jan 2022 21:30:17 +0900 Subject: [PATCH 50/81] wip: refactor(client): migrate components to composition api Fix #8155 --- packages/client/src/account.ts | 9 +- packages/client/src/pages/instance-info.vue | 137 +++++++------------ packages/client/src/scripts/get-user-menu.ts | 4 +- 3 files changed, 57 insertions(+), 93 deletions(-) diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index 4c83b78c91..76a0d2bd00 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -16,6 +16,8 @@ const data = localStorage.getItem('account'); // TODO: 外部からはreadonlyに export const $i = data ? reactive(JSON.parse(data) as Account) : null; +export const iAmModerator = $i != null && ($i.isAdmin || $i.isModerator); + export async function signout() { waiting(); localStorage.removeItem('account'); @@ -197,10 +199,3 @@ export async function openAccountMenu(ev: MouseEvent) { align: 'left' }); } - -// このファイルに書きたくないけどここに書かないと何故かVeturが認識しない -declare module '@vue/runtime-core' { - interface ComponentCustomProperties { - $i: typeof $i; - } -} diff --git a/packages/client/src/pages/instance-info.vue b/packages/client/src/pages/instance-info.vue index 475107ab6d..fa36db0659 100644 --- a/packages/client/src/pages/instance-info.vue +++ b/packages/client/src/pages/instance-info.vue @@ -28,7 +28,7 @@ <FormSection v-if="iAmModerator"> <template #label>Moderation</template> <FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.stopActivityDelivery }}</FormSwitch> - <FormSwitch :model-value="isBlocked" class="switch" @update:modelValue="changeBlock">{{ $ts.blockThisInstance }}</FormSwitch> + <FormSwitch v-model="isBlocked" class="_formBlock" @update:modelValue="toggleBlock">{{ $ts.blockThisInstance }}</FormSwitch> </FormSection> <FormSection> @@ -104,15 +104,14 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import MkChart from '@/components/chart.vue'; import MkObjectView from '@/components/object-view.vue'; -import FormTextarea from '@/components/form/textarea.vue'; import FormLink from '@/components/form/link.vue'; import MkLink from '@/components/link.vue'; import FormSection from '@/components/form/section.vue'; -import FormButton from '@/components/ui/button.vue'; import MkKeyValue from '@/components/key-value.vue'; import MkSelect from '@/components/form/select.vue'; import FormSwitch from '@/components/form/switch.vue'; @@ -120,87 +119,57 @@ import * as os from '@/os'; import number from '@/filters/number'; import bytes from '@/filters/bytes'; import * as symbols from '@/symbols'; +import { iAmModerator } from '@/account'; -export default defineComponent({ - components: { - FormTextarea, - MkObjectView, - FormButton, - FormLink, - FormSection, - FormSwitch, - MkKeyValue, - MkSelect, - MkChart, - MkLink, +const props = defineProps<{ + host: string; +}>(); + +let meta = $ref<misskey.entities.DetailedInstanceMetadata | null>(null); +let instance = $ref<misskey.entities.Instance | null>(null); +let suspended = $ref(false); +let isBlocked = $ref(false); +let chartSrc = $ref('instance-requests'); +let chartSpan = $ref('hour'); + +async function fetch() { + meta = await os.api('meta', { detail: true }); + instance = await os.api('federation/show-instance', { + host: props.host, + }); + suspended = instance.isSuspended; + isBlocked = meta.blockedHosts.includes(instance.host); +} + +async function toggleBlock(ev) { + if (meta == null) return; + await os.api('admin/update-meta', { + blockedHosts: isBlocked ? meta.blockedHosts.concat([instance.host]) : meta.blockedHosts.filter(x => x !== instance.host) + }); +} + +async function toggleSuspend(v) { + await os.api('admin/federation/update-instance', { + host: instance.host, + isSuspended: suspended, + }); +} + +fetch(); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: props.host, + icon: 'fas fa-info-circle', + bg: 'var(--bg)', + actions: [{ + text: `https://${props.host}`, + icon: 'fas fa-external-link-alt', + handler: () => { + window.open(`https://${props.host}`, '_blank'); + } + }], }, - - props: { - host: { - type: String, - required: true - } - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.host, - icon: 'fas fa-info-circle', - bg: 'var(--bg)', - actions: [{ - text: `https://${this.host}`, - icon: 'fas fa-external-link-alt', - handler: () => { - window.open(`https://${this.host}`, '_blank'); - } - }], - }, - instance: null, - suspended: false, - chartSrc: 'instance-requests', - chartSpan: 'hour', - } - }, - - computed: { - iAmModerator(): boolean { - return this.$i && (this.$i.isAdmin || this.$i.isModerator); - }, - - isBlocked() { - return this.instance && this.$instance && this.$instance.blockedHosts && this.$instance.blockedHosts.includes(this.instance.host); - } - }, - - mounted() { - this.fetch(); - }, - - methods: { - number, - bytes, - - async fetch() { - this.instance = await os.api('federation/show-instance', { - host: this.host - }); - this.suspended = this.instance.isSuspended; - }, - - changeBlock(e) { - os.api('admin/update-meta', { - blockedHosts: this.isBlocked ? this.meta.blockedHosts.concat([this.instance.host]) : this.meta.blockedHosts.filter(x => x !== this.instance.host) - }); - }, - - async toggleSuspend(v) { - await os.api('admin/federation/update-instance', { - host: this.instance.host, - isSuspended: this.suspended - }); - }, - } }); </script> diff --git a/packages/client/src/scripts/get-user-menu.ts b/packages/client/src/scripts/get-user-menu.ts index ebe101bc0f..7b910a0083 100644 --- a/packages/client/src/scripts/get-user-menu.ts +++ b/packages/client/src/scripts/get-user-menu.ts @@ -5,7 +5,7 @@ import * as Acct from 'misskey-js/built/acct'; import * as os from '@/os'; import { userActions } from '@/store'; import { router } from '@/router'; -import { $i } from '@/account'; +import { $i, iAmModerator } from '@/account'; export function getUserMenu(user) { const meId = $i ? $i.id : null; @@ -175,7 +175,7 @@ export function getUserMenu(user) { action: reportAbuse }]); - if ($i && ($i.isAdmin || $i.isModerator)) { + if (iAmModerator) { menu = menu.concat([null, { icon: 'fas fa-microphone-slash', text: user.isSilenced ? i18n.locale.unsilence : i18n.locale.silence, From 9869f01e7c87652822a26d3feb8e958a752acadd Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Tue, 18 Jan 2022 21:32:17 +0900 Subject: [PATCH 51/81] Fix #8151 (#8152) --- packages/client/src/components/note-detailed.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue index 07e9920f65..b33257915d 100644 --- a/packages/client/src/components/note-detailed.vue +++ b/packages/client/src/components/note-detailed.vue @@ -4,6 +4,7 @@ v-show="!isDeleted" v-hotkey="keymap" v-size="{ max: [500, 450, 350, 300] }" + ref="el" class="lxwezrsl _block" :tabindex="!isDeleted ? '-1' : null" :class="{ renote: isRenote }" From a8fad1b61c554b9e42d310a522f3c2c4e8879892 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Tue, 18 Jan 2022 21:35:57 +0900 Subject: [PATCH 52/81] refactor --- packages/client/src/components/modal-page-window.vue | 4 ++-- packages/client/src/components/note-detailed.vue | 8 ++++---- packages/client/src/components/note.vue | 8 ++++---- packages/client/src/components/ui/window.vue | 4 ++-- packages/client/src/ui/classic.side.vue | 4 ++-- packages/client/src/ui/classic.vue | 8 ++++---- packages/client/src/ui/deck/column.vue | 4 ++-- packages/client/src/ui/deck/main-column.vue | 8 ++++---- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/client/src/components/modal-page-window.vue b/packages/client/src/components/modal-page-window.vue index 3de1980820..2e17d5d030 100644 --- a/packages/client/src/components/modal-page-window.vue +++ b/packages/client/src/components/modal-page-window.vue @@ -153,8 +153,8 @@ export default defineComponent({ this.$refs.window.close(); }, - onContextmenu(e) { - os.contextMenu(this.contextmenu, e); + onContextmenu(ev: MouseEvent) { + os.contextMenu(this.contextmenu, ev); } }, }); diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue index b33257915d..a3b30f726e 100644 --- a/packages/client/src/components/note-detailed.vue +++ b/packages/client/src/components/note-detailed.vue @@ -223,21 +223,21 @@ function undoReact(note): void { }); } -function onContextmenu(e): void { +function onContextmenu(ev: MouseEvent): void { const isLink = (el: HTMLElement) => { if (el.tagName === 'A') return true; if (el.parentElement) { return isLink(el.parentElement); } }; - if (isLink(e.target)) return; + if (isLink(ev.target)) return; if (window.getSelection().toString() !== '') return; if (defaultStore.state.useReactionPickerForContextMenu) { - e.preventDefault(); + ev.preventDefault(); react(); } else { - os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), e).then(focus); + os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), ev).then(focus); } } diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue index b309afe051..fc89c2777b 100644 --- a/packages/client/src/components/note.vue +++ b/packages/client/src/components/note.vue @@ -211,21 +211,21 @@ function undoReact(note): void { }); } -function onContextmenu(e): void { +function onContextmenu(ev: MouseEvent): void { const isLink = (el: HTMLElement) => { if (el.tagName === 'A') return true; if (el.parentElement) { return isLink(el.parentElement); } }; - if (isLink(e.target)) return; + if (isLink(ev.target)) return; if (window.getSelection().toString() !== '') return; if (defaultStore.state.useReactionPickerForContextMenu) { - e.preventDefault(); + ev.preventDefault(); react(); } else { - os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), e).then(focus); + os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), ev).then(focus); } } diff --git a/packages/client/src/components/ui/window.vue b/packages/client/src/components/ui/window.vue index bd33289ccc..fa32ecfdef 100644 --- a/packages/client/src/components/ui/window.vue +++ b/packages/client/src/components/ui/window.vue @@ -147,9 +147,9 @@ export default defineComponent({ } }, - onContextmenu(e) { + onContextmenu(ev: MouseEvent) { if (this.contextmenu) { - os.contextMenu(this.contextmenu, e); + os.contextMenu(this.contextmenu, ev); } }, diff --git a/packages/client/src/ui/classic.side.vue b/packages/client/src/ui/classic.side.vue index ede658626a..f816834141 100644 --- a/packages/client/src/ui/classic.side.vue +++ b/packages/client/src/ui/classic.side.vue @@ -72,7 +72,7 @@ export default defineComponent({ this.props = {}; }, - onContextmenu(e) { + onContextmenu(ev: MouseEvent) { os.contextMenu([{ type: 'label', text: this.path, @@ -103,7 +103,7 @@ export default defineComponent({ action: () => { copyToClipboard(this.url); } - }], e); + }], ev); } } }); diff --git a/packages/client/src/ui/classic.vue b/packages/client/src/ui/classic.vue index 41da973152..1603ea6399 100644 --- a/packages/client/src/ui/classic.vue +++ b/packages/client/src/ui/classic.vue @@ -167,15 +167,15 @@ export default defineComponent({ if (window._scroll) window._scroll(); }, - onContextmenu(e) { + onContextmenu(ev: MouseEvent) { const isLink = (el: HTMLElement) => { if (el.tagName === 'A') return true; if (el.parentElement) { return isLink(el.parentElement); } }; - if (isLink(e.target)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return; + if (isLink(ev.target)) return; + if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; if (window.getSelection().toString() !== '') return; const path = this.$route.path; os.contextMenu([{ @@ -193,7 +193,7 @@ export default defineComponent({ action: () => { os.pageWindow(path); } - }], e); + }], ev); }, onAiClick(ev) { diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue index 1982d92ad3..f1ce3ca838 100644 --- a/packages/client/src/ui/deck/column.vue +++ b/packages/client/src/ui/deck/column.vue @@ -207,8 +207,8 @@ export default defineComponent({ return items; }, - onContextmenu(e) { - os.contextMenu(this.getMenu(), e); + onContextmenu(ev: MouseEvent) { + os.contextMenu(this.getMenu(), ev); }, goTop() { diff --git a/packages/client/src/ui/deck/main-column.vue b/packages/client/src/ui/deck/main-column.vue index 744056881c..b77d6f35cc 100644 --- a/packages/client/src/ui/deck/main-column.vue +++ b/packages/client/src/ui/deck/main-column.vue @@ -64,15 +64,15 @@ export default defineComponent({ history.back(); }, - onContextmenu(e) { + onContextmenu(ev: MouseEvent) { const isLink = (el: HTMLElement) => { if (el.tagName === 'A') return true; if (el.parentElement) { return isLink(el.parentElement); } }; - if (isLink(e.target)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return; + if (isLink(ev.target)) return; + if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; if (window.getSelection().toString() !== '') return; const path = this.$route.path; os.contextMenu([{ @@ -84,7 +84,7 @@ export default defineComponent({ action: () => { os.pageWindow(path); } - }], e); + }], ev); }, } }); From efb0ffc4ec1e0b9f6af922cee11481c28dd9a8d6 Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Tue, 18 Jan 2022 22:27:10 +0900 Subject: [PATCH 53/81] =?UTF-8?q?refactor:=20API=E3=82=A8=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=83=9D=E3=82=A4=E3=83=B3=E3=83=88=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=81=AE=E5=AE=9A=E7=BE=A9=E3=82=92=E8=89=AF?= =?UTF-8?q?=E3=81=84=E6=84=9F=E3=81=98=E3=81=AB=E3=81=99=E3=82=8B=20(#8154?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix API Schema Error * Delete SimpleSchema/SimpleObj and Move schemas to dedicated files * Userのスキーマを分割してみる * define packMany type * add , * Ensure enum schema and Make "as const" put once * test? * Revert "test?" This reverts commit 97dc9bfa70851bfb7d1cf38e883f8df20fb78b79. * Revert "Fix API Schema Error" This reverts commit 21b6176d974ed8e3eb73723ad21a105c5d297323. * :v: * clean up * test? * wip * wip * better schema def * :v: * fix * add minLength property * wip * wip * wip * anyOf/oneOf/allOfに対応? ~ relation.ts * refactor! * Define MinimumSchema * wip * wip * anyOf/oneOf/allOfが動作するようにUnionSchemaTypeを修正 * anyOf/oneOf/allOfが動作するようにUnionSchemaTypeを修正 * Update packages/backend/src/misc/schema.ts Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> * fix * array oneOfをより正確な型に * array oneOfをより正確な型に * wip * :v: * なんかもういろいろ * remove * very good schema * api schema * wip * refactor: awaitAllの型定義を変えてみる * fix * specify types in awaitAll * specify types in awaitAll * :v: * wip * ... * :v: * AllowDateはやめておく * 不必要なoptional: false, nullable: falseを廃止 * Packedが展開されないように * 続packed * wip * define note type * wip * UserDetailedをMeDetailedかUserDetailedNotMeかを区別できるように * wip * wip * wip specify user type of other schemas * ok * convertSchemaToOpenApiSchemaを改修 * convertSchemaToOpenApiSchemaを改修 * Fix * fix * :v: * wip * 分割代入ではなくallOfで定義するように Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> --- packages/backend/src/misc/schema.ts | 173 +++++-- packages/backend/src/misc/simple-schema.ts | 15 - .../src/models/repositories/antenna.ts | 91 ---- .../backend/src/models/repositories/app.ts | 35 -- .../src/models/repositories/blocking.ts | 28 -- .../src/models/repositories/channel.ts | 53 -- .../backend/src/models/repositories/clip.ts | 39 -- .../src/models/repositories/drive-file.ts | 113 +---- .../src/models/repositories/drive-folder.ts | 41 -- .../backend/src/models/repositories/emoji.ts | 38 -- .../repositories/federation-instance.ts | 104 ---- .../src/models/repositories/following.ts | 38 -- .../src/models/repositories/gallery-post.ts | 71 --- .../src/models/repositories/hashtag.ts | 36 -- .../models/repositories/messaging-message.ts | 75 --- .../backend/src/models/repositories/muting.ts | 28 -- .../src/models/repositories/note-favorite.ts | 28 -- .../src/models/repositories/note-reaction.ts | 27 - .../backend/src/models/repositories/note.ts | 187 +------ .../src/models/repositories/notification.ts | 66 --- .../backend/src/models/repositories/page.ts | 53 -- .../backend/src/models/repositories/queue.ts | 30 -- .../src/models/repositories/user-group.ts | 36 -- .../src/models/repositories/user-list.ts | 31 -- .../backend/src/models/repositories/user.ts | 348 +------------ packages/backend/src/models/schema/antenna.ts | 89 ++++ packages/backend/src/models/schema/app.ts | 33 ++ .../backend/src/models/schema/blocking.ts | 26 + packages/backend/src/models/schema/channel.ts | 51 ++ packages/backend/src/models/schema/clip.ts | 38 ++ .../backend/src/models/schema/drive-file.ts | 107 ++++ .../backend/src/models/schema/drive-folder.ts | 39 ++ packages/backend/src/models/schema/emoji.ts | 36 ++ .../src/models/schema/federation-instance.ts | 105 ++++ .../backend/src/models/schema/following.ts | 36 ++ .../backend/src/models/schema/gallery-post.ts | 69 +++ packages/backend/src/models/schema/hashtag.ts | 34 ++ .../src/models/schema/messaging-message.ts | 73 +++ packages/backend/src/models/schema/muting.ts | 26 + .../src/models/schema/note-favorite.ts | 26 + .../src/models/schema/note-reaction.ts | 25 + packages/backend/src/models/schema/note.ts | 183 +++++++ .../backend/src/models/schema/notification.ts | 66 +++ packages/backend/src/models/schema/page.ts | 51 ++ packages/backend/src/models/schema/queue.ts | 25 + .../backend/src/models/schema/user-group.ts | 34 ++ .../backend/src/models/schema/user-list.ts | 29 ++ packages/backend/src/models/schema/user.ts | 467 ++++++++++++++++++ packages/backend/src/prelude/await-all.ts | 14 +- packages/backend/src/server/api/endpoints.ts | 44 +- .../api/endpoints/admin/abuse-user-reports.ts | 52 +- .../api/endpoints/admin/accounts/create.ts | 10 +- .../api/endpoints/admin/accounts/delete.ts | 4 +- .../server/api/endpoints/admin/ad/create.ts | 4 +- .../server/api/endpoints/admin/ad/delete.ts | 4 +- .../src/server/api/endpoints/admin/ad/list.ts | 4 +- .../server/api/endpoints/admin/ad/update.ts | 4 +- .../endpoints/admin/announcements/create.ts | 32 +- .../endpoints/admin/announcements/delete.ts | 4 +- .../api/endpoints/admin/announcements/list.ts | 40 +- .../endpoints/admin/announcements/update.ts | 4 +- .../admin/delete-all-files-of-a-user.ts | 4 +- .../server/api/endpoints/admin/delete-logs.ts | 14 - .../admin/drive/clean-remote-files.ts | 4 +- .../api/endpoints/admin/drive/cleanup.ts | 4 +- .../server/api/endpoints/admin/drive/files.ts | 12 +- .../api/endpoints/admin/drive/show-file.ts | 112 ++--- .../endpoints/admin/emoji/add-aliases-bulk.ts | 4 +- .../server/api/endpoints/admin/emoji/add.ts | 4 +- .../server/api/endpoints/admin/emoji/copy.ts | 12 +- .../api/endpoints/admin/emoji/delete-bulk.ts | 4 +- .../api/endpoints/admin/emoji/delete.ts | 4 +- .../api/endpoints/admin/emoji/import-zip.ts | 4 +- .../api/endpoints/admin/emoji/list-remote.ts | 40 +- .../server/api/endpoints/admin/emoji/list.ts | 40 +- .../admin/emoji/remove-aliases-bulk.ts | 4 +- .../endpoints/admin/emoji/set-aliases-bulk.ts | 4 +- .../admin/emoji/set-category-bulk.ts | 4 +- .../api/endpoints/admin/emoji/update.ts | 4 +- .../admin/federation/delete-all-files.ts | 4 +- .../refresh-remote-instance-metadata.ts | 4 +- .../admin/federation/remove-all-following.ts | 4 +- .../admin/federation/update-instance.ts | 4 +- .../api/endpoints/admin/get-index-stats.ts | 4 +- .../api/endpoints/admin/get-table-stats.ts | 8 +- .../src/server/api/endpoints/admin/invite.ts | 12 +- .../api/endpoints/admin/moderators/add.ts | 4 +- .../api/endpoints/admin/moderators/remove.ts | 4 +- .../api/endpoints/admin/promo/create.ts | 4 +- .../server/api/endpoints/admin/queue/clear.ts | 4 +- .../endpoints/admin/queue/deliver-delayed.ts | 16 +- .../endpoints/admin/queue/inbox-delayed.ts | 16 +- .../server/api/endpoints/admin/queue/jobs.ts | 32 +- .../server/api/endpoints/admin/queue/stats.ts | 12 +- .../server/api/endpoints/admin/relays/add.ts | 22 +- .../server/api/endpoints/admin/relays/list.ts | 26 +- .../api/endpoints/admin/relays/remove.ts | 6 +- .../api/endpoints/admin/reset-password.ts | 12 +- .../admin/resolve-abuse-user-report.ts | 4 +- .../api/endpoints/admin/resync-chart.ts | 4 +- .../server/api/endpoints/admin/send-email.ts | 4 +- .../server/api/endpoints/admin/server-info.ts | 64 +-- .../endpoints/admin/show-moderation-logs.ts | 38 +- .../server/api/endpoints/admin/show-user.ts | 136 ++--- .../server/api/endpoints/admin/show-users.ts | 14 +- .../api/endpoints/admin/silence-user.ts | 4 +- .../api/endpoints/admin/suspend-user.ts | 4 +- .../api/endpoints/admin/unsilence-user.ts | 4 +- .../api/endpoints/admin/unsuspend-user.ts | 4 +- .../server/api/endpoints/admin/update-meta.ts | 4 +- .../src/server/api/endpoints/admin/vacuum.ts | 4 +- .../src/server/api/endpoints/announcements.ts | 40 +- .../server/api/endpoints/antennas/create.ts | 8 +- .../server/api/endpoints/antennas/delete.ts | 4 +- .../src/server/api/endpoints/antennas/list.ts | 12 +- .../server/api/endpoints/antennas/notes.ts | 12 +- .../src/server/api/endpoints/antennas/show.ts | 8 +- .../server/api/endpoints/antennas/update.ts | 8 +- .../src/server/api/endpoints/ap/get.ts | 8 +- .../src/server/api/endpoints/ap/show.ts | 57 ++- .../src/server/api/endpoints/app/create.ts | 8 +- .../src/server/api/endpoints/app/show.ts | 12 +- .../src/server/api/endpoints/auth/accept.ts | 4 +- .../api/endpoints/auth/session/generate.ts | 16 +- .../server/api/endpoints/auth/session/show.ts | 20 +- .../api/endpoints/auth/session/userkey.ts | 18 +- .../server/api/endpoints/blocking/create.ts | 10 +- .../server/api/endpoints/blocking/delete.ts | 10 +- .../src/server/api/endpoints/blocking/list.ts | 12 +- .../server/api/endpoints/channels/create.ts | 8 +- .../server/api/endpoints/channels/featured.ts | 12 +- .../server/api/endpoints/channels/follow.ts | 4 +- .../server/api/endpoints/channels/followed.ts | 12 +- .../server/api/endpoints/channels/owned.ts | 12 +- .../src/server/api/endpoints/channels/show.ts | 8 +- .../server/api/endpoints/channels/timeline.ts | 12 +- .../server/api/endpoints/channels/unfollow.ts | 4 +- .../server/api/endpoints/channels/update.ts | 8 +- .../api/endpoints/charts/active-users.ts | 2 +- .../src/server/api/endpoints/charts/drive.ts | 2 +- .../server/api/endpoints/charts/federation.ts | 2 +- .../server/api/endpoints/charts/hashtag.ts | 2 +- .../server/api/endpoints/charts/instance.ts | 2 +- .../server/api/endpoints/charts/network.ts | 2 +- .../src/server/api/endpoints/charts/notes.ts | 2 +- .../server/api/endpoints/charts/user/drive.ts | 2 +- .../api/endpoints/charts/user/following.ts | 2 +- .../server/api/endpoints/charts/user/notes.ts | 2 +- .../api/endpoints/charts/user/reactions.ts | 2 +- .../src/server/api/endpoints/charts/users.ts | 2 +- .../server/api/endpoints/clips/add-note.ts | 4 +- .../src/server/api/endpoints/clips/create.ts | 8 +- .../src/server/api/endpoints/clips/delete.ts | 4 +- .../src/server/api/endpoints/clips/list.ts | 12 +- .../src/server/api/endpoints/clips/notes.ts | 12 +- .../src/server/api/endpoints/clips/show.ts | 8 +- .../src/server/api/endpoints/clips/update.ts | 8 +- .../backend/src/server/api/endpoints/drive.ts | 16 +- .../src/server/api/endpoints/drive/files.ts | 12 +- .../endpoints/drive/files/attached-notes.ts | 12 +- .../endpoints/drive/files/check-existence.ts | 8 +- .../api/endpoints/drive/files/create.ts | 8 +- .../api/endpoints/drive/files/delete.ts | 4 +- .../api/endpoints/drive/files/find-by-hash.ts | 12 +- .../server/api/endpoints/drive/files/find.ts | 12 +- .../server/api/endpoints/drive/files/show.ts | 8 +- .../api/endpoints/drive/files/update.ts | 8 +- .../endpoints/drive/files/upload-from-url.ts | 4 +- .../src/server/api/endpoints/drive/folders.ts | 12 +- .../api/endpoints/drive/folders/create.ts | 4 +- .../api/endpoints/drive/folders/delete.ts | 4 +- .../api/endpoints/drive/folders/find.ts | 12 +- .../api/endpoints/drive/folders/show.ts | 8 +- .../api/endpoints/drive/folders/update.ts | 8 +- .../src/server/api/endpoints/drive/stream.ts | 12 +- .../api/endpoints/email-address/available.ts | 16 +- .../src/server/api/endpoints/endpoint.ts | 4 +- .../src/server/api/endpoints/endpoints.ts | 12 +- .../api/endpoints/export-custom-emojis.ts | 4 +- .../api/endpoints/federation/followers.ts | 12 +- .../api/endpoints/federation/following.ts | 12 +- .../api/endpoints/federation/instances.ts | 12 +- .../api/endpoints/federation/show-instance.ts | 8 +- .../federation/update-remote-user.ts | 4 +- .../server/api/endpoints/federation/users.ts | 14 +- .../server/api/endpoints/following/create.ts | 10 +- .../server/api/endpoints/following/delete.ts | 10 +- .../api/endpoints/following/invalidate.ts | 10 +- .../endpoints/following/requests/accept.ts | 4 +- .../endpoints/following/requests/cancel.ts | 10 +- .../api/endpoints/following/requests/list.ts | 28 +- .../endpoints/following/requests/reject.ts | 4 +- .../server/api/endpoints/gallery/featured.ts | 12 +- .../server/api/endpoints/gallery/popular.ts | 12 +- .../src/server/api/endpoints/gallery/posts.ts | 10 +- .../api/endpoints/gallery/posts/create.ts | 8 +- .../api/endpoints/gallery/posts/delete.ts | 4 +- .../api/endpoints/gallery/posts/like.ts | 4 +- .../api/endpoints/gallery/posts/show.ts | 8 +- .../api/endpoints/gallery/posts/unlike.ts | 4 +- .../api/endpoints/gallery/posts/update.ts | 8 +- .../api/endpoints/get-online-users-count.ts | 4 +- .../src/server/api/endpoints/hashtags/list.ts | 12 +- .../server/api/endpoints/hashtags/search.ts | 12 +- .../src/server/api/endpoints/hashtags/show.ts | 8 +- .../server/api/endpoints/hashtags/trend.ts | 28 +- .../server/api/endpoints/hashtags/users.ts | 14 +- .../backend/src/server/api/endpoints/i.ts | 12 +- .../src/server/api/endpoints/i/2fa/done.ts | 4 +- .../server/api/endpoints/i/2fa/key-done.ts | 4 +- .../api/endpoints/i/2fa/password-less.ts | 4 +- .../api/endpoints/i/2fa/register-key.ts | 4 +- .../server/api/endpoints/i/2fa/register.ts | 4 +- .../server/api/endpoints/i/2fa/remove-key.ts | 4 +- .../server/api/endpoints/i/2fa/unregister.ts | 4 +- .../src/server/api/endpoints/i/apps.ts | 4 +- .../server/api/endpoints/i/authorized-apps.ts | 4 +- .../server/api/endpoints/i/change-password.ts | 4 +- .../server/api/endpoints/i/delete-account.ts | 4 +- .../server/api/endpoints/i/export-blocking.ts | 4 +- .../api/endpoints/i/export-following.ts | 4 +- .../src/server/api/endpoints/i/export-mute.ts | 4 +- .../server/api/endpoints/i/export-notes.ts | 4 +- .../api/endpoints/i/export-user-lists.ts | 4 +- .../src/server/api/endpoints/i/favorites.ts | 12 +- .../server/api/endpoints/i/gallery/likes.ts | 16 +- .../server/api/endpoints/i/gallery/posts.ts | 12 +- .../endpoints/i/get-word-muted-notes-count.ts | 12 +- .../server/api/endpoints/i/import-blocking.ts | 4 +- .../api/endpoints/i/import-following.ts | 4 +- .../server/api/endpoints/i/import-muting.ts | 4 +- .../api/endpoints/i/import-user-lists.ts | 4 +- .../server/api/endpoints/i/notifications.ts | 12 +- .../src/server/api/endpoints/i/page-likes.ts | 16 +- .../src/server/api/endpoints/i/pages.ts | 12 +- .../backend/src/server/api/endpoints/i/pin.ts | 12 +- .../i/read-all-messaging-messages.ts | 4 +- .../api/endpoints/i/read-all-unread-notes.ts | 4 +- .../api/endpoints/i/read-announcement.ts | 4 +- .../api/endpoints/i/regenerate-token.ts | 4 +- .../api/endpoints/i/registry/get-all.ts | 4 +- .../api/endpoints/i/registry/get-detail.ts | 4 +- .../server/api/endpoints/i/registry/get.ts | 4 +- .../endpoints/i/registry/keys-with-type.ts | 4 +- .../server/api/endpoints/i/registry/keys.ts | 4 +- .../server/api/endpoints/i/registry/remove.ts | 4 +- .../server/api/endpoints/i/registry/scopes.ts | 4 +- .../server/api/endpoints/i/registry/set.ts | 4 +- .../server/api/endpoints/i/revoke-token.ts | 4 +- .../server/api/endpoints/i/signin-history.ts | 4 +- .../src/server/api/endpoints/i/unpin.ts | 12 +- .../server/api/endpoints/i/update-email.ts | 4 +- .../src/server/api/endpoints/i/update.ts | 12 +- .../api/endpoints/i/user-group-invites.ts | 20 +- .../server/api/endpoints/messaging/history.ts | 12 +- .../api/endpoints/messaging/messages.ts | 12 +- .../endpoints/messaging/messages/create.ts | 8 +- .../endpoints/messaging/messages/delete.ts | 4 +- .../api/endpoints/messaging/messages/read.ts | 4 +- .../backend/src/server/api/endpoints/meta.ts | 408 +++++++-------- .../server/api/endpoints/miauth/gen-token.ts | 12 +- .../src/server/api/endpoints/mute/create.ts | 4 +- .../src/server/api/endpoints/mute/delete.ts | 4 +- .../src/server/api/endpoints/mute/list.ts | 12 +- .../src/server/api/endpoints/my/apps.ts | 48 +- .../backend/src/server/api/endpoints/notes.ts | 10 +- .../server/api/endpoints/notes/children.ts | 12 +- .../src/server/api/endpoints/notes/clips.ts | 14 +- .../api/endpoints/notes/conversation.ts | 12 +- .../src/server/api/endpoints/notes/create.ts | 12 +- .../src/server/api/endpoints/notes/delete.ts | 4 +- .../api/endpoints/notes/favorites/create.ts | 4 +- .../api/endpoints/notes/favorites/delete.ts | 4 +- .../server/api/endpoints/notes/featured.ts | 12 +- .../api/endpoints/notes/global-timeline.ts | 10 +- .../api/endpoints/notes/hybrid-timeline.ts | 12 +- .../api/endpoints/notes/local-timeline.ts | 10 +- .../server/api/endpoints/notes/mentions.ts | 12 +- .../endpoints/notes/polls/recommendation.ts | 12 +- .../server/api/endpoints/notes/polls/vote.ts | 4 +- .../server/api/endpoints/notes/reactions.ts | 12 +- .../api/endpoints/notes/reactions/create.ts | 4 +- .../api/endpoints/notes/reactions/delete.ts | 4 +- .../src/server/api/endpoints/notes/renotes.ts | 12 +- .../src/server/api/endpoints/notes/replies.ts | 12 +- .../api/endpoints/notes/search-by-tag.ts | 10 +- .../src/server/api/endpoints/notes/search.ts | 12 +- .../src/server/api/endpoints/notes/show.ts | 8 +- .../src/server/api/endpoints/notes/state.ts | 20 +- .../endpoints/notes/thread-muting/create.ts | 4 +- .../endpoints/notes/thread-muting/delete.ts | 4 +- .../server/api/endpoints/notes/timeline.ts | 12 +- .../server/api/endpoints/notes/translate.ts | 8 +- .../server/api/endpoints/notes/unrenote.ts | 4 +- .../api/endpoints/notes/user-list-timeline.ts | 12 +- .../api/endpoints/notes/watching/create.ts | 4 +- .../api/endpoints/notes/watching/delete.ts | 4 +- .../api/endpoints/notifications/create.ts | 4 +- .../notifications/mark-all-as-read.ts | 4 +- .../api/endpoints/notifications/read.ts | 4 +- .../src/server/api/endpoints/page-push.ts | 4 +- .../src/server/api/endpoints/pages/create.ts | 8 +- .../src/server/api/endpoints/pages/delete.ts | 4 +- .../server/api/endpoints/pages/featured.ts | 12 +- .../src/server/api/endpoints/pages/like.ts | 4 +- .../src/server/api/endpoints/pages/show.ts | 8 +- .../src/server/api/endpoints/pages/unlike.ts | 4 +- .../src/server/api/endpoints/pages/update.ts | 4 +- .../backend/src/server/api/endpoints/ping.ts | 12 +- .../src/server/api/endpoints/pinned-users.ts | 14 +- .../src/server/api/endpoints/promo/read.ts | 4 +- .../api/endpoints/request-reset-password.ts | 4 +- .../src/server/api/endpoints/reset-db.ts | 4 +- .../server/api/endpoints/reset-password.ts | 4 +- .../src/server/api/endpoints/server-info.ts | 4 +- .../backend/src/server/api/endpoints/stats.ts | 36 +- .../src/server/api/endpoints/sw/register.ts | 16 +- .../src/server/api/endpoints/sw/unregister.ts | 4 +- .../api/endpoints/username/available.ts | 12 +- .../backend/src/server/api/endpoints/users.ts | 14 +- .../src/server/api/endpoints/users/clips.ts | 2 +- .../server/api/endpoints/users/followers.ts | 12 +- .../server/api/endpoints/users/following.ts | 12 +- .../api/endpoints/users/gallery/posts.ts | 2 +- .../users/get-frequently-replied-users.ts | 24 +- .../api/endpoints/users/groups/create.ts | 8 +- .../api/endpoints/users/groups/delete.ts | 4 +- .../users/groups/invitations/accept.ts | 4 +- .../users/groups/invitations/reject.ts | 4 +- .../api/endpoints/users/groups/invite.ts | 4 +- .../api/endpoints/users/groups/joined.ts | 12 +- .../api/endpoints/users/groups/leave.ts | 4 +- .../api/endpoints/users/groups/owned.ts | 12 +- .../server/api/endpoints/users/groups/pull.ts | 4 +- .../server/api/endpoints/users/groups/show.ts | 8 +- .../api/endpoints/users/groups/transfer.ts | 8 +- .../api/endpoints/users/groups/update.ts | 8 +- .../api/endpoints/users/lists/create.ts | 8 +- .../api/endpoints/users/lists/delete.ts | 4 +- .../server/api/endpoints/users/lists/list.ts | 12 +- .../server/api/endpoints/users/lists/pull.ts | 4 +- .../server/api/endpoints/users/lists/push.ts | 4 +- .../server/api/endpoints/users/lists/show.ts | 8 +- .../api/endpoints/users/lists/update.ts | 8 +- .../src/server/api/endpoints/users/notes.ts | 10 +- .../src/server/api/endpoints/users/pages.ts | 2 +- .../server/api/endpoints/users/reactions.ts | 12 +- .../api/endpoints/users/recommendation.ts | 14 +- .../server/api/endpoints/users/relation.ts | 79 ++- .../api/endpoints/users/report-abuse.ts | 4 +- .../users/search-by-username-and-host.ts | 14 +- .../src/server/api/endpoints/users/search.ts | 12 +- .../src/server/api/endpoints/users/show.ts | 23 +- .../src/server/api/endpoints/users/stats.ts | 4 +- .../backend/src/server/api/openapi/schemas.ts | 8 +- .../backend/src/server/api/stream/types.ts | 4 +- packages/backend/src/services/chart/core.ts | 14 +- .../backend/src/services/following/create.ts | 5 +- 358 files changed, 3623 insertions(+), 3417 deletions(-) delete mode 100644 packages/backend/src/misc/simple-schema.ts delete mode 100644 packages/backend/src/models/repositories/queue.ts create mode 100644 packages/backend/src/models/schema/antenna.ts create mode 100644 packages/backend/src/models/schema/app.ts create mode 100644 packages/backend/src/models/schema/blocking.ts create mode 100644 packages/backend/src/models/schema/channel.ts create mode 100644 packages/backend/src/models/schema/clip.ts create mode 100644 packages/backend/src/models/schema/drive-file.ts create mode 100644 packages/backend/src/models/schema/drive-folder.ts create mode 100644 packages/backend/src/models/schema/emoji.ts create mode 100644 packages/backend/src/models/schema/federation-instance.ts create mode 100644 packages/backend/src/models/schema/following.ts create mode 100644 packages/backend/src/models/schema/gallery-post.ts create mode 100644 packages/backend/src/models/schema/hashtag.ts create mode 100644 packages/backend/src/models/schema/messaging-message.ts create mode 100644 packages/backend/src/models/schema/muting.ts create mode 100644 packages/backend/src/models/schema/note-favorite.ts create mode 100644 packages/backend/src/models/schema/note-reaction.ts create mode 100644 packages/backend/src/models/schema/note.ts create mode 100644 packages/backend/src/models/schema/notification.ts create mode 100644 packages/backend/src/models/schema/page.ts create mode 100644 packages/backend/src/models/schema/queue.ts create mode 100644 packages/backend/src/models/schema/user-group.ts create mode 100644 packages/backend/src/models/schema/user-list.ts create mode 100644 packages/backend/src/models/schema/user.ts delete mode 100644 packages/backend/src/server/api/endpoints/admin/delete-logs.ts diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts index ae6f1bac86..2dae954af9 100644 --- a/packages/backend/src/misc/schema.ts +++ b/packages/backend/src/misc/schema.ts @@ -1,30 +1,44 @@ -import { SimpleObj, SimpleSchema } from './simple-schema'; -import { packedUserSchema } from '@/models/repositories/user'; -import { packedNoteSchema } from '@/models/repositories/note'; -import { packedUserListSchema } from '@/models/repositories/user-list'; -import { packedAppSchema } from '@/models/repositories/app'; -import { packedMessagingMessageSchema } from '@/models/repositories/messaging-message'; -import { packedNotificationSchema } from '@/models/repositories/notification'; -import { packedDriveFileSchema } from '@/models/repositories/drive-file'; -import { packedDriveFolderSchema } from '@/models/repositories/drive-folder'; -import { packedFollowingSchema } from '@/models/repositories/following'; -import { packedMutingSchema } from '@/models/repositories/muting'; -import { packedBlockingSchema } from '@/models/repositories/blocking'; -import { packedNoteReactionSchema } from '@/models/repositories/note-reaction'; -import { packedHashtagSchema } from '@/models/repositories/hashtag'; -import { packedPageSchema } from '@/models/repositories/page'; -import { packedUserGroupSchema } from '@/models/repositories/user-group'; -import { packedNoteFavoriteSchema } from '@/models/repositories/note-favorite'; -import { packedChannelSchema } from '@/models/repositories/channel'; -import { packedAntennaSchema } from '@/models/repositories/antenna'; -import { packedClipSchema } from '@/models/repositories/clip'; -import { packedFederationInstanceSchema } from '@/models/repositories/federation-instance'; -import { packedQueueCountSchema } from '@/models/repositories/queue'; -import { packedGalleryPostSchema } from '@/models/repositories/gallery-post'; -import { packedEmojiSchema } from '@/models/repositories/emoji'; +import { + packedUserLiteSchema, + packedUserDetailedNotMeOnlySchema, + packedMeDetailedOnlySchema, + packedUserDetailedNotMeSchema, + packedMeDetailedSchema, + packedUserDetailedSchema, + packedUserSchema, +} from '@/models/schema/user'; +import { packedNoteSchema } from '@/models/schema/note'; +import { packedUserListSchema } from '@/models/schema/user-list'; +import { packedAppSchema } from '@/models/schema/app'; +import { packedMessagingMessageSchema } from '@/models/schema/messaging-message'; +import { packedNotificationSchema } from '@/models/schema/notification'; +import { packedDriveFileSchema } from '@/models/schema/drive-file'; +import { packedDriveFolderSchema } from '@/models/schema/drive-folder'; +import { packedFollowingSchema } from '@/models/schema/following'; +import { packedMutingSchema } from '@/models/schema/muting'; +import { packedBlockingSchema } from '@/models/schema/blocking'; +import { packedNoteReactionSchema } from '@/models/schema/note-reaction'; +import { packedHashtagSchema } from '@/models/schema/hashtag'; +import { packedPageSchema } from '@/models/schema/page'; +import { packedUserGroupSchema } from '@/models/schema/user-group'; +import { packedNoteFavoriteSchema } from '@/models/schema/note-favorite'; +import { packedChannelSchema } from '@/models/schema/channel'; +import { packedAntennaSchema } from '@/models/schema/antenna'; +import { packedClipSchema } from '@/models/schema/clip'; +import { packedFederationInstanceSchema } from '@/models/schema/federation-instance'; +import { packedQueueCountSchema } from '@/models/schema/queue'; +import { packedGalleryPostSchema } from '@/models/schema/gallery-post'; +import { packedEmojiSchema } from '@/models/schema/emoji'; export const refs = { + UserLite: packedUserLiteSchema, + UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema, + MeDetailedOnly: packedMeDetailedOnlySchema, + UserDetailedNotMe: packedUserDetailedNotMeSchema, + MeDetailed: packedMeDetailedSchema, + UserDetailed: packedUserDetailedSchema, User: packedUserSchema, + UserList: packedUserListSchema, UserGroup: packedUserGroupSchema, App: packedAppSchema, @@ -49,12 +63,50 @@ export const refs = { Emoji: packedEmojiSchema, }; -export type Packed<x extends keyof typeof refs> = ObjType<(typeof refs[x])['properties']>; +// Packed = SchemaTypeDef<typeof refs[x]>; とすると展開されてマウスホバー時に型表示が使い物にならなくなる +// ObjType<r['properties']>を指定すると(なぜか)展開されずにPacked<'Hoge'>と表示される +type PackedDef<r extends { properties?: Obj; oneOf?: ReadonlyArray<MinimumSchema>; allOf?: ReadonlyArray<MinimumSchema> }> = + r['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<r['allOf']>> : + r['oneOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<r['oneOf']> : + r['properties'] extends Obj ? ObjType<r['properties']> : + never; +export type Packed<x extends keyof typeof refs> = PackedDef<typeof refs[x]>; -export interface Schema extends SimpleSchema { - items?: Schema; - properties?: Obj; - ref?: keyof typeof refs; +type TypeStringef = 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any'; +type StringDefToType<T extends TypeStringef> = + T extends 'boolean' ? boolean : + T extends 'number' ? number : + T extends 'string' ? string | Date : + T extends 'array' ? ReadonlyArray<any> : + T extends 'object' ? Record<string, any> : + any; + +// https://swagger.io/specification/?sbsearch=optional#schema-object +type OfSchema = { + readonly anyOf?: ReadonlyArray<MinimumSchema>; + readonly oneOf?: ReadonlyArray<MinimumSchema>; + readonly allOf?: ReadonlyArray<MinimumSchema>; +} + +export interface MinimumSchema extends OfSchema { + readonly type?: TypeStringef; + readonly nullable?: boolean; + readonly optional?: boolean; + readonly items?: MinimumSchema; + readonly properties?: Obj; + readonly description?: string; + readonly example?: any; + readonly format?: string; + readonly ref?: keyof typeof refs; + readonly enum?: ReadonlyArray<string>; + readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null; + readonly maxLength?: number; + readonly minLength?: number; +} + +export interface Schema extends MinimumSchema { + readonly nullable: boolean; + readonly optional: boolean; } type NonUndefinedPropertyNames<T extends Obj> = { @@ -65,22 +117,13 @@ type UndefinedPropertyNames<T extends Obj> = { [K in keyof T]: T[K]['optional'] extends true ? K : never }[keyof T]; -type OnlyRequired<T extends Obj> = Pick<T, NonUndefinedPropertyNames<T>>; -type OnlyOptional<T extends Obj> = Pick<T, UndefinedPropertyNames<T>>; - -export interface Obj extends SimpleObj { [key: string]: Schema; } +export interface Obj { [key: string]: Schema; } export type ObjType<s extends Obj> = - { [P in keyof OnlyOptional<s>]?: SchemaType<s[P]> } & - { [P in keyof OnlyRequired<s>]: SchemaType<s[P]> }; + { -readonly [P in UndefinedPropertyNames<s>]?: SchemaType<s[P]> } & + { -readonly [P in NonUndefinedPropertyNames<s>]: SchemaType<s[P]> }; -// https://qiita.com/hrsh7th@github/items/84e8968c3601009cdcf2 -type MyType<T extends Schema> = { - 0: any; - 1: SchemaType<T>; -}[T extends Schema ? 1 : 0]; - -type NullOrUndefined<p extends Schema, T> = +type NullOrUndefined<p extends MinimumSchema, T> = p['nullable'] extends true ? p['optional'] extends true ? (T | null | undefined) @@ -89,15 +132,41 @@ type NullOrUndefined<p extends Schema, T> = ? (T | undefined) : T; -export type SchemaType<p extends Schema> = - p['type'] extends 'number' ? NullOrUndefined<p, number> : - p['type'] extends 'string' ? NullOrUndefined<p, string> : - p['type'] extends 'boolean' ? NullOrUndefined<p, boolean> : - p['type'] extends 'array' ? NullOrUndefined<p, MyType<NonNullable<p['items']>>[]> : - p['type'] extends 'object' ? ( - p['ref'] extends keyof typeof refs - ? NullOrUndefined<p, Packed<p['ref']>> - : NullOrUndefined<p, ObjType<NonNullable<p['properties']>>> +// 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection +type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; + +// https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552 +// 単純にSchemaTypeDef<X>で判定するだけではダメ +type UnionSchemaType<a extends readonly any[], X extends MinimumSchema = a[number]> = X extends any ? SchemaType<X> : never; +type ArrayUnion<T> = T extends any ? Array<T> : never; + +export type SchemaTypeDef<p extends MinimumSchema> = + p['type'] extends 'number' ? number : + p['type'] extends 'string' ? ( + p['enum'] extends readonly string[] ? + p['enum'][number] : + p['format'] extends 'date-time' ? string : // Dateにする?? + string ) : - p['type'] extends 'any' ? NullOrUndefined<p, any> : + p['type'] extends 'boolean' ? boolean : + p['type'] extends 'object' ? ( + p['ref'] extends keyof typeof refs ? Packed<p['ref']> : + p['properties'] extends NonNullable<Obj> ? ObjType<p['properties']> : + p['anyOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<p['anyOf']> & Partial<UnionToIntersection<UnionSchemaType<p['anyOf']>>> : + p['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> : + any + ) : + p['type'] extends 'array' ? ( + p['items'] extends OfSchema ? ( + p['items']['anyOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<NonNullable<p['items']['anyOf']>>[] : + p['items']['oneOf'] extends ReadonlyArray<MinimumSchema> ? ArrayUnion<UnionSchemaType<NonNullable<p['items']['oneOf']>>> : + p['items']['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] : + never + ) : + p['items'] extends NonNullable<MinimumSchema> ? SchemaTypeDef<p['items']>[] : + any[] + ) : + p['oneOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<p['oneOf']> : any; + +export type SchemaType<p extends MinimumSchema> = NullOrUndefined<p, SchemaTypeDef<p>>; diff --git a/packages/backend/src/misc/simple-schema.ts b/packages/backend/src/misc/simple-schema.ts deleted file mode 100644 index abbb348e24..0000000000 --- a/packages/backend/src/misc/simple-schema.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface SimpleSchema { - type: 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any'; - nullable: boolean; - optional: boolean; - items?: SimpleSchema; - properties?: SimpleObj; - description?: string; - example?: any; - format?: string; - ref?: string; - enum?: string[]; - default?: boolean | null; -} - -export interface SimpleObj { [key: string]: SimpleSchema; } diff --git a/packages/backend/src/models/repositories/antenna.ts b/packages/backend/src/models/repositories/antenna.ts index 548f44f1b7..3bf0645a7f 100644 --- a/packages/backend/src/models/repositories/antenna.ts +++ b/packages/backend/src/models/repositories/antenna.ts @@ -31,94 +31,3 @@ export class AntennaRepository extends Repository<Antenna> { }; } } - -export const packedAntennaSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - keywords: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - excludeKeywords: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - src: { - type: 'string' as const, - optional: false as const, nullable: false as const, - enum: ['home', 'all', 'users', 'list', 'group'], - }, - userListId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - userGroupId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - users: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - caseSensitive: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - notify: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - withReplies: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - withFile: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - hasUnreadNote: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/app.ts b/packages/backend/src/models/repositories/app.ts index bec0765ac2..6bac4d9598 100644 --- a/packages/backend/src/models/repositories/app.ts +++ b/packages/backend/src/models/repositories/app.ts @@ -38,38 +38,3 @@ export class AppRepository extends Repository<App> { }; } } - -export const packedAppSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - callbackUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - permission: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - secret: { - type: 'string' as const, - optional: true as const, nullable: false as const, - }, - isAuthorized: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/blocking.ts b/packages/backend/src/models/repositories/blocking.ts index a6895eabf4..c20b02f501 100644 --- a/packages/backend/src/models/repositories/blocking.ts +++ b/packages/backend/src/models/repositories/blocking.ts @@ -30,31 +30,3 @@ export class BlockingRepository extends Repository<Blocking> { return Promise.all(blockings.map(x => this.pack(x, me))); } } - -export const packedBlockingSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - blockeeId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - blockee: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/channel.ts b/packages/backend/src/models/repositories/channel.ts index 0a6b02f495..b3afb823ab 100644 --- a/packages/backend/src/models/repositories/channel.ts +++ b/packages/backend/src/models/repositories/channel.ts @@ -40,56 +40,3 @@ export class ChannelRepository extends Repository<Channel> { }; } } - -export const packedChannelSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - lastNotedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - description: { - type: 'string' as const, - nullable: true as const, optional: false as const, - }, - bannerUrl: { - type: 'string' as const, - format: 'url', - nullable: true as const, optional: false as const, - }, - notesCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, - }, - usersCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, - }, - isFollowing: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - userId: { - type: 'string' as const, - nullable: true as const, optional: false as const, - format: 'id', - }, - }, -}; diff --git a/packages/backend/src/models/repositories/clip.ts b/packages/backend/src/models/repositories/clip.ts index 7892811d48..6f9ceeb50a 100644 --- a/packages/backend/src/models/repositories/clip.ts +++ b/packages/backend/src/models/repositories/clip.ts @@ -29,42 +29,3 @@ export class ClipRepository extends Repository<Clip> { } } -export const packedClipSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: false as const, nullable: false as const, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - description: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - isPublic: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts index 79b890aa6e..44db9a0a58 100644 --- a/packages/backend/src/models/repositories/drive-file.ts +++ b/packages/backend/src/models/repositories/drive-file.ts @@ -3,7 +3,7 @@ import { DriveFile } from '@/models/entities/drive-file'; import { Users, DriveFolders } from '../index'; import { User } from '@/models/entities/user'; import { toPuny } from '@/misc/convert-host'; -import { awaitAll } from '@/prelude/await-all'; +import { awaitAll, Promiseable } from '@/prelude/await-all'; import { Packed } from '@/misc/schema'; import config from '@/config/index'; import { query, appendQuery } from '@/prelude/url'; @@ -126,7 +126,7 @@ export class DriveFileRepository extends Repository<DriveFile> { const meta = await fetchMeta(); - return await awaitAll({ + return await awaitAll<Packed<'DriveFile'>>({ id: file.id, createdAt: file.createdAt.toISOString(), name: file.name, @@ -156,112 +156,3 @@ export class DriveFileRepository extends Repository<DriveFile> { return items.filter(x => x != null); } } - -export const packedDriveFileSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - example: 'lenna.jpg', - }, - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - example: 'image/jpeg', - }, - md5: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'md5', - example: '15eca7fba0480996e2245f5185bf39f2', - }, - size: { - type: 'number' as const, - optional: false as const, nullable: false as const, - example: 51469, - }, - isSensitive: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - blurhash: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - properties: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - width: { - type: 'number' as const, - optional: true as const, nullable: false as const, - example: 1280, - }, - height: { - type: 'number' as const, - optional: true as const, nullable: false as const, - example: 720, - }, - orientation: { - type: 'number' as const, - optional: true as const, nullable: false as const, - example: 8, - }, - avgColor: { - type: 'string' as const, - optional: true as const, nullable: false as const, - example: 'rgb(40,65,87)', - }, - }, - }, - url: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'url', - }, - thumbnailUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'url', - }, - comment: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - folderId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - folder: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'DriveFolder' as const, - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - user: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/drive-folder.ts b/packages/backend/src/models/repositories/drive-folder.ts index 4ee4a68e08..b2e6cee9b8 100644 --- a/packages/backend/src/models/repositories/drive-folder.ts +++ b/packages/backend/src/models/repositories/drive-folder.ts @@ -48,44 +48,3 @@ export class DriveFolderRepository extends Repository<DriveFolder> { }); } } - -export const packedDriveFolderSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - foldersCount: { - type: 'number' as const, - optional: true as const, nullable: false as const, - }, - filesCount: { - type: 'number' as const, - optional: true as const, nullable: false as const, - }, - parentId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - parent: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'DriveFolder' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/emoji.ts b/packages/backend/src/models/repositories/emoji.ts index b7529595a9..9d63ced811 100644 --- a/packages/backend/src/models/repositories/emoji.ts +++ b/packages/backend/src/models/repositories/emoji.ts @@ -25,41 +25,3 @@ export class EmojiRepository extends Repository<Emoji> { return Promise.all(emojis.map(x => this.pack(x))); } } - -export const packedEmojiSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - aliases: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - category: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - host: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - url: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/federation-instance.ts b/packages/backend/src/models/repositories/federation-instance.ts index 90dbbaab1c..426fd5bfc3 100644 --- a/packages/backend/src/models/repositories/federation-instance.ts +++ b/packages/backend/src/models/repositories/federation-instance.ts @@ -1,106 +1,2 @@ import config from '@/config/index'; -export const packedFederationInstanceSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - caughtAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - host: { - type: 'string' as const, - optional: false as const, nullable: false as const, - example: 'misskey.example.com', - }, - usersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - notesCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - followingCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - followersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - driveUsage: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - driveFiles: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - latestRequestSentAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'date-time', - }, - lastCommunicatedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - isNotResponding: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - isSuspended: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - softwareName: { - type: 'string' as const, - optional: false as const, nullable: true as const, - example: 'misskey', - }, - softwareVersion: { - type: 'string' as const, - optional: false as const, nullable: true as const, - example: config.version, - }, - openRegistrations: { - type: 'boolean' as const, - optional: false as const, nullable: true as const, - example: true, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - description: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - maintainerName: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - maintainerEmail: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - iconUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'url', - }, - infoUpdatedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'date-time', - }, - }, -}; diff --git a/packages/backend/src/models/repositories/following.ts b/packages/backend/src/models/repositories/following.ts index 1dfaaf908a..9d20f442df 100644 --- a/packages/backend/src/models/repositories/following.ts +++ b/packages/backend/src/models/repositories/following.ts @@ -84,41 +84,3 @@ export class FollowingRepository extends Repository<Following> { return Promise.all(followings.map(x => this.pack(x, me, opts))); } } - -export const packedFollowingSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - followeeId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - followee: { - type: 'object' as const, - optional: true as const, nullable: false as const, - ref: 'User' as const, - }, - followerId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - follower: { - type: 'object' as const, - optional: true as const, nullable: false as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/gallery-post.ts b/packages/backend/src/models/repositories/gallery-post.ts index 6d37e3120e..e9233bb91e 100644 --- a/packages/backend/src/models/repositories/gallery-post.ts +++ b/packages/backend/src/models/repositories/gallery-post.ts @@ -38,74 +38,3 @@ export class GalleryPostRepository extends Repository<GalleryPost> { return Promise.all(posts.map(x => this.pack(x, me))); } } - -export const packedGalleryPostSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - updatedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - title: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - description: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: false as const, nullable: false as const, - }, - fileIds: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - files: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'DriveFile' as const, - }, - }, - tags: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - isSensitive: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/hashtag.ts b/packages/backend/src/models/repositories/hashtag.ts index 6e513c7ebb..c4b8d50c4e 100644 --- a/packages/backend/src/models/repositories/hashtag.ts +++ b/packages/backend/src/models/repositories/hashtag.ts @@ -24,39 +24,3 @@ export class HashtagRepository extends Repository<Hashtag> { return Promise.all(hashtags.map(x => this.pack(x))); } } - -export const packedHashtagSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - tag: { - type: 'string' as const, - optional: false as const, nullable: false as const, - example: 'misskey', - }, - mentionedUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - mentionedLocalUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - mentionedRemoteUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - attachedUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - attachedLocalUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - attachedRemoteUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/messaging-message.ts b/packages/backend/src/models/repositories/messaging-message.ts index 1b2dd3a246..0a342430b9 100644 --- a/packages/backend/src/models/repositories/messaging-message.ts +++ b/packages/backend/src/models/repositories/messaging-message.ts @@ -42,78 +42,3 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> { }; } } - -export const packedMessagingMessageSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: true as const, nullable: false as const, - }, - text: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - fileId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - }, - file: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'DriveFile' as const, - }, - recipientId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - recipient: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'User' as const, - }, - groupId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - group: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'UserGroup' as const, - }, - isRead: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - reads: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/muting.ts b/packages/backend/src/models/repositories/muting.ts index b82d1f0daa..bdbe9b47da 100644 --- a/packages/backend/src/models/repositories/muting.ts +++ b/packages/backend/src/models/repositories/muting.ts @@ -30,31 +30,3 @@ export class MutingRepository extends Repository<Muting> { return Promise.all(mutings.map(x => this.pack(x, me))); } } - -export const packedMutingSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - muteeId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - mutee: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/note-favorite.ts b/packages/backend/src/models/repositories/note-favorite.ts index 47586a9116..c5de55c0c0 100644 --- a/packages/backend/src/models/repositories/note-favorite.ts +++ b/packages/backend/src/models/repositories/note-favorite.ts @@ -26,31 +26,3 @@ export class NoteFavoriteRepository extends Repository<NoteFavorite> { return Promise.all(favorites.map(x => this.pack(x, me))); } } - -export const packedNoteFavoriteSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - note: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'Note' as const, - }, - noteId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, -}; diff --git a/packages/backend/src/models/repositories/note-reaction.ts b/packages/backend/src/models/repositories/note-reaction.ts index dfb25cbea1..097574effa 100644 --- a/packages/backend/src/models/repositories/note-reaction.ts +++ b/packages/backend/src/models/repositories/note-reaction.ts @@ -31,30 +31,3 @@ export class NoteReactionRepository extends Repository<NoteReaction> { }; } } - -export const packedNoteReactionSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - user: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index 96dfad70e9..9a7fef4977 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -218,7 +218,7 @@ export class NoteRepository extends Repository<Note> { const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, '')); - const packed = await awaitAll({ + const packed: Packed<'Note'> = await awaitAll({ id: note.id, createdAt: note.createdAt.toISOString(), userId: note.userId, @@ -320,188 +320,3 @@ export class NoteRepository extends Repository<Note> { }))); } } - -export const packedNoteSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - text: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - cw: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: false as const, nullable: false as const, - }, - replyId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - renoteId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - reply: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'Note' as const, - }, - renote: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'Note' as const, - }, - isHidden: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - visibility: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - mentions: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - visibleUserIds: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - fileIds: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - files: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'DriveFile' as const, - }, - }, - tags: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - poll: { - type: 'object' as const, - optional: true as const, nullable: true as const, - }, - channelId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - channel: { - type: 'object' as const, - optional: true as const, nullable: true as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - }, - }, - }, - localOnly: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - emojis: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - url: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - }, - }, - }, - reactions: { - type: 'object' as const, - optional: false as const, nullable: false as const, - }, - renoteCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - repliesCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - uri: { - type: 'string' as const, - optional: true as const, nullable: false as const, - }, - url: { - type: 'string' as const, - optional: true as const, nullable: false as const, - }, - - myReaction: { - type: 'object' as const, - optional: true as const, nullable: true as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/notification.ts b/packages/backend/src/models/repositories/notification.ts index 47d569ed21..5e42798898 100644 --- a/packages/backend/src/models/repositories/notification.ts +++ b/packages/backend/src/models/repositories/notification.ts @@ -107,69 +107,3 @@ export class NotificationRepository extends Repository<Notification> { }))); } } - -export const packedNotificationSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - isRead: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - enum: [...notificationTypes], - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: true as const, nullable: true as const, - }, - userId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - }, - note: { - type: 'object' as const, - ref: 'Note' as const, - optional: true as const, nullable: true as const, - }, - reaction: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - choice: { - type: 'number' as const, - optional: true as const, nullable: true as const, - }, - invitation: { - type: 'object' as const, - optional: true as const, nullable: true as const, - }, - body: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - header: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - icon: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/page.ts b/packages/backend/src/models/repositories/page.ts index 46b453cad9..ec76c2e418 100644 --- a/packages/backend/src/models/repositories/page.ts +++ b/packages/backend/src/models/repositories/page.ts @@ -87,56 +87,3 @@ export class PageRepository extends Repository<Page> { return Promise.all(pages.map(x => this.pack(x, me))); } } - -export const packedPageSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - updatedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - title: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - summary: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - content: { - type: 'array' as const, - optional: false as const, nullable: false as const, - }, - variables: { - type: 'array' as const, - optional: false as const, nullable: false as const, - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/queue.ts b/packages/backend/src/models/repositories/queue.ts deleted file mode 100644 index 521c634390..0000000000 --- a/packages/backend/src/models/repositories/queue.ts +++ /dev/null @@ -1,30 +0,0 @@ -export const packedQueueCountSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - waiting: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - active: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - completed: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - failed: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - delayed: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - paused: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/user-group.ts b/packages/backend/src/models/repositories/user-group.ts index 02a0348885..3ed37ca0ed 100644 --- a/packages/backend/src/models/repositories/user-group.ts +++ b/packages/backend/src/models/repositories/user-group.ts @@ -23,39 +23,3 @@ export class UserGroupRepository extends Repository<UserGroup> { }; } } - -export const packedUserGroupSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - ownerId: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - }, - userIds: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - }, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/user-list.ts b/packages/backend/src/models/repositories/user-list.ts index 792a17cb49..a2bffe8357 100644 --- a/packages/backend/src/models/repositories/user-list.ts +++ b/packages/backend/src/models/repositories/user-list.ts @@ -22,34 +22,3 @@ export class UserListRepository extends Repository<UserList> { }; } } - -export const packedUserListSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - userIds: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - }, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 85141cdc41..2b8398832d 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -4,11 +4,19 @@ import { User, ILocalUser, IRemoteUser } from '@/models/entities/user'; import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances } from '../index'; import config from '@/config/index'; import { Packed } from '@/misc/schema'; -import { awaitAll } from '@/prelude/await-all'; +import { awaitAll, Promiseable } from '@/prelude/await-all'; import { populateEmojis } from '@/misc/populate-emojis'; import { getAntennas } from '@/misc/antenna-cache'; import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const'; +type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>; +type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> = + Detailed extends true ? + ExpectsMe extends true ? Packed<'MeDetailed'> : + ExpectsMe extends false ? Packed<'UserDetailedNotMe'> : + Packed<'UserDetailed'> : + Packed<'UserLite'>; + @EntityRepository(User) export class UserRepository extends Repository<User> { public async getRelation(me: User['id'], target: User['id']) { @@ -144,7 +152,7 @@ export class UserRepository extends Repository<User> { return count > 0; } - public getOnlineStatus(user: User): string { + public getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' { if (user.hideOnlineStatus) return 'unknown'; if (user.lastActiveDate == null) return 'unknown'; const elapsed = Date.now() - user.lastActiveDate.getTime(); @@ -163,14 +171,14 @@ export class UserRepository extends Repository<User> { } } - public async pack( + public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>( src: User['id'] | User, me?: { id: User['id'] } | null | undefined, options?: { - detail?: boolean, + detail?: D, includeSecrets?: boolean, } - ): Promise<Packed<'User'>> { + ): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> { const opts = Object.assign({ detail: false, includeSecrets: false, @@ -178,8 +186,9 @@ export class UserRepository extends Repository<User> { const user = typeof src === 'object' ? src : await this.findOneOrFail(src); const meId = me ? me.id : null; + const isMe = meId === user.id; - const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null; + const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null; const pins = opts.detail ? await UserNotePinings.createQueryBuilder('pin') .where('pin.userId = :userId', { userId: user.id }) .innerJoinAndSelect('pin.note', 'note') @@ -188,12 +197,12 @@ export class UserRepository extends Repository<User> { const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null; const followingCount = profile == null ? null : - (profile.ffVisibility === 'public') || (meId === user.id) ? user.followingCount : + (profile.ffVisibility === 'public') || isMe ? user.followingCount : (profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount : null; const followersCount = profile == null ? null : - (profile.ffVisibility === 'public') || (meId === user.id) ? user.followersCount : + (profile.ffVisibility === 'public') || isMe ? user.followersCount : (profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount : null; @@ -227,12 +236,11 @@ export class UserRepository extends Repository<User> { uri: user.uri, createdAt: user.createdAt.toISOString(), updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, - lastFetchedAt: user.lastFetchedAt?.toISOString(), + lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null, bannerUrl: user.bannerUrl, bannerBlurhash: user.bannerBlurhash, bannerColor: null, // 後方互換性のため isLocked: user.isLocked, - isModerator: user.isModerator || falsy, isSilenced: user.isSilenced || falsy, isSuspended: user.isSuspended || falsy, description: profile!.description, @@ -260,7 +268,7 @@ export class UserRepository extends Repository<User> { : false, } : {}), - ...(opts.detail && meId === user.id ? { + ...(opts.detail && isMe ? { avatarId: user.avatarId, bannerId: user.bannerId, injectFeaturedNote: profile!.injectFeaturedNote, @@ -315,19 +323,19 @@ export class UserRepository extends Repository<User> { isBlocked: relation.isBlocked, isMuted: relation.isMuted, } : {}), - }; + } as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>; return await awaitAll(packed); } - public packMany( + public packMany<D extends boolean = false>( users: (User['id'] | User)[], me?: { id: User['id'] } | null | undefined, options?: { - detail?: boolean, + detail?: D, includeSecrets?: boolean, } - ) { + ): Promise<IsUserDetailed<D>[]> { return Promise.all(users.map(u => this.pack(u, me, options))); } @@ -352,313 +360,3 @@ export class UserRepository extends Repository<User> { public validateBirthday = $.str.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/); //#endregion } - -export const packedUserSchema = { - type: 'object' as const, - nullable: false as const, optional: false as const, - properties: { - id: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - name: { - type: 'string' as const, - nullable: true as const, optional: false as const, - example: '藍', - }, - username: { - type: 'string' as const, - nullable: false as const, optional: false as const, - example: 'ai', - }, - host: { - type: 'string' as const, - nullable: true as const, optional: false as const, - example: 'misskey.example.com', - }, - avatarUrl: { - type: 'string' as const, - format: 'url', - nullable: true as const, optional: false as const, - }, - avatarBlurhash: { - type: 'any' as const, - nullable: true as const, optional: false as const, - }, - avatarColor: { - type: 'any' as const, - nullable: true as const, optional: false as const, - default: null, - }, - isAdmin: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - isModerator: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - isBot: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - isCat: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - emojis: { - type: 'array' as const, - nullable: false as const, optional: false as const, - items: { - type: 'object' as const, - nullable: false as const, optional: false as const, - properties: { - name: { - type: 'string' as const, - nullable: false as const, optional: false as const, - }, - url: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'url', - }, - }, - }, - }, - url: { - type: 'string' as const, - format: 'url', - nullable: true as const, optional: true as const, - }, - createdAt: { - type: 'string' as const, - nullable: false as const, optional: true as const, - format: 'date-time', - }, - updatedAt: { - type: 'string' as const, - nullable: true as const, optional: true as const, - format: 'date-time', - }, - bannerUrl: { - type: 'string' as const, - format: 'url', - nullable: true as const, optional: true as const, - }, - bannerBlurhash: { - type: 'any' as const, - nullable: true as const, optional: true as const, - }, - bannerColor: { - type: 'any' as const, - nullable: true as const, optional: true as const, - default: null, - }, - isLocked: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - isSuspended: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - example: false, - }, - description: { - type: 'string' as const, - nullable: true as const, optional: true as const, - example: 'Hi masters, I am Ai!', - }, - location: { - type: 'string' as const, - nullable: true as const, optional: true as const, - }, - birthday: { - type: 'string' as const, - nullable: true as const, optional: true as const, - example: '2018-03-12', - }, - fields: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'object' as const, - nullable: false as const, optional: false as const, - properties: { - name: { - type: 'string' as const, - nullable: false as const, optional: false as const, - }, - value: { - type: 'string' as const, - nullable: false as const, optional: false as const, - }, - }, - maxLength: 4, - }, - }, - followersCount: { - type: 'number' as const, - nullable: false as const, optional: true as const, - }, - followingCount: { - type: 'number' as const, - nullable: false as const, optional: true as const, - }, - notesCount: { - type: 'number' as const, - nullable: false as const, optional: true as const, - }, - pinnedNoteIds: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - }, - }, - pinnedNotes: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'object' as const, - nullable: false as const, optional: false as const, - ref: 'Note' as const, - }, - }, - pinnedPageId: { - type: 'string' as const, - nullable: true as const, optional: true as const, - }, - pinnedPage: { - type: 'object' as const, - nullable: true as const, optional: true as const, - ref: 'Page' as const, - }, - twoFactorEnabled: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - usePasswordLessLogin: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - securityKeys: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - avatarId: { - type: 'string' as const, - nullable: true as const, optional: true as const, - format: 'id', - }, - bannerId: { - type: 'string' as const, - nullable: true as const, optional: true as const, - format: 'id', - }, - autoWatch: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - injectFeaturedNote: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - alwaysMarkNsfw: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - carefulBot: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - autoAcceptFollowed: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadSpecifiedNotes: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadMentions: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadAnnouncement: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadAntenna: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadChannel: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadMessagingMessage: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadNotification: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasPendingReceivedFollowRequest: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - integrations: { - type: 'object' as const, - nullable: false as const, optional: true as const, - }, - mutedWords: { - type: 'array' as const, - nullable: false as const, optional: true as const, - }, - mutedInstances: { - type: 'array' as const, - nullable: false as const, optional: true as const, - }, - mutingNotificationTypes: { - type: 'array' as const, - nullable: false as const, optional: true as const, - }, - isFollowing: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - hasPendingFollowRequestFromYou: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - hasPendingFollowRequestToYou: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - isFollowed: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - isBlocking: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - isBlocked: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - isMuted: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/schema/antenna.ts b/packages/backend/src/models/schema/antenna.ts new file mode 100644 index 0000000000..9cf522802c --- /dev/null +++ b/packages/backend/src/models/schema/antenna.ts @@ -0,0 +1,89 @@ +export const packedAntennaSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + keywords: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, + excludeKeywords: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, + src: { + type: 'string', + optional: false, nullable: false, + enum: ['home', 'all', 'users', 'list', 'group'], + }, + userListId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + userGroupId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + users: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + caseSensitive: { + type: 'boolean', + optional: false, nullable: false, + default: false, + }, + notify: { + type: 'boolean', + optional: false, nullable: false, + }, + withReplies: { + type: 'boolean', + optional: false, nullable: false, + default: false, + }, + withFile: { + type: 'boolean', + optional: false, nullable: false, + }, + hasUnreadNote: { + type: 'boolean', + optional: false, nullable: false, + default: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/app.ts b/packages/backend/src/models/schema/app.ts new file mode 100644 index 0000000000..c80dc81c33 --- /dev/null +++ b/packages/backend/src/models/schema/app.ts @@ -0,0 +1,33 @@ +export const packedAppSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + callbackUrl: { + type: 'string', + optional: false, nullable: true, + }, + permission: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + secret: { + type: 'string', + optional: true, nullable: false, + }, + isAuthorized: { + type: 'boolean', + optional: true, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/blocking.ts b/packages/backend/src/models/schema/blocking.ts new file mode 100644 index 0000000000..5532322420 --- /dev/null +++ b/packages/backend/src/models/schema/blocking.ts @@ -0,0 +1,26 @@ +export const packedBlockingSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + blockeeId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + blockee: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/channel.ts b/packages/backend/src/models/schema/channel.ts new file mode 100644 index 0000000000..7f4f2a48b8 --- /dev/null +++ b/packages/backend/src/models/schema/channel.ts @@ -0,0 +1,51 @@ +export const packedChannelSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + lastNotedAt: { + type: 'string', + optional: false, nullable: true, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + description: { + type: 'string', + nullable: true, optional: false, + }, + bannerUrl: { + type: 'string', + format: 'url', + nullable: true, optional: false, + }, + notesCount: { + type: 'number', + nullable: false, optional: false, + }, + usersCount: { + type: 'number', + nullable: false, optional: false, + }, + isFollowing: { + type: 'boolean', + optional: true, nullable: false, + }, + userId: { + type: 'string', + nullable: true, optional: false, + format: 'id', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/clip.ts b/packages/backend/src/models/schema/clip.ts new file mode 100644 index 0000000000..f0ee2ce0c4 --- /dev/null +++ b/packages/backend/src/models/schema/clip.ts @@ -0,0 +1,38 @@ +export const packedClipSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + description: { + type: 'string', + optional: false, nullable: true, + }, + isPublic: { + type: 'boolean', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/drive-file.ts b/packages/backend/src/models/schema/drive-file.ts new file mode 100644 index 0000000000..4359076612 --- /dev/null +++ b/packages/backend/src/models/schema/drive-file.ts @@ -0,0 +1,107 @@ +export const packedDriveFileSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + example: 'lenna.jpg', + }, + type: { + type: 'string', + optional: false, nullable: false, + example: 'image/jpeg', + }, + md5: { + type: 'string', + optional: false, nullable: false, + format: 'md5', + example: '15eca7fba0480996e2245f5185bf39f2', + }, + size: { + type: 'number', + optional: false, nullable: false, + example: 51469, + }, + isSensitive: { + type: 'boolean', + optional: false, nullable: false, + }, + blurhash: { + type: 'string', + optional: false, nullable: true, + }, + properties: { + type: 'object', + optional: false, nullable: false, + properties: { + width: { + type: 'number', + optional: true, nullable: false, + example: 1280, + }, + height: { + type: 'number', + optional: true, nullable: false, + example: 720, + }, + orientation: { + type: 'number', + optional: true, nullable: false, + example: 8, + }, + avgColor: { + type: 'string', + optional: true, nullable: false, + example: 'rgb(40,65,87)', + }, + }, + }, + url: { + type: 'string', + optional: false, nullable: true, + format: 'url', + }, + thumbnailUrl: { + type: 'string', + optional: false, nullable: true, + format: 'url', + }, + comment: { + type: 'string', + optional: false, nullable: true, + }, + folderId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + folder: { + type: 'object', + optional: true, nullable: true, + ref: 'DriveFolder', + }, + userId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + user: { + type: 'object', + optional: true, nullable: true, + ref: 'UserLite', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/drive-folder.ts b/packages/backend/src/models/schema/drive-folder.ts new file mode 100644 index 0000000000..88cb8ab4a2 --- /dev/null +++ b/packages/backend/src/models/schema/drive-folder.ts @@ -0,0 +1,39 @@ +export const packedDriveFolderSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + foldersCount: { + type: 'number', + optional: true, nullable: false, + }, + filesCount: { + type: 'number', + optional: true, nullable: false, + }, + parentId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + parent: { + type: 'object', + optional: true, nullable: true, + ref: 'DriveFolder', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/emoji.ts b/packages/backend/src/models/schema/emoji.ts new file mode 100644 index 0000000000..5f9af88db4 --- /dev/null +++ b/packages/backend/src/models/schema/emoji.ts @@ -0,0 +1,36 @@ +export const packedEmojiSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + aliases: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + category: { + type: 'string', + optional: false, nullable: true, + }, + host: { + type: 'string', + optional: false, nullable: true, + }, + url: { + type: 'string', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/federation-instance.ts b/packages/backend/src/models/schema/federation-instance.ts new file mode 100644 index 0000000000..eef2f9e24f --- /dev/null +++ b/packages/backend/src/models/schema/federation-instance.ts @@ -0,0 +1,105 @@ +import config from "@/config"; + +export const packedFederationInstanceSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + caughtAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + host: { + type: 'string', + optional: false, nullable: false, + example: 'misskey.example.com', + }, + usersCount: { + type: 'number', + optional: false, nullable: false, + }, + notesCount: { + type: 'number', + optional: false, nullable: false, + }, + followingCount: { + type: 'number', + optional: false, nullable: false, + }, + followersCount: { + type: 'number', + optional: false, nullable: false, + }, + driveUsage: { + type: 'number', + optional: false, nullable: false, + }, + driveFiles: { + type: 'number', + optional: false, nullable: false, + }, + latestRequestSentAt: { + type: 'string', + optional: false, nullable: true, + format: 'date-time', + }, + lastCommunicatedAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + isNotResponding: { + type: 'boolean', + optional: false, nullable: false, + }, + isSuspended: { + type: 'boolean', + optional: false, nullable: false, + }, + softwareName: { + type: 'string', + optional: false, nullable: true, + example: 'misskey', + }, + softwareVersion: { + type: 'string', + optional: false, nullable: true, + example: config.version, + }, + openRegistrations: { + type: 'boolean', + optional: false, nullable: true, + example: true, + }, + name: { + type: 'string', + optional: false, nullable: true, + }, + description: { + type: 'string', + optional: false, nullable: true, + }, + maintainerName: { + type: 'string', + optional: false, nullable: true, + }, + maintainerEmail: { + type: 'string', + optional: false, nullable: true, + }, + iconUrl: { + type: 'string', + optional: false, nullable: true, + format: 'url', + }, + infoUpdatedAt: { + type: 'string', + optional: false, nullable: true, + format: 'date-time', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/following.ts b/packages/backend/src/models/schema/following.ts new file mode 100644 index 0000000000..2bcffbfc4d --- /dev/null +++ b/packages/backend/src/models/schema/following.ts @@ -0,0 +1,36 @@ +export const packedFollowingSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + followeeId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + followee: { + type: 'object', + optional: true, nullable: false, + ref: 'UserDetailed', + }, + followerId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + follower: { + type: 'object', + optional: true, nullable: false, + ref: 'UserDetailed', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/gallery-post.ts b/packages/backend/src/models/schema/gallery-post.ts new file mode 100644 index 0000000000..fc503d4a64 --- /dev/null +++ b/packages/backend/src/models/schema/gallery-post.ts @@ -0,0 +1,69 @@ +export const packedGalleryPostSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + updatedAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + title: { + type: 'string', + optional: false, nullable: false, + }, + description: { + type: 'string', + optional: false, nullable: true, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + fileIds: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + files: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'DriveFile', + }, + }, + tags: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + isSensitive: { + type: 'boolean', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/hashtag.ts b/packages/backend/src/models/schema/hashtag.ts new file mode 100644 index 0000000000..98f8827640 --- /dev/null +++ b/packages/backend/src/models/schema/hashtag.ts @@ -0,0 +1,34 @@ +export const packedHashtagSchema = { + type: 'object', + properties: { + tag: { + type: 'string', + optional: false, nullable: false, + example: 'misskey', + }, + mentionedUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + mentionedLocalUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + mentionedRemoteUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + attachedUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + attachedLocalUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + attachedRemoteUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/messaging-message.ts b/packages/backend/src/models/schema/messaging-message.ts new file mode 100644 index 0000000000..b1ffa45955 --- /dev/null +++ b/packages/backend/src/models/schema/messaging-message.ts @@ -0,0 +1,73 @@ +export const packedMessagingMessageSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: true, nullable: false, + }, + text: { + type: 'string', + optional: false, nullable: true, + }, + fileId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + }, + file: { + type: 'object', + optional: true, nullable: true, + ref: 'DriveFile', + }, + recipientId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + recipient: { + type: 'object', + optional: true, nullable: true, + ref: 'UserLite', + }, + groupId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + group: { + type: 'object', + optional: true, nullable: true, + ref: 'UserGroup', + }, + isRead: { + type: 'boolean', + optional: true, nullable: false, + }, + reads: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/muting.ts b/packages/backend/src/models/schema/muting.ts new file mode 100644 index 0000000000..d75a4fbfed --- /dev/null +++ b/packages/backend/src/models/schema/muting.ts @@ -0,0 +1,26 @@ +export const packedMutingSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + muteeId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + mutee: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/note-favorite.ts b/packages/backend/src/models/schema/note-favorite.ts new file mode 100644 index 0000000000..d133f7367d --- /dev/null +++ b/packages/backend/src/models/schema/note-favorite.ts @@ -0,0 +1,26 @@ +export const packedNoteFavoriteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + note: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + }, + noteId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/note-reaction.ts b/packages/backend/src/models/schema/note-reaction.ts new file mode 100644 index 0000000000..0d8fc5449b --- /dev/null +++ b/packages/backend/src/models/schema/note-reaction.ts @@ -0,0 +1,25 @@ +export const packedNoteReactionSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, + type: { + type: 'string', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts new file mode 100644 index 0000000000..cdf4b9a544 --- /dev/null +++ b/packages/backend/src/models/schema/note.ts @@ -0,0 +1,183 @@ +export const packedNoteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + text: { + type: 'string', + optional: false, nullable: true, + }, + cw: { + type: 'string', + optional: true, nullable: true, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + replyId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + renoteId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + reply: { + type: 'object', + optional: true, nullable: true, + ref: 'Note', + }, + renote: { + type: 'object', + optional: true, nullable: true, + ref: 'Note', + }, + isHidden: { + type: 'boolean', + optional: true, nullable: false, + }, + visibility: { + type: 'string', + optional: false, nullable: false, + }, + mentions: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + visibleUserIds: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + fileIds: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + files: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'DriveFile', + }, + }, + tags: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + poll: { + type: 'object', + optional: true, nullable: true, + }, + channelId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + channel: { + type: 'object', + optional: true, nullable: true, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + id: { + type: 'string', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: true, + }, + }, + }, + }, + localOnly: { + type: 'boolean', + optional: true, nullable: false, + }, + emojis: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + name: { + type: 'string', + optional: false, nullable: false, + }, + url: { + type: 'string', + optional: false, nullable: true, + }, + }, + }, + }, + reactions: { + type: 'object', + optional: false, nullable: false, + }, + renoteCount: { + type: 'number', + optional: false, nullable: false, + }, + repliesCount: { + type: 'number', + optional: false, nullable: false, + }, + uri: { + type: 'string', + optional: true, nullable: false, + }, + url: { + type: 'string', + optional: true, nullable: false, + }, + + myReaction: { + type: 'object', + optional: true, nullable: true, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/notification.ts b/packages/backend/src/models/schema/notification.ts new file mode 100644 index 0000000000..f3c293c480 --- /dev/null +++ b/packages/backend/src/models/schema/notification.ts @@ -0,0 +1,66 @@ +import { notificationTypes } from "@/types"; + +export const packedNotificationSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + isRead: { + type: 'boolean', + optional: false, nullable: false, + }, + type: { + type: 'string', + optional: false, nullable: false, + enum: [...notificationTypes], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: true, nullable: true, + }, + userId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: true, nullable: true, + }, + reaction: { + type: 'string', + optional: true, nullable: true, + }, + choice: { + type: 'number', + optional: true, nullable: true, + }, + invitation: { + type: 'object', + optional: true, nullable: true, + }, + body: { + type: 'string', + optional: true, nullable: true, + }, + header: { + type: 'string', + optional: true, nullable: true, + }, + icon: { + type: 'string', + optional: true, nullable: true, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/page.ts b/packages/backend/src/models/schema/page.ts new file mode 100644 index 0000000000..55ba3ce7f7 --- /dev/null +++ b/packages/backend/src/models/schema/page.ts @@ -0,0 +1,51 @@ +export const packedPageSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + updatedAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + title: { + type: 'string', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + summary: { + type: 'string', + optional: false, nullable: true, + }, + content: { + type: 'array', + optional: false, nullable: false, + }, + variables: { + type: 'array', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/queue.ts b/packages/backend/src/models/schema/queue.ts new file mode 100644 index 0000000000..7ceeda26af --- /dev/null +++ b/packages/backend/src/models/schema/queue.ts @@ -0,0 +1,25 @@ +export const packedQueueCountSchema = { + type: 'object', + properties: { + waiting: { + type: 'number', + optional: false, nullable: false, + }, + active: { + type: 'number', + optional: false, nullable: false, + }, + completed: { + type: 'number', + optional: false, nullable: false, + }, + failed: { + type: 'number', + optional: false, nullable: false, + }, + delayed: { + type: 'number', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/user-group.ts b/packages/backend/src/models/schema/user-group.ts new file mode 100644 index 0000000000..a73bf82bb8 --- /dev/null +++ b/packages/backend/src/models/schema/user-group.ts @@ -0,0 +1,34 @@ +export const packedUserGroupSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + ownerId: { + type: 'string', + nullable: false, optional: false, + format: 'id', + }, + userIds: { + type: 'array', + nullable: false, optional: true, + items: { + type: 'string', + nullable: false, optional: false, + format: 'id', + }, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/user-list.ts b/packages/backend/src/models/schema/user-list.ts new file mode 100644 index 0000000000..3ba5dc4a8a --- /dev/null +++ b/packages/backend/src/models/schema/user-list.ts @@ -0,0 +1,29 @@ +export const packedUserListSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + userIds: { + type: 'array', + nullable: false, optional: true, + items: { + type: 'string', + nullable: false, optional: false, + format: 'id', + }, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/user.ts b/packages/backend/src/models/schema/user.ts new file mode 100644 index 0000000000..616bedc0dc --- /dev/null +++ b/packages/backend/src/models/schema/user.ts @@ -0,0 +1,467 @@ +export const packedUserLiteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + nullable: false, optional: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + name: { + type: 'string', + nullable: true, optional: false, + example: '藍', + }, + username: { + type: 'string', + nullable: false, optional: false, + example: 'ai', + }, + host: { + type: 'string', + nullable: true, optional: false, + example: 'misskey.example.com', + }, + avatarUrl: { + type: 'string', + format: 'url', + nullable: true, optional: false, + }, + avatarBlurhash: { + type: 'any', + nullable: true, optional: false, + }, + avatarColor: { + type: 'any', + nullable: true, optional: false, + default: null, + }, + isAdmin: { + type: 'boolean', + nullable: false, optional: true, + default: false, + }, + isModerator: { + type: 'boolean', + nullable: false, optional: true, + default: false, + }, + isBot: { + type: 'boolean', + nullable: false, optional: true, + }, + isCat: { + type: 'boolean', + nullable: false, optional: true, + }, + emojis: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'object', + nullable: false, optional: false, + properties: { + name: { + type: 'string', + nullable: false, optional: false, + }, + url: { + type: 'string', + nullable: false, optional: false, + format: 'url', + }, + }, + }, + }, + onlineStatus: { + type: 'string', + format: 'url', + nullable: true, optional: false, + enum: ['unknown', 'online', 'active', 'offline'], + }, + }, +} as const; + +export const packedUserDetailedNotMeOnlySchema = { + type: 'object', + properties: { + url: { + type: 'string', + format: 'url', + nullable: true, optional: false, + }, + uri: { + type: 'string', + format: 'uri', + nullable: true, optional: false, + }, + createdAt: { + type: 'string', + nullable: false, optional: false, + format: 'date-time', + }, + updatedAt: { + type: 'string', + nullable: true, optional: false, + format: 'date-time', + }, + lastFetchedAt: { + type: 'string', + nullable: true, optional: false, + format: 'date-time', + }, + bannerUrl: { + type: 'string', + format: 'url', + nullable: true, optional: false, + }, + bannerBlurhash: { + type: 'any', + nullable: true, optional: false, + }, + bannerColor: { + type: 'any', + nullable: true, optional: false, + default: null, + }, + isLocked: { + type: 'boolean', + nullable: false, optional: false, + }, + isSilenced: { + type: 'boolean', + nullable: false, optional: false, + }, + isSuspended: { + type: 'boolean', + nullable: false, optional: false, + example: false, + }, + description: { + type: 'string', + nullable: true, optional: false, + example: 'Hi masters, I am Ai!', + }, + location: { + type: 'string', + nullable: true, optional: false, + }, + birthday: { + type: 'string', + nullable: true, optional: false, + example: '2018-03-12', + }, + lang: { + type: 'string', + nullable: true, optional: false, + example: 'ja-JP', + }, + fields: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'object', + nullable: false, optional: false, + properties: { + name: { + type: 'string', + nullable: false, optional: false, + }, + value: { + type: 'string', + nullable: false, optional: false, + }, + }, + maxLength: 4, + }, + }, + followersCount: { + type: 'number', + nullable: false, optional: false, + }, + followingCount: { + type: 'number', + nullable: false, optional: false, + }, + notesCount: { + type: 'number', + nullable: false, optional: false, + }, + pinnedNoteIds: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + format: 'id', + }, + }, + pinnedNotes: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'object', + nullable: false, optional: false, + ref: 'Note', + }, + }, + pinnedPageId: { + type: 'string', + nullable: true, optional: false, + }, + pinnedPage: { + type: 'object', + nullable: true, optional: false, + ref: 'Page', + }, + publicReactions: { + type: 'boolean', + nullable: false, optional: false, + }, + twoFactorEnabled: { + type: 'boolean', + nullable: false, optional: false, + default: false, + }, + usePasswordLessLogin: { + type: 'boolean', + nullable: false, optional: false, + default: false, + }, + securityKeys: { + type: 'boolean', + nullable: false, optional: false, + default: false, + }, + //#region relations + isFollowing: { + type: 'boolean', + nullable: false, optional: true, + }, + isFollowed: { + type: 'boolean', + nullable: false, optional: true, + }, + hasPendingFollowRequestFromYou: { + type: 'boolean', + nullable: false, optional: true, + }, + hasPendingFollowRequestToYou: { + type: 'boolean', + nullable: false, optional: true, + }, + isBlocking: { + type: 'boolean', + nullable: false, optional: true, + }, + isBlocked: { + type: 'boolean', + nullable: false, optional: true, + }, + isMuted: { + type: 'boolean', + nullable: false, optional: true, + }, + //#endregion + }, +} as const; + +export const packedMeDetailedOnlySchema = { + type: 'object', + properties: { + avatarId: { + type: 'string', + nullable: true, optional: false, + format: 'id', + }, + bannerId: { + type: 'string', + nullable: true, optional: false, + format: 'id', + }, + injectFeaturedNote: { + type: 'boolean', + nullable: true, optional: false, + }, + receiveAnnouncementEmail: { + type: 'boolean', + nullable: true, optional: false, + }, + alwaysMarkNsfw: { + type: 'boolean', + nullable: true, optional: false, + }, + carefulBot: { + type: 'boolean', + nullable: true, optional: false, + }, + autoAcceptFollowed: { + type: 'boolean', + nullable: true, optional: false, + }, + noCrawle: { + type: 'boolean', + nullable: true, optional: false, + }, + isExplorable: { + type: 'boolean', + nullable: false, optional: false, + }, + isDeleted: { + type: 'boolean', + nullable: false, optional: false, + }, + hideOnlineStatus: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadSpecifiedNotes: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadMentions: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadAnnouncement: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadAntenna: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadChannel: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadMessagingMessage: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadNotification: { + type: 'boolean', + nullable: false, optional: false, + }, + hasPendingReceivedFollowRequest: { + type: 'boolean', + nullable: false, optional: false, + }, + integrations: { + type: 'object', + nullable: true, optional: false, + }, + mutedWords: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + }, + }, + }, + mutedInstances: { + type: 'array', + nullable: true, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + }, + }, + mutingNotificationTypes: { + type: 'array', + nullable: true, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + }, + }, + emailNotificationTypes: { + type: 'array', + nullable: true, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + }, + }, + //#region secrets + email: { + type: 'string', + nullable: true, optional: true, + }, + emailVerified: { + type: 'boolean', + nullable: true, optional: true, + }, + securityKeysList: { + type: 'array', + nullable: false, optional: true, + items: { + type: 'object', + nullable: false, optional: false, + }, + }, + //#endregion + }, +} as const; + +export const packedUserDetailedNotMeSchema = { + type: 'object', + allOf: [ + { + type: 'object', + ref: 'UserLite', + }, + { + type: 'object', + ref: 'UserDetailedNotMeOnly', + }, + ], +} as const; + +export const packedMeDetailedSchema = { + type: 'object', + allOf: [ + { + type: 'object', + ref: 'UserLite', + }, + { + type: 'object', + ref: 'UserDetailedNotMeOnly', + }, + { + type: 'object', + ref: 'MeDetailedOnly', + }, + ], +} as const; + +export const packedUserDetailedSchema = { + oneOf: [ + { + type: 'object', + ref: 'UserDetailedNotMe', + }, + { + type: 'object', + ref: 'MeDetailed', + }, + ], +} as const; + +export const packedUserSchema = { + oneOf: [ + { + type: 'object', + ref: 'UserLite', + }, + { + type: 'object', + ref: 'UserDetailed', + }, + ], +} as const; diff --git a/packages/backend/src/prelude/await-all.ts b/packages/backend/src/prelude/await-all.ts index 24795f3ae5..b955c3a5d8 100644 --- a/packages/backend/src/prelude/await-all.ts +++ b/packages/backend/src/prelude/await-all.ts @@ -1,13 +1,11 @@ -type Await<T> = T extends Promise<infer U> ? U : T; - -type AwaitAll<T> = { - [P in keyof T]: Await<T[P]>; +export type Promiseable<T> = { + [K in keyof T]: Promise<T[K]> | T[K]; }; -export async function awaitAll<T>(obj: T): Promise<AwaitAll<T>> { - const target = {} as any; - const keys = Object.keys(obj); - const values = Object.values(obj); +export async function awaitAll<T>(obj: Promiseable<T>): Promise<T> { + const target = {} as T; + const keys = Object.keys(obj) as unknown as (keyof T)[]; + const values = Object.values(obj) as any[]; const resolvedValues = await Promise.all(values.map(value => (!value || !value.constructor || value.constructor.name !== 'Object') diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index a61b3f564c..bb4e972b8e 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -3,7 +3,7 @@ import { dirname } from 'path'; import { Context } from 'cafy'; import * as path from 'path'; import * as glob from 'glob'; -import { SimpleSchema } from '@/misc/simple-schema'; +import { Schema } from '@/misc/schema'; //const _filename = fileURLToPath(import.meta.url); const _filename = __filename; @@ -18,87 +18,87 @@ export type Param = { }; export interface IEndpointMeta { - stability?: string; //'deprecated' | 'experimental' | 'stable'; + readonly stability?: 'deprecated' | 'experimental' | 'stable'; - tags?: string[]; + readonly tags?: ReadonlyArray<string>; - params?: { - [key: string]: Param; + readonly params?: { + readonly [key: string]: Param; }; - errors?: { - [key: string]: { - message: string; - code: string; - id: string; + readonly errors?: { + readonly [key: string]: { + readonly message: string; + readonly code: string; + readonly id: string; }; }; - res?: SimpleSchema; + readonly res?: Schema; /** * このエンドポイントにリクエストするのにユーザー情報が必須か否か * 省略した場合は false として解釈されます。 */ - requireCredential?: boolean; + readonly requireCredential?: boolean; /** * 管理者のみ使えるエンドポイントか否か */ - requireAdmin?: boolean; + readonly requireAdmin?: boolean; /** * 管理者またはモデレーターのみ使えるエンドポイントか否か */ - requireModerator?: boolean; + readonly requireModerator?: boolean; /** * エンドポイントのリミテーションに関するやつ * 省略した場合はリミテーションは無いものとして解釈されます。 * また、withCredential が false の場合はリミテーションを行うことはできません。 */ - limit?: { + readonly limit?: { /** * 複数のエンドポイントでリミットを共有したい場合に指定するキー */ - key?: string; + readonly key?: string; /** * リミットを適用する期間(ms) * このプロパティを設定する場合、max プロパティも設定する必要があります。 */ - duration?: number; + readonly duration?: number; /** * durationで指定した期間内にいくつまでリクエストできるのか * このプロパティを設定する場合、duration プロパティも設定する必要があります。 */ - max?: number; + readonly max?: number; /** * 最低でもどれくらいの間隔を開けてリクエストしなければならないか(ms) */ - minInterval?: number; + readonly minInterval?: number; }; /** * ファイルの添付を必要とするか否か * 省略した場合は false として解釈されます。 */ - requireFile?: boolean; + readonly requireFile?: boolean; /** * サードパーティアプリからはリクエストすることができないか否か * 省略した場合は false として解釈されます。 */ - secure?: boolean; + readonly secure?: boolean; /** * エンドポイントの種類 * パーミッションの実現に利用されます。 */ - kind?: string; + readonly kind?: string; } export interface IEndpoint { diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 5d4cc2c044..807afd2690 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -49,66 +49,66 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'date-time', }, comment: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, }, resolved: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, example: false, }, reporterId: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'id', }, targetUserId: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'id', }, assigneeId: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, format: 'id', }, reporter: { - type: 'object' as const, - nullable: false as const, optional: false as const, + type: 'object', + nullable: false, optional: false, ref: 'User', }, targetUser: { - type: 'object' as const, - nullable: false as const, optional: false as const, + type: 'object', + nullable: false, optional: false, ref: 'User', }, assignee: { - type: 'object' as const, - nullable: true as const, optional: true as const, + type: 'object', + nullable: true, optional: true, ref: 'User', }, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index edfac244f5..20f1232959 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -16,17 +16,17 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'User', properties: { token: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, _me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index 3ed6ac2f33..1701c1e3a7 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -9,7 +9,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -17,7 +17,7 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index 3388ef2726..00ad2012fe 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -6,7 +6,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -32,7 +32,7 @@ export const meta = { validator: $.str.min(1), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts index ab0458af6e..c0124e2484 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts @@ -7,7 +7,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -23,7 +23,7 @@ export const meta = { id: 'ccac9863-3a03-416e-b899-8a64041118b1', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index 04597ec7b5..7a83637f3b 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -24,7 +24,7 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index 7fab745ef7..c2b09ab9cf 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -7,7 +7,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -44,7 +44,7 @@ export const meta = { id: 'b7aa1727-1354-47bc-a182-3a9c3973d300', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index 7d169d6caf..24c4caa37d 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -6,7 +6,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -22,40 +22,40 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, updatedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'date-time', }, title: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, text: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, imageUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts index 81dd8cfc59..5548f99006 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts @@ -7,7 +7,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -23,7 +23,7 @@ export const meta = { id: 'ecad8040-a276-4e85-bda9-015a708d291e', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index f6ad6d36f4..e5cc53ccdd 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -26,48 +26,48 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, updatedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'date-time', }, text: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, title: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, imageUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, reads: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index 6fb4b571ad..f66293bb18 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -7,7 +7,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -32,7 +32,7 @@ export const meta = { id: 'd3aae5a7-6372-4cb4-b61c-f511ffc2d7cc', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts index 82cbe7b194..249e63a0f8 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts @@ -7,7 +7,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -15,7 +15,7 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/delete-logs.ts b/packages/backend/src/server/api/endpoints/admin/delete-logs.ts deleted file mode 100644 index 197ad01cb3..0000000000 --- a/packages/backend/src/server/api/endpoints/admin/delete-logs.ts +++ /dev/null @@ -1,14 +0,0 @@ -import define from '../../define'; -import { Logs } from '@/models/index'; - -export const meta = { - tags: ['admin'], - - requireCredential: true as const, - requireModerator: true, -}; - -// eslint-disable-next-line import/no-default-export -export default define(meta, async (ps) => { - await Logs.clear(); // TRUNCATE -}); diff --git a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts index 518535fdde..acabbfef5c 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts @@ -4,9 +4,9 @@ import { createCleanRemoteFilesJob } from '@/queue/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts index a523c5b398..452e7069a8 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts @@ -6,9 +6,9 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index b90ad90440..264f549867 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -7,7 +7,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: false as const, + requireCredential: false, requireModerator: true, params: { @@ -44,15 +44,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index bc0857588c..5d9a1f2703 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -7,7 +7,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -29,137 +29,137 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, userId: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'id', example: 'xxxxxxxxxx', }, userHost: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, md5: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'md5', example: '15eca7fba0480996e2245f5185bf39f2', }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: 'lenna.jpg', }, type: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: 'image/jpeg', }, size: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, example: 51469, }, comment: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, blurhash: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, properties: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { width: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, example: 1280, }, height: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, example: 720, }, avgColor: { - type: 'string' as const, - optional: true as const, nullable: false as const, + type: 'string', + optional: true, nullable: false, example: 'rgb(40,65,87)', }, }, }, storedInternal: { - type: 'boolean' as const, - optional: false as const, nullable: true as const, + type: 'boolean', + optional: false, nullable: true, example: true, }, url: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'url', }, thumbnailUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'url', }, webpublicUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'url', }, accessKey: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, thumbnailAccessKey: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, webpublicAccessKey: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, uri: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, src: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, folderId: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'id', example: 'xxxxxxxxxx', }, isSensitive: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isLink: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts index ef0f315022..f0fd73c276 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -20,7 +20,7 @@ export const meta = { validator: $.arr($.str), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index f7a0fdb875..72adda9dcb 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -12,7 +12,7 @@ import { publishBroadcastStream } from '@/services/stream'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -28,7 +28,7 @@ export const meta = { id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index 6dc7d1bd97..de239c4f0d 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -12,7 +12,7 @@ import { publishBroadcastStream } from '@/services/stream'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -30,17 +30,17 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts index a99cd3c978..797a5de672 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -9,7 +9,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -17,7 +17,7 @@ export const meta = { validator: $.arr($.type(ID)), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index 870245ac92..1580439024 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -9,7 +9,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -25,7 +25,7 @@ export const meta = { id: 'be83669b-773a-44b7-b1f8-e5e5170ac3c2', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts index 04895b8f20..8856a38f24 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts @@ -6,14 +6,14 @@ import { ID } from '@/misc/cafy-id'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { fileId: { validator: $.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts index 42b6cb1fcf..6e502547f5 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts @@ -8,7 +8,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -37,45 +37,45 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, aliases: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, category: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, host: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 5026af914a..76ef190f94 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -8,7 +8,7 @@ import { Emoji } from '@/models/entities/emoji'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -32,45 +32,45 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, aliases: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, category: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, host: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts index 4c771b4e42..c49f84b7fb 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -20,7 +20,7 @@ export const meta = { validator: $.arr($.str), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts index 33dccbc642..06197820f0 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -20,7 +20,7 @@ export const meta = { validator: $.arr($.str), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts index d40ed52da7..f0645f111b 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -20,7 +20,7 @@ export const meta = { validator: $.optional.nullable.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index 391887257a..54a2cf9517 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -36,7 +36,7 @@ export const meta = { id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts index 9046196838..db023c6f0b 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts @@ -6,7 +6,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -14,7 +14,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts index d9e3900a29..b68252ef2e 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts @@ -7,7 +7,7 @@ import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -15,7 +15,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts index 485bbe7d58..4de8ad1336 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -6,7 +6,7 @@ import { Followings, Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -14,7 +14,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index 3ddccecc6a..6ac2f1f467 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -6,7 +6,7 @@ import { toPuny } from '@/misc/convert-host'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -18,7 +18,7 @@ export const meta = { validator: $.bool, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts index 877e67aa79..9a2bccec77 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts @@ -2,14 +2,14 @@ import define from '../../define'; import { getConnection } from 'typeorm'; export const meta = { - requireCredential: true as const, + requireCredential: true, requireModerator: true, tags: ['admin'], params: { }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async () => { diff --git a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts index 5d4ea9c444..1c5f250676 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts @@ -2,7 +2,7 @@ import define from '../../define'; import { getConnection } from 'typeorm'; export const meta = { - requireCredential: true as const, + requireCredential: true, requireModerator: true, tags: ['admin'], @@ -11,8 +11,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, example: { migrations: { count: 66, @@ -20,7 +20,7 @@ export const meta = { }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async () => { diff --git a/packages/backend/src/server/api/endpoints/admin/invite.ts b/packages/backend/src/server/api/endpoints/admin/invite.ts index 1c8c51434b..3428709c04 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite.ts @@ -6,25 +6,25 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: {}, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { code: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: '2ERUA5VR', maxLength: 8, minLength: 8, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async () => { diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts index ea795895f2..0308cf2761 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts @@ -6,7 +6,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireAdmin: true, params: { @@ -14,7 +14,7 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts index 25f237d81f..bdb976e9ec 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts @@ -6,7 +6,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireAdmin: true, params: { @@ -14,7 +14,7 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index 1bd54ba890..f2735ac9f8 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -8,7 +8,7 @@ import { PromoNotes } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -34,7 +34,7 @@ export const meta = { id: 'ae427aa2-7a41-484f-a18c-2c1104051604', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts index 8a91168ece..3c8e7a27a2 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts @@ -5,11 +5,11 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: {}, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts index 2c867463e3..4760e2c310 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts @@ -5,25 +5,25 @@ import define from '../../../define'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { anyOf: [ { - type: 'string' as const, + type: 'string', }, { - type: 'number' as const, + type: 'number', }, ], }, @@ -33,7 +33,7 @@ export const meta = { 12, ]], }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts index 974e680121..a95aabc506 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -5,25 +5,25 @@ import { inboxQueue } from '@/queue/queues'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { anyOf: [ { - type: 'string' as const, + type: 'string', }, { - type: 'number' as const, + type: 'number', }, ], }, @@ -33,7 +33,7 @@ export const meta = { 12, ]], }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts b/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts index 70649e0675..df0b4a8f13 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts @@ -5,7 +5,7 @@ import define from '../../../define'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -24,37 +24,37 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, data: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, }, attempts: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, maxAttempts: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, timestamp: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts index 5de871a60a..dab0be5dbc 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts @@ -4,30 +4,34 @@ import define from '../../../define'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: {}, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { deliver: { + optional: false, nullable: false, ref: 'QueueCount', }, inbox: { + optional: false, nullable: false, ref: 'QueueCount', }, db: { + optional: false, nullable: false, ref: 'QueueCount', }, objectStorage: { + optional: false, nullable: false, ref: 'QueueCount', }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/relays/add.ts b/packages/backend/src/server/api/endpoints/admin/relays/add.ts index c4a0f13028..65890a00f7 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts @@ -7,8 +7,8 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, - requireModerator: true as const, + requireCredential: true, + requireModerator: true, params: { inbox: { @@ -25,22 +25,22 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, inbox: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, status: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'requesting', enum: [ 'requesting', @@ -50,7 +50,7 @@ export const meta = { }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/admin/relays/list.ts b/packages/backend/src/server/api/endpoints/admin/relays/list.ts index 1e8afd783d..bdddf13374 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/list.ts @@ -4,32 +4,32 @@ import { listRelay } from '@/services/relay'; export const meta = { tags: ['admin'], - requireCredential: true as const, - requireModerator: true as const, + requireCredential: true, + requireModerator: true, params: { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, inbox: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, status: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'requesting', enum: [ 'requesting', @@ -40,7 +40,7 @@ export const meta = { }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts index 293de2b91b..4b04e620c1 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts @@ -5,15 +5,15 @@ import { removeRelay } from '@/services/relay'; export const meta = { tags: ['admin'], - requireCredential: true as const, - requireModerator: true as const, + requireCredential: true, + requireModerator: true, params: { inbox: { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index 227bcbab91..b6cf1ee2d0 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -8,7 +8,7 @@ import { Users, UserProfiles } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -18,18 +18,18 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { password: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, minLength: 8, maxLength: 8, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index a189c3e99b..3b47d7d35e 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -6,7 +6,7 @@ import { AbuseUserReports } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -14,7 +14,7 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/resync-chart.ts b/packages/backend/src/server/api/endpoints/admin/resync-chart.ts index 49d41cd8c2..d80d2b0426 100644 --- a/packages/backend/src/server/api/endpoints/admin/resync-chart.ts +++ b/packages/backend/src/server/api/endpoints/admin/resync-chart.ts @@ -5,9 +5,9 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/send-email.ts b/packages/backend/src/server/api/endpoints/admin/send-email.ts index 74ec98f75d..c2972c35fa 100644 --- a/packages/backend/src/server/api/endpoints/admin/send-email.ts +++ b/packages/backend/src/server/api/endpoints/admin/send-email.ts @@ -5,7 +5,7 @@ import { sendEmail } from '@/services/send-email'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -19,7 +19,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts index 9346c5dd2c..cd282e364c 100644 --- a/packages/backend/src/server/api/endpoints/admin/server-info.ts +++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts @@ -5,7 +5,7 @@ import define from '../../define'; import { redisClient } from '../../../../db/redis'; export const meta = { - requireCredential: true as const, + requireCredential: true, requireModerator: true, tags: ['admin', 'meta'], @@ -14,81 +14,81 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { machine: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, os: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: 'linux', }, node: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, psql: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, cpu: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { model: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, cores: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, mem: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { total: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, format: 'bytes', }, }, }, fs: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { total: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, format: 'bytes', }, used: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, format: 'bytes', }, }, }, net: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { interface: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: 'eth0', }, }, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async () => { diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index 0d3759a84b..84e2b84bb5 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -26,44 +26,44 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, type: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, info: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, }, userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, user: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 7ac922ca7e..c2a6a294b5 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -6,7 +6,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -16,148 +16,148 @@ export const meta = { }, res: { - type: 'object' as const, - nullable: false as const, optional: false as const, + type: 'object', + nullable: false, optional: false, properties: { id: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'id', }, createdAt: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'date-time', }, updatedAt: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, format: 'date-time', }, lastFetchedAt: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, username: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, }, name: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, folowersCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, + type: 'number', + nullable: false, optional: true, }, followingCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, + type: 'number', + nullable: false, optional: false, }, notesCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, + type: 'number', + nullable: false, optional: false, }, avatarId: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, bannerId: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, tags: { - type: 'array' as const, - nullable: false as const, optional: false as const, + type: 'array', + nullable: false, optional: false, items: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, }, }, avatarUrl: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, format: 'url', }, bannerUrl: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, format: 'url', }, avatarBlurhash: { - type: 'any' as const, - nullable: true as const, optional: false as const, + type: 'any', + nullable: true, optional: false, default: null, }, bannerBlurhash: { - type: 'any' as const, - nullable: true as const, optional: false as const, + type: 'any', + nullable: true, optional: false, default: null, }, isSuspended: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isSilenced: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isLocked: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isBot: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isCat: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isAdmin: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isModerator: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, emojis: { - type: 'array' as const, - nullable: false as const, optional: false as const, + type: 'array', + nullable: false, optional: false, items: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, }, }, host: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, inbox: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, sharedInbox: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, featured: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, uri: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, token: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: true, optional: false, default: '<MASKED>', }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 507183e876..d3dde99b72 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -5,7 +5,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -64,15 +64,15 @@ export const meta = { }, res: { - type: 'array' as const, - nullable: false as const, optional: false as const, + type: 'array', + nullable: false, optional: false, items: { - type: 'object' as const, - nullable: false as const, optional: false as const, - ref: 'User', + type: 'object', + nullable: false, optional: false, + ref: 'UserDetailed', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/silence-user.ts b/packages/backend/src/server/api/endpoints/admin/silence-user.ts index b4a1ddcc0f..872bd2a6ac 100644 --- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts @@ -7,7 +7,7 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -15,7 +15,7 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index 95771e0c32..2bb1875fc0 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -11,7 +11,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -19,7 +19,7 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts index e04960a4ec..a4c6ff2ade 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts @@ -7,7 +7,7 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -15,7 +15,7 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index 0a6ceadab9..5ab56d51c7 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -8,7 +8,7 @@ import { doPostUnsuspend } from '@/services/unsuspend-user'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -16,7 +16,7 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 0d5455cd33..aa2d1222f7 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -9,7 +9,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireAdmin: true, params: { @@ -297,7 +297,7 @@ export const meta = { validator: $.optional.bool, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/admin/vacuum.ts b/packages/backend/src/server/api/endpoints/admin/vacuum.ts index 798a51acc5..4229ef0d29 100644 --- a/packages/backend/src/server/api/endpoints/admin/vacuum.ts +++ b/packages/backend/src/server/api/endpoints/admin/vacuum.ts @@ -6,7 +6,7 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -17,7 +17,7 @@ export const meta = { validator: $.bool, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index 122d04f17c..0bd29607d6 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../common/make-pagination-query'; export const meta = { tags: ['meta'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -30,48 +30,48 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, updatedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'date-time', }, text: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, title: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, imageUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, isRead: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index b3276bd9cd..2092d177ba 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -9,7 +9,7 @@ import { publishInternalEvent } from '@/services/stream'; export const meta = { tags: ['antennas'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -74,11 +74,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Antenna', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts index c6da545400..b2793fc70d 100644 --- a/packages/backend/src/server/api/endpoints/antennas/delete.ts +++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts @@ -8,7 +8,7 @@ import { publishInternalEvent } from '@/services/stream'; export const meta = { tags: ['antennas'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,7 +25,7 @@ export const meta = { id: 'b34dcf9d-348f-44bb-99d0-6c9314cfe2df', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/antennas/list.ts b/packages/backend/src/server/api/endpoints/antennas/list.ts index 1f24e0fb97..bb58912612 100644 --- a/packages/backend/src/server/api/endpoints/antennas/list.ts +++ b/packages/backend/src/server/api/endpoints/antennas/list.ts @@ -4,20 +4,20 @@ import { Antennas } from '@/models/index'; export const meta = { tags: ['antennas', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Antenna', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 09500a38dc..eb7de901c5 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -12,7 +12,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['antennas', 'account', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -52,15 +52,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts index a02c44ac2b..a37d37d31c 100644 --- a/packages/backend/src/server/api/endpoints/antennas/show.ts +++ b/packages/backend/src/server/api/endpoints/antennas/show.ts @@ -7,7 +7,7 @@ import { Antennas } from '@/models/index'; export const meta = { tags: ['antennas', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -26,11 +26,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Antenna', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 968f761a35..900f725505 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -8,7 +8,7 @@ import { publishInternalEvent } from '@/services/stream'; export const meta = { tags: ['antennas'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -83,11 +83,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Antenna', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts index 0acce9bdbc..ff8c677b91 100644 --- a/packages/backend/src/server/api/endpoints/ap/get.ts +++ b/packages/backend/src/server/api/endpoints/ap/get.ts @@ -7,7 +7,7 @@ import ms from 'ms'; export const meta = { tags: ['federation'], - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -24,10 +24,10 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index e4e13117e6..7d17d8edce 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -12,11 +12,12 @@ import { User } from '@/models/entities/user'; import { fetchMeta } from '@/misc/fetch-meta'; import { isActor, isPost, getApId } from '@/remote/activitypub/type'; import ms from 'ms'; +import { SchemaType } from '@/misc/schema'; export const meta = { tags: ['federation'], - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -38,21 +39,41 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - enum: ['User', 'Note'], + optional: false, nullable: false, + oneOf: [ + { + type: 'object', + properties: { + type: { + type: 'string', + optional: false, nullable: false, + enum: ['User'], + }, + object: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', + } + } }, - object: { - type: 'object' as const, - optional: false as const, nullable: false as const, - }, - }, + { + type: 'object', + properties: { + type: { + type: 'string', + optional: false, nullable: false, + enum: ['Note'], + }, + object: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + } + } + } + ], }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { @@ -67,7 +88,7 @@ export default define(meta, async (ps) => { /*** * URIからUserかNoteを解決する */ -async function fetchAny(uri: string) { +async function fetchAny(uri: string): Promise<SchemaType<typeof meta['res']> | null> { // URIがこのサーバーを指しているなら、ローカルユーザーIDとしてDBからフェッチ if (uri.startsWith(config.url + '/')) { const parts = uri.split('/'); @@ -96,8 +117,8 @@ async function fetchAny(uri: string) { } // ブロックしてたら中断 - const meta = await fetchMeta(); - if (meta.blockedHosts.includes(extractDbHost(uri))) return null; + const fetchedMeta = await fetchMeta(); + if (fetchedMeta.blockedHosts.includes(extractDbHost(uri))) return null; // URI(AP Object id)としてDB検索 { @@ -172,7 +193,7 @@ async function fetchAny(uri: string) { return null; } -async function mergePack(user: User | null | undefined, note: Note | null | undefined) { +async function mergePack(user: User | null | undefined, note: Note | null | undefined): Promise<SchemaType<typeof meta.res> | null> { if (user != null) { return { type: 'User', diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index 88e5d866c2..838cbf606e 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -8,7 +8,7 @@ import { secureRndstr } from '@/misc/secure-rndstr'; export const meta = { tags: ['app'], - requireCredential: false as const, + requireCredential: false, params: { name: { @@ -31,11 +31,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'App', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts index 701a23b223..9f4777b383 100644 --- a/packages/backend/src/server/api/endpoints/app/show.ts +++ b/packages/backend/src/server/api/endpoints/app/show.ts @@ -13,12 +13,6 @@ export const meta = { }, }, - res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'App', - }, - errors: { noSuchApp: { message: 'No such app.', @@ -28,11 +22,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'App', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user, token) => { diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index 6b2b0bc706..f028135ca5 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -9,7 +9,7 @@ import { secureRndstr } from '@/misc/secure-rndstr'; export const meta = { tags: ['auth'], - requireCredential: true as const, + requireCredential: true, secure: true, @@ -26,7 +26,7 @@ export const meta = { id: '9c72d8de-391a-43c1-9d06-08d29efde8df', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index b9e5e84f6e..98987eba5b 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -9,7 +9,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['auth'], - requireCredential: false as const, + requireCredential: false, params: { appSecret: { @@ -18,16 +18,16 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { token: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, }, @@ -40,7 +40,7 @@ export const meta = { id: '92f93e63-428e-4f2f-a5a4-39e1407fe998', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/auth/session/show.ts b/packages/backend/src/server/api/endpoints/auth/session/show.ts index 3a32b5e548..ae0d016cea 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/show.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts @@ -6,7 +6,7 @@ import { AuthSessions } from '@/models/index'; export const meta = { tags: ['auth'], - requireCredential: false as const, + requireCredential: false, params: { token: { @@ -23,26 +23,26 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, app: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'App', }, token: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts index 131cdf3dfa..fe0211ebe3 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts @@ -6,7 +6,7 @@ import { Apps, AuthSessions, AccessTokens, Users } from '@/models/index'; export const meta = { tags: ['auth'], - requireCredential: false as const, + requireCredential: false, params: { appSecret: { @@ -19,18 +19,18 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { accessToken: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, user: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', }, }, }, @@ -54,7 +54,7 @@ export const meta = { id: '8c8a4145-02cc-4cca-8e66-29ba60445a8e', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index f718bcd205..6d555ff569 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:blocks', @@ -46,11 +46,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index e563117410..942cddaedf 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:blocks', @@ -46,11 +46,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts index f1f4b16999..9a4f662140 100644 --- a/packages/backend/src/server/api/endpoints/blocking/list.ts +++ b/packages/backend/src/server/api/endpoints/blocking/list.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:blocks', @@ -27,15 +27,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Blocking', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index 9c88231ddd..48be728d99 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -9,7 +9,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['channels'], - requireCredential: true as const, + requireCredential: true, kind: 'write:channels', @@ -28,8 +28,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, @@ -40,7 +40,7 @@ export const meta = { id: 'cd1e9f3e-5a12-4ab4-96f6-5d0a2cc32050', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts index cec14cda11..ceadde907c 100644 --- a/packages/backend/src/server/api/endpoints/channels/featured.ts +++ b/packages/backend/src/server/api/endpoints/channels/featured.ts @@ -4,18 +4,18 @@ import { Channels } from '@/models/index'; export const meta = { tags: ['channels'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index 3f4904f71d..bf580eea60 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -9,7 +9,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['channels'], - requireCredential: true as const, + requireCredential: true, kind: 'write:channels', @@ -26,7 +26,7 @@ export const meta = { id: 'c0031718-d573-4e85-928e-10039f1fbb68', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/channels/followed.ts b/packages/backend/src/server/api/endpoints/channels/followed.ts index 82ae1a3afd..9e4c942af2 100644 --- a/packages/backend/src/server/api/endpoints/channels/followed.ts +++ b/packages/backend/src/server/api/endpoints/channels/followed.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['channels', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:channels', @@ -27,15 +27,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/channels/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts index 128ea41158..5473636a85 100644 --- a/packages/backend/src/server/api/endpoints/channels/owned.ts +++ b/packages/backend/src/server/api/endpoints/channels/owned.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['channels', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:channels', @@ -27,15 +27,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts index 28cb927647..598a87ec4e 100644 --- a/packages/backend/src/server/api/endpoints/channels/show.ts +++ b/packages/backend/src/server/api/endpoints/channels/show.ts @@ -7,7 +7,7 @@ import { Channels } from '@/models/index'; export const meta = { tags: ['channels'], - requireCredential: false as const, + requireCredential: false, params: { channelId: { @@ -16,8 +16,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, @@ -28,7 +28,7 @@ export const meta = { id: '6f6c314b-7486-4897-8966-c04a66a02923', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index ec60e5f236..927ce7c741 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -9,7 +9,7 @@ import { activeUsersChart } from '@/services/chart/index'; export const meta = { tags: ['notes', 'channels'], - requireCredential: false as const, + requireCredential: false, params: { channelId: { @@ -39,11 +39,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -55,7 +55,7 @@ export const meta = { id: '4d0eeeba-a02c-4c3c-9966-ef60d38d2e7f', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts index 1472aa0457..ada0cb29fd 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts @@ -8,7 +8,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['channels'], - requireCredential: true as const, + requireCredential: true, kind: 'write:channels', @@ -25,7 +25,7 @@ export const meta = { id: '19959ee9-0153-4c51-bbd9-a98c49dc59d6', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index fee79df2fa..1f7108a1cb 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -7,7 +7,7 @@ import { Channels, DriveFiles } from '@/models/index'; export const meta = { tags: ['channels'], - requireCredential: true as const, + requireCredential: true, kind: 'write:channels', @@ -30,8 +30,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, @@ -54,7 +54,7 @@ export const meta = { id: 'e86c14a4-0da2-4032-8df3-e737a04c7f3b', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/charts/active-users.ts b/packages/backend/src/server/api/endpoints/charts/active-users.ts index ac77f8f1da..f7eadc7089 100644 --- a/packages/backend/src/server/api/endpoints/charts/active-users.ts +++ b/packages/backend/src/server/api/endpoints/charts/active-users.ts @@ -23,7 +23,7 @@ export const meta = { }, res: convertLog(activeUsersChart.schema), -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/charts/drive.ts b/packages/backend/src/server/api/endpoints/charts/drive.ts index 7f15467e7d..364279da95 100644 --- a/packages/backend/src/server/api/endpoints/charts/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/drive.ts @@ -23,7 +23,7 @@ export const meta = { }, res: convertLog(driveChart.schema), -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/charts/federation.ts b/packages/backend/src/server/api/endpoints/charts/federation.ts index 677c28c1e0..6feb82b6d9 100644 --- a/packages/backend/src/server/api/endpoints/charts/federation.ts +++ b/packages/backend/src/server/api/endpoints/charts/federation.ts @@ -23,7 +23,7 @@ export const meta = { }, res: convertLog(federationChart.schema), -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/charts/hashtag.ts b/packages/backend/src/server/api/endpoints/charts/hashtag.ts index aa41bcf90e..99dc77998e 100644 --- a/packages/backend/src/server/api/endpoints/charts/hashtag.ts +++ b/packages/backend/src/server/api/endpoints/charts/hashtag.ts @@ -27,7 +27,7 @@ export const meta = { }, res: convertLog(hashtagChart.schema), -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/charts/instance.ts b/packages/backend/src/server/api/endpoints/charts/instance.ts index 2ac415464d..23e6fbf2b0 100644 --- a/packages/backend/src/server/api/endpoints/charts/instance.ts +++ b/packages/backend/src/server/api/endpoints/charts/instance.ts @@ -27,7 +27,7 @@ export const meta = { }, res: convertLog(instanceChart.schema), -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/charts/network.ts b/packages/backend/src/server/api/endpoints/charts/network.ts index 4056becad2..c5a39bbd76 100644 --- a/packages/backend/src/server/api/endpoints/charts/network.ts +++ b/packages/backend/src/server/api/endpoints/charts/network.ts @@ -23,7 +23,7 @@ export const meta = { }, res: convertLog(networkChart.schema), -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/charts/notes.ts b/packages/backend/src/server/api/endpoints/charts/notes.ts index 00d6e0aa64..dcbd80c3e9 100644 --- a/packages/backend/src/server/api/endpoints/charts/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/notes.ts @@ -23,7 +23,7 @@ export const meta = { }, res: convertLog(notesChart.schema), -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/charts/user/drive.ts b/packages/backend/src/server/api/endpoints/charts/user/drive.ts index 462fb5879b..94787b4a57 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/drive.ts @@ -28,7 +28,7 @@ export const meta = { }, res: convertLog(perUserDriveChart.schema), -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/charts/user/following.ts b/packages/backend/src/server/api/endpoints/charts/user/following.ts index 3aee237d8f..effe0c54b9 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/following.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/following.ts @@ -28,7 +28,7 @@ export const meta = { }, res: convertLog(perUserFollowingChart.schema), -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/charts/user/notes.ts b/packages/backend/src/server/api/endpoints/charts/user/notes.ts index 3536e7b779..df68a5fe52 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/notes.ts @@ -28,7 +28,7 @@ export const meta = { }, res: convertLog(perUserNotesChart.schema), -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts index 9c5515f0b4..dcd067305f 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts @@ -28,7 +28,7 @@ export const meta = { }, res: convertLog(perUserReactionsChart.schema), -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/charts/users.ts b/packages/backend/src/server/api/endpoints/charts/users.ts index d8bbb8300c..d32e14ad61 100644 --- a/packages/backend/src/server/api/endpoints/charts/users.ts +++ b/packages/backend/src/server/api/endpoints/charts/users.ts @@ -23,7 +23,7 @@ export const meta = { }, res: convertLog(usersChart.schema), -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index 992ba0edd3..4a740b6cfe 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -9,7 +9,7 @@ import { getNote } from '../../common/getters'; export const meta = { tags: ['account', 'notes', 'clips'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -42,7 +42,7 @@ export const meta = { id: '734806c4-542c-463a-9311-15c512803965', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index e9900247a1..852e66c9e4 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -6,7 +6,7 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,11 +25,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Clip', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/clips/delete.ts b/packages/backend/src/server/api/endpoints/clips/delete.ts index b7d16322dd..85c64a115d 100644 --- a/packages/backend/src/server/api/endpoints/clips/delete.ts +++ b/packages/backend/src/server/api/endpoints/clips/delete.ts @@ -7,7 +7,7 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -24,7 +24,7 @@ export const meta = { id: '70ca08ba-6865-4630-b6fb-8494759aa754', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index 8388438bd1..d88897d164 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -4,20 +4,20 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Clip', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index c8871cd3fa..eeb20631c1 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -11,7 +11,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['account', 'notes', 'clips'], - requireCredential: false as const, + requireCredential: false, kind: 'read:account', @@ -43,15 +43,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts index ce65abd65d..0a45672019 100644 --- a/packages/backend/src/server/api/endpoints/clips/show.ts +++ b/packages/backend/src/server/api/endpoints/clips/show.ts @@ -7,7 +7,7 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips', 'account'], - requireCredential: false as const, + requireCredential: false, kind: 'read:account', @@ -26,11 +26,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Clip', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts index 44e8fe33e8..795483d5b2 100644 --- a/packages/backend/src/server/api/endpoints/clips/update.ts +++ b/packages/backend/src/server/api/endpoints/clips/update.ts @@ -7,7 +7,7 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -38,11 +38,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Clip', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts index 35ac98bdf2..d9ab9883ca 100644 --- a/packages/backend/src/server/api/endpoints/drive.ts +++ b/packages/backend/src/server/api/endpoints/drive.ts @@ -5,25 +5,25 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { capacity: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, usage: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index 7a577bce64..a5c0a626a1 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -36,15 +36,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index a02ac3eef0..835dde8058 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -7,7 +7,7 @@ import { DriveFiles, Notes } from '@/models/index'; export const meta = { tags: ['drive', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -18,11 +18,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -34,7 +34,7 @@ export const meta = { id: 'c118ece3-2e4b-4296-99d1-51756e32d232', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index 14517ab4b5..a45d357ee8 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -5,7 +5,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -16,10 +16,10 @@ export const meta = { }, res: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index 640b62c6ec..f598f18ed5 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -10,7 +10,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -46,8 +46,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, @@ -58,7 +58,7 @@ export const meta = { id: 'f449b209-0c60-4e51-84d5-29486263bfd4', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user, _, file, cleanup) => { diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 2e32e6879b..308beb58a4 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -9,7 +9,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -32,7 +32,7 @@ export const meta = { id: '5eb8d909-2540-4970-90b8-dd6f86088121', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts index 5617769a9e..dc74dcb7e6 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -5,7 +5,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -16,15 +16,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index 415ab79612..2244df13cd 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -4,7 +4,7 @@ import define from '../../../define'; import { DriveFiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, tags: ['drive'], @@ -22,15 +22,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index 4a8830e9cb..18b17c4653 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -23,8 +23,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, @@ -47,7 +47,7 @@ export const meta = { id: '89674805-722c-440c-8d88-5641830dc3e4', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index 329e959c53..b7ca80e83c 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -9,7 +9,7 @@ import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -60,11 +60,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index 7e3ffd881f..6ab1ca137d 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -15,7 +15,7 @@ export const meta = { max: 60, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -50,7 +50,7 @@ export const meta = { default: false, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/folders.ts b/packages/backend/src/server/api/endpoints/drive/folders.ts index 85938e7b54..8f8d1d2c0a 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -32,15 +32,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFolder', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts index 401b591525..38ed17e0e5 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -9,7 +9,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -37,7 +37,7 @@ export const meta = { optional: false as const, nullable: false as const, ref: 'DriveFolder', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts index 2360a3abf8..13716fccea 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts @@ -8,7 +8,7 @@ import { DriveFolders, DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -31,7 +31,7 @@ export const meta = { id: 'b0fc8a17-963c-405d-bfbc-859a487295e1', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/folders/find.ts b/packages/backend/src/server/api/endpoints/drive/folders/find.ts index 47e45a23fc..911f51d78b 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/find.ts @@ -6,7 +6,7 @@ import { DriveFolders } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -22,15 +22,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFolder', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/folders/show.ts b/packages/backend/src/server/api/endpoints/drive/folders/show.ts index d6eac59fe0..58a6dd3c06 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts @@ -7,7 +7,7 @@ import { DriveFolders } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -18,8 +18,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFolder', }, @@ -30,7 +30,7 @@ export const meta = { id: 'd74ab9eb-bb09-4bba-bf24-fb58f761e1e9', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts index 5ae5424fb2..5b0cccd1c6 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts @@ -8,7 +8,7 @@ import { DriveFolders } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -47,11 +47,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFolder', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/drive/stream.ts b/packages/backend/src/server/api/endpoints/drive/stream.ts index 675e3225f5..9ba7804946 100644 --- a/packages/backend/src/server/api/endpoints/drive/stream.ts +++ b/packages/backend/src/server/api/endpoints/drive/stream.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -31,15 +31,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/email-address/available.ts b/packages/backend/src/server/api/endpoints/email-address/available.ts index 5e8f1706f3..19f9b7ccdc 100644 --- a/packages/backend/src/server/api/endpoints/email-address/available.ts +++ b/packages/backend/src/server/api/endpoints/email-address/available.ts @@ -5,7 +5,7 @@ import { validateEmailForAccount } from '@/services/validate-email-for-account'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { emailAddress: { @@ -14,20 +14,20 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { available: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, reason: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts index 597911da82..42fd468838 100644 --- a/packages/backend/src/server/api/endpoints/endpoint.ts +++ b/packages/backend/src/server/api/endpoints/endpoint.ts @@ -3,7 +3,7 @@ import define from '../define'; import endpoints from '../endpoints'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['meta'], @@ -12,7 +12,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/endpoints.ts b/packages/backend/src/server/api/endpoints/endpoints.ts index 4e304dbeb1..ebb78de337 100644 --- a/packages/backend/src/server/api/endpoints/endpoints.ts +++ b/packages/backend/src/server/api/endpoints/endpoints.ts @@ -2,7 +2,7 @@ import define from '../define'; import endpoints from '../endpoints'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['meta'], @@ -10,11 +10,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, example: [ 'admin/abuse-user-reports', @@ -23,7 +23,7 @@ export const meta = { '...', ], }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async () => { diff --git a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts index 8b7e8a0493..24c9f56aa6 100644 --- a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts +++ b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts @@ -5,12 +5,12 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts index a44b0aecf6..c0a85f166c 100644 --- a/packages/backend/src/server/api/endpoints/federation/followers.ts +++ b/packages/backend/src/server/api/endpoints/federation/followers.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -29,15 +29,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Following', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/federation/following.ts b/packages/backend/src/server/api/endpoints/federation/following.ts index c10e25864e..147f0aedb2 100644 --- a/packages/backend/src/server/api/endpoints/federation/following.ts +++ b/packages/backend/src/server/api/endpoints/federation/following.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -29,15 +29,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Following', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index e73d0e5ada..11df7ed6b6 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -7,7 +7,7 @@ import { fetchMeta } from '@/misc/fetch-meta'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -54,15 +54,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'FederationInstance', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts index 15b4f31518..6f13b28cae 100644 --- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts +++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts @@ -6,7 +6,7 @@ import { toPuny } from '@/misc/convert-host'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -15,11 +15,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: true, nullable: false, ref: 'FederationInstance', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts index 371859345f..092f805bc2 100644 --- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts +++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts @@ -7,14 +7,14 @@ import { updatePerson } from '@/remote/activitypub/models/person'; export const meta = { tags: ['federation'], - requireCredential: true as const, + requireCredential: true, params: { userId: { validator: $.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts index 8372169d67..9a8f749936 100644 --- a/packages/backend/src/server/api/endpoints/federation/users.ts +++ b/packages/backend/src/server/api/endpoints/federation/users.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -29,15 +29,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 951cf8fa20..96aede4550 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -58,11 +58,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index de43fa4387..4cd0c49452 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -46,11 +46,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index 388ddda797..92e887e00b 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -46,11 +46,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/following/requests/accept.ts b/packages/backend/src/server/api/endpoints/following/requests/accept.ts index 29f0ace2ab..7e7c056f55 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/accept.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/accept.ts @@ -8,7 +8,7 @@ import { getUser } from '../../../common/getters'; export const meta = { tags: ['following', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -30,7 +30,7 @@ export const meta = { id: 'bcde4f8b-0913-4614-8881-614e522fb041', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts index d5281c468b..19ed02c152 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts @@ -9,7 +9,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['following', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -34,11 +34,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts index bfd793faf9..ec0c76502c 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -4,36 +4,36 @@ import { FollowRequests } from '@/models/index'; export const meta = { tags: ['following', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:following', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, follower: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, followee: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/following/requests/reject.ts b/packages/backend/src/server/api/endpoints/following/requests/reject.ts index 77a14af1f7..a5ce1e7c77 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/reject.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/reject.ts @@ -8,7 +8,7 @@ import { getUser } from '../../../common/getters'; export const meta = { tags: ['following', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -25,7 +25,7 @@ export const meta = { id: 'abc2ffa6-25b2-4380-ba99-321ff3a94555', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/gallery/featured.ts b/packages/backend/src/server/api/endpoints/gallery/featured.ts index a6ca956924..ff7c16889f 100644 --- a/packages/backend/src/server/api/endpoints/gallery/featured.ts +++ b/packages/backend/src/server/api/endpoints/gallery/featured.ts @@ -4,18 +4,18 @@ import { GalleryPosts } from '@/models/index'; export const meta = { tags: ['gallery'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/gallery/popular.ts b/packages/backend/src/server/api/endpoints/gallery/popular.ts index efd1f76052..2c3368a19d 100644 --- a/packages/backend/src/server/api/endpoints/gallery/popular.ts +++ b/packages/backend/src/server/api/endpoints/gallery/popular.ts @@ -4,18 +4,18 @@ import { GalleryPosts } from '@/models/index'; export const meta = { tags: ['gallery'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts.ts b/packages/backend/src/server/api/endpoints/gallery/posts.ts index 1b1368b06f..9d2601c7e9 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts.ts @@ -23,15 +23,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index 1a75ba2cb2..e9d5df1ab6 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -11,7 +11,7 @@ import { DriveFile } from '@/models/entities/drive-file'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery', @@ -40,15 +40,15 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, errors: { }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts index dbf7b2f9c0..2a13b9ed58 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts @@ -7,7 +7,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery', @@ -24,7 +24,7 @@ export const meta = { id: 'ae52f367-4bd7-4ecd-afc6-5672fff427f5', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index 2f98f41fa4..0fb408fa5f 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -8,7 +8,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery-likes', @@ -37,7 +37,7 @@ export const meta = { id: '40e9ed56-a59c-473a-bf3f-f289c54fb5a7', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts index 7044f948fb..4325d2ad37 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts @@ -7,7 +7,7 @@ import { GalleryPosts } from '@/models/index'; export const meta = { tags: ['gallery'], - requireCredential: false as const, + requireCredential: false, params: { postId: { @@ -24,11 +24,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts index 8cf4f34251..9cca09bddc 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts @@ -7,7 +7,7 @@ import { GalleryPosts, GalleryLikes } from '@/models/index'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery-likes', @@ -30,7 +30,7 @@ export const meta = { id: 'e3e8e06e-be37-41f7-a5b4-87a8250288f0', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 67028125d4..c35e1bbf58 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -10,7 +10,7 @@ import { DriveFile } from '@/models/entities/drive-file'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery', @@ -43,15 +43,15 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, errors: { }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index b429eacef3..5b13d5a3b8 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -6,11 +6,11 @@ import define from '../define'; export const meta = { tags: ['meta'], - requireCredential: false as const, + requireCredential: false, params: { }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async () => { diff --git a/packages/backend/src/server/api/endpoints/hashtags/list.ts b/packages/backend/src/server/api/endpoints/hashtags/list.ts index 3900fdcc4a..9fa9b3edc6 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/list.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/list.ts @@ -5,7 +5,7 @@ import { Hashtags } from '@/models/index'; export const meta = { tags: ['hashtags'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -47,15 +47,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Hashtag', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts index 8642bb39ff..0d646c64f5 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/search.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts @@ -5,7 +5,7 @@ import { Hashtags } from '@/models/index'; export const meta = { tags: ['hashtags'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -24,14 +24,14 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/hashtags/show.ts b/packages/backend/src/server/api/endpoints/hashtags/show.ts index 454b98e5a4..242cef99d4 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/show.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts @@ -7,7 +7,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search'; export const meta = { tags: ['hashtags'], - requireCredential: false as const, + requireCredential: false, params: { tag: { @@ -16,8 +16,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Hashtag', }, @@ -28,7 +28,7 @@ export const meta = { id: '110ee688-193e-4a3a-9ecf-c167b2e6981e', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts index e02666a888..be964ad639 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts @@ -23,35 +23,35 @@ const max = 5; export const meta = { tags: ['hashtags'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { tag: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, chart: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, usersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async () => { diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index 6bbe87a0d5..2158dc4349 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -4,7 +4,7 @@ import { Users } from '@/models/index'; import { normalizeForSearch } from '@/misc/normalize-for-search'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['hashtags', 'users'], @@ -48,15 +48,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index e5a2c9d2f7..d69c118cfc 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -4,23 +4,23 @@ import { Users } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, params: {}, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'MeDetailed', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user, token) => { const isSecure = token == null; // ここで渡ってきている user はキャッシュされていて古い可能性もあるので id だけ渡す - return await Users.pack(user.id, user, { + return await Users.pack<true, true>(user.id, user, { detail: true, includeSecrets: isSecure, }); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index f6c5ac33aa..4853908693 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -4,7 +4,7 @@ import define from '../../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,7 +13,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index cd1f16e548..271632d362 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -16,7 +16,7 @@ import { publishMainStream } from '@/services/stream'; const cborDecodeFirst = promisify(cbor.decodeFirst) as any; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -37,7 +37,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; const rpIdHashReal = hash(Buffer.from(config.hostname, 'utf-8')); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts index 9eecbb3dc8..854848a434 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts @@ -3,7 +3,7 @@ import define from '../../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -12,7 +12,7 @@ export const meta = { validator: $.boolean, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index 19294d15a6..b6b0fd50b4 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -10,7 +10,7 @@ import { hash } from '../../../2fa'; const randomBytes = promisify(crypto.randomBytes); export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -19,7 +19,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index 0fe824a6ef..c5cfb9dfad 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -7,7 +7,7 @@ import define from '../../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -16,7 +16,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index b9a6354238..03e1d0434d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -5,7 +5,7 @@ import { UserProfiles, UserSecurityKeys, Users } from '@/models/index'; import { publishMainStream } from '@/services/stream'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -17,7 +17,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index 0e7014d3f5..a19ad6810d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -4,7 +4,7 @@ import define from '../../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,7 +13,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 6c71a071f2..63999b0981 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -3,7 +3,7 @@ import define from '../../define'; import { AccessTokens } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -17,7 +17,7 @@ export const meta = { ]), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts index 127a272c44..52122b851b 100644 --- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -3,7 +3,7 @@ import define from '../../define'; import { AccessTokens, Apps } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -23,7 +23,7 @@ export const meta = { default: 'desc', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index 5ba7a2a87e..7b6c137737 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -4,7 +4,7 @@ import define from '../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -17,7 +17,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index bfbf2e5e5c..e1eee949fc 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -7,7 +7,7 @@ import { publishUserEvent } from '@/services/stream'; import { createDeleteAccountJob } from '@/queue'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -16,7 +16,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/export-blocking.ts b/packages/backend/src/server/api/endpoints/i/export-blocking.ts index 4f2143475f..44d8a1cb38 100644 --- a/packages/backend/src/server/api/endpoints/i/export-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/export-blocking.ts @@ -4,12 +4,12 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/export-following.ts b/packages/backend/src/server/api/endpoints/i/export-following.ts index 4b2f4c86a9..5d1617d57b 100644 --- a/packages/backend/src/server/api/endpoints/i/export-following.ts +++ b/packages/backend/src/server/api/endpoints/i/export-following.ts @@ -5,7 +5,7 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, @@ -20,7 +20,7 @@ export const meta = { default: false, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/export-mute.ts b/packages/backend/src/server/api/endpoints/i/export-mute.ts index 1f655c5118..27ce8f0b29 100644 --- a/packages/backend/src/server/api/endpoints/i/export-mute.ts +++ b/packages/backend/src/server/api/endpoints/i/export-mute.ts @@ -4,12 +4,12 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/export-notes.ts b/packages/backend/src/server/api/endpoints/i/export-notes.ts index aa3d7955fa..25b1849e80 100644 --- a/packages/backend/src/server/api/endpoints/i/export-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/export-notes.ts @@ -4,12 +4,12 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1day'), max: 1, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts index e135ba1031..d28b699c5a 100644 --- a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts @@ -4,12 +4,12 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1min'), max: 1, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts index 2ae5aa87a7..92c767876b 100644 --- a/packages/backend/src/server/api/endpoints/i/favorites.ts +++ b/packages/backend/src/server/api/endpoints/i/favorites.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account', 'notes', 'favorites'], - requireCredential: true as const, + requireCredential: true, kind: 'read:favorites', @@ -27,15 +27,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'NoteFavorite', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts index ebcb459c68..f1c5763593 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query'; export const meta = { tags: ['account', 'gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'read:gallery-likes', @@ -27,22 +27,22 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, page: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts index bb77484dab..d46d42f633 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query'; export const meta = { tags: ['account', 'gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'read:gallery', @@ -27,15 +27,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts index 93c8321166..4e1a4d3db9 100644 --- a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts +++ b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts @@ -4,7 +4,7 @@ import { MutedNotes } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -12,16 +12,16 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { count: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index 6b5100c21e..acc5797420 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -46,7 +46,7 @@ export const meta = { id: '6f3a4dcc-f060-a707-4950-806fbdbe60d6', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index a0ab45b0a4..35006746fb 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, @@ -45,7 +45,7 @@ export const meta = { id: '31a1b42c-06f7-42ae-8a38-a661c5c9f691', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index b5878f4f5d..7bbb2e008e 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -46,7 +46,7 @@ export const meta = { id: 'd2f12af1-e7b4-feac-86a3-519548f2728e', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index 563ecf38e6..759d41b6cd 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, @@ -45,7 +45,7 @@ export const meta = { id: '99efe367-ce6e-4d44-93f8-5fae7b040356', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index f02e94f328..59efd32bb2 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -12,7 +12,7 @@ import { Brackets } from 'typeorm'; export const meta = { tags: ['account', 'notifications'], - requireCredential: true as const, + requireCredential: true, kind: 'read:notifications', @@ -55,15 +55,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Notification', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts index 7b8f4864fa..59239c7446 100644 --- a/packages/backend/src/server/api/endpoints/i/page-likes.ts +++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account', 'pages'], - requireCredential: true as const, + requireCredential: true, kind: 'read:page-likes', @@ -27,22 +27,22 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, page: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts index 78de9e2bfb..bef775d063 100644 --- a/packages/backend/src/server/api/endpoints/i/pages.ts +++ b/packages/backend/src/server/api/endpoints/i/pages.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account', 'pages'], - requireCredential: true as const, + requireCredential: true, kind: 'read:pages', @@ -27,15 +27,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts index 9fc73908ae..a940d1b99b 100644 --- a/packages/backend/src/server/api/endpoints/i/pin.ts +++ b/packages/backend/src/server/api/endpoints/i/pin.ts @@ -8,7 +8,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['account', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -39,11 +39,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'MeDetailed', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { @@ -54,7 +54,7 @@ export default define(meta, async (ps, user) => { throw e; }); - return await Users.pack(user.id, user, { + return await Users.pack<true, true>(user.id, user, { detail: true, }); }); diff --git a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts index 26c1abced4..4e4fb3840f 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -5,13 +5,13 @@ import { MessagingMessages, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['account', 'messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', params: { }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts index 44c32a9ee1..99f17ddfc9 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts @@ -5,13 +5,13 @@ import { NoteUnreads } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', params: { }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts index 5814c5c3c1..e9bb66264b 100644 --- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -9,7 +9,7 @@ import { publishMainStream } from '@/services/stream'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -26,7 +26,7 @@ export const meta = { id: '184663db-df88-4bc2-8b52-fb85f0681939', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index 13dbd7bd5f..a20719363b 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -6,7 +6,7 @@ import define from '../../define'; import { Users, UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -15,7 +15,7 @@ export const meta = { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts index 0f36d3f507..2941b441e2 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts @@ -3,7 +3,7 @@ import define from '../../../define'; import { RegistryItems } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,7 +13,7 @@ export const meta = { default: [], }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts index 36d8452074..51371353c9 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts @@ -4,7 +4,7 @@ import { RegistryItems } from '@/models/index'; import { ApiError } from '../../../error'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -26,7 +26,7 @@ export const meta = { id: '97a1e8e7-c0f7-47d2-957a-92e61256e01a', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts index e1b80035b6..ac617defb0 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts @@ -4,7 +4,7 @@ import { RegistryItems } from '@/models/index'; import { ApiError } from '../../../error'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -26,7 +26,7 @@ export const meta = { id: 'ac3ed68a-62f0-422b-a7bc-d5e09e8f6a6a', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts index 53ff7b1369..0445922188 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts @@ -3,7 +3,7 @@ import define from '../../../define'; import { RegistryItems } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,7 +13,7 @@ export const meta = { default: [], }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys.ts b/packages/backend/src/server/api/endpoints/i/registry/keys.ts index 40ae094d55..a3c9d0e5ee 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys.ts @@ -3,7 +3,7 @@ import define from '../../../define'; import { RegistryItems } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,7 +13,7 @@ export const meta = { default: [], }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/registry/remove.ts b/packages/backend/src/server/api/endpoints/i/registry/remove.ts index bea9be73e4..08185f224b 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/remove.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/remove.ts @@ -4,7 +4,7 @@ import { RegistryItems } from '@/models/index'; import { ApiError } from '../../../error'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -26,7 +26,7 @@ export const meta = { id: '1fac4e8a-a6cd-4e39-a4a5-3a7e11f1b019', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts index c7596f48f1..9de68ac6e8 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts @@ -2,13 +2,13 @@ import define from '../../../define'; import { RegistryItems } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, params: { }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/registry/set.ts b/packages/backend/src/server/api/endpoints/i/registry/set.ts index c373b1a215..27884046b4 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/set.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts @@ -5,7 +5,7 @@ import { RegistryItems } from '@/models/index'; import { genId } from '@/misc/gen-id'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -23,7 +23,7 @@ export const meta = { default: [], }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index acef3a58ed..51721c5b58 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -5,7 +5,7 @@ import { ID } from '@/misc/cafy-id'; import { publishUserEvent } from '@/services/stream'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -14,7 +14,7 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/signin-history.ts b/packages/backend/src/server/api/endpoints/i/signin-history.ts index deb1315477..796e2ec309 100644 --- a/packages/backend/src/server/api/endpoints/i/signin-history.ts +++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts @@ -5,7 +5,7 @@ import { Signins } from '@/models/index'; import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -23,7 +23,7 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/unpin.ts b/packages/backend/src/server/api/endpoints/i/unpin.ts index 8182254ac1..9c82b74960 100644 --- a/packages/backend/src/server/api/endpoints/i/unpin.ts +++ b/packages/backend/src/server/api/endpoints/i/unpin.ts @@ -8,7 +8,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['account', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -27,11 +27,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'MeDetailed', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { @@ -40,7 +40,7 @@ export default define(meta, async (ps, user) => { throw e; }); - return await Users.pack(user.id, user, { + return await Users.pack<true, true>(user.id, user, { detail: true, }); }); diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 19bf802482..b4479aa50d 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -11,7 +11,7 @@ import { ApiError } from '../../error'; import { validateEmailForAccount } from '@/services/validate-email-for-account'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -43,7 +43,7 @@ export const meta = { id: 'a2defefb-f220-8849-0af6-17f816099323', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 3c6050efde..6b7e53aa1f 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -19,7 +19,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -162,11 +162,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'MeDetailed', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, _user, token) => { @@ -279,7 +279,7 @@ export default define(meta, async (ps, _user, token) => { if (Object.keys(updates).length > 0) await Users.update(user.id, updates); if (Object.keys(profileUpdates).length > 0) await UserProfiles.update(user.id, profileUpdates); - const iObj = await Users.pack(user.id, user, { + const iObj = await Users.pack<true, true>(user.id, user, { detail: true, includeSecrets: isSecure, }); diff --git a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts index cbe3f64a20..76a3131e6d 100644 --- a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts +++ b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account', 'groups'], - requireCredential: true as const, + requireCredential: true, kind: 'read:user-groups', @@ -27,26 +27,26 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, group: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts index ca5b1d06a3..5ac49cf96b 100644 --- a/packages/backend/src/server/api/endpoints/messaging/history.ts +++ b/packages/backend/src/server/api/endpoints/messaging/history.ts @@ -7,7 +7,7 @@ import { Brackets } from 'typeorm'; export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'read:messaging', @@ -24,15 +24,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'MessagingMessage', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts index 9332695fdc..7dbddd80e2 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts @@ -11,7 +11,7 @@ import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivit export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'read:messaging', @@ -44,11 +44,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'MessagingMessage', }, }, @@ -72,7 +72,7 @@ export const meta = { id: 'a053a8dd-a491-4718-8f87-50775aad9284', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts index 6092746676..5ec16f5e5a 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -11,7 +11,7 @@ import { createMessage } from '@/services/messages/create'; export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'write:messaging', @@ -34,8 +34,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'MessagingMessage', }, @@ -82,7 +82,7 @@ export const meta = { id: 'c15a5199-7422-4968-941a-2a462c478f7d', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts index 7362f705ef..2975419cef 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts @@ -9,7 +9,7 @@ import { deleteMessage } from '@/services/messages/delete'; export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'write:messaging', @@ -32,7 +32,7 @@ export const meta = { id: '54b5b326-7925-42cf-8019-130fda8b56af', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts index c698f39792..42c3f49f6f 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts @@ -8,7 +8,7 @@ import { readUserMessagingMessage, readGroupMessagingMessage } from '../../../co export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'write:messaging', @@ -25,7 +25,7 @@ export const meta = { id: '86d56a2f-a9c3-4afb-b13c-3e9bfef9aa14', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 6b07011fd8..693a7a04ec 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -9,7 +9,7 @@ import { MoreThan } from 'typeorm'; export const meta = { tags: ['meta'], - requireCredential: false as const, + requireCredential: false, params: { detail: { @@ -19,434 +19,434 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { maintainerName: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, maintainerEmail: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, version: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: config.version, }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, uri: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', example: 'https://misskey.example.com', }, description: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, langs: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, tosUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, repositoryUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'https://github.com/misskey-dev/misskey', }, feedbackUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'https://github.com/misskey-dev/misskey/issues/new', }, secure: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, default: false, }, disableRegistration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, disableLocalTimeline: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, disableGlobalTimeline: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, driveCapacityPerLocalUserMb: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, driveCapacityPerRemoteUserMb: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, cacheRemoteFiles: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, proxyRemoteFiles: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, emailRequiredForSignup: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableHcaptcha: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hcaptchaSiteKey: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, enableRecaptcha: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, recaptchaSiteKey: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, swPublickey: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, mascotImageUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: '/assets/ai.png', }, bannerUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, errorImageUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'https://xn--931a.moe/aiart/yubitun.png', }, iconUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, maxNoteTextLength: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, default: 500, }, emojis: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, aliases: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, category: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, host: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, }, }, }, ads: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { place: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, imageUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, }, }, }, requireSetup: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, example: false, }, enableEmail: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableTwitterIntegration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableGithubIntegration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableDiscordIntegration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableServiceWorker: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, translatorAvailable: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, proxyAccountName: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, features: { - type: 'object' as const, - optional: true as const, nullable: false as const, + type: 'object', + optional: true, nullable: false, properties: { registration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, localTimeLine: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, globalTimeLine: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, elasticsearch: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hcaptcha: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, recaptcha: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, objectStorage: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, twitter: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, github: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, discord: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, serviceWorker: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, miauth: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, default: true, }, }, }, userStarForReactionFallback: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, pinnedUsers: { - type: 'array' as const, - optional: true as const, nullable: false as const, + type: 'array', + optional: true, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, hiddenTags: { - type: 'array' as const, - optional: true as const, nullable: false as const, + type: 'array', + optional: true, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, blockedHosts: { - type: 'array' as const, - optional: true as const, nullable: false as const, + type: 'array', + optional: true, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, hcaptchaSecretKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, recaptchaSecretKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, proxyAccountId: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, format: 'id', }, twitterConsumerKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, twitterConsumerSecret: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, githubClientId: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, githubClientSecret: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, discordClientId: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, discordClientSecret: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, summaryProxy: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, email: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, smtpSecure: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, smtpHost: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, smtpPort: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, smtpUser: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, smtpPass: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, swPrivateKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, useObjectStorage: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, objectStorageBaseUrl: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageBucket: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStoragePrefix: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageEndpoint: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageRegion: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStoragePort: { - type: 'number' as const, - optional: true as const, nullable: true as const, + type: 'number', + optional: true, nullable: true, }, objectStorageAccessKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageSecretKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageUseSSL: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, objectStorageUseProxy: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, objectStorageSetPublicRead: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts index cc739a51e3..158c8877e9 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -7,7 +7,7 @@ import { secureRndstr } from '@/misc/secure-rndstr'; export const meta = { tags: ['auth'], - requireCredential: true as const, + requireCredential: true, secure: true, @@ -34,16 +34,16 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { token: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 000e48ddea..6ba5a453c3 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -11,7 +11,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:mutes', @@ -40,7 +40,7 @@ export const meta = { id: '7e7359cb-160c-4956-b08f-4d1c653cd007', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index c204add1bd..21948dc3d1 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -9,7 +9,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:mutes', @@ -38,7 +38,7 @@ export const meta = { id: '5467d020-daa9-4553-81e1-135c0c35a96d', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/mute/list.ts b/packages/backend/src/server/api/endpoints/mute/list.ts index a736161b9c..4c6a81b63c 100644 --- a/packages/backend/src/server/api/endpoints/mute/list.ts +++ b/packages/backend/src/server/api/endpoints/mute/list.ts @@ -7,7 +7,7 @@ import { Mutings } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:mutes', @@ -27,15 +27,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Muting', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/my/apps.ts b/packages/backend/src/server/api/endpoints/my/apps.ts index d9443d38d9..42bd5c5f75 100644 --- a/packages/backend/src/server/api/endpoints/my/apps.ts +++ b/packages/backend/src/server/api/endpoints/my/apps.ts @@ -5,7 +5,7 @@ import { Apps } from '@/models/index'; export const meta = { tags: ['account', 'app'], - requireCredential: true as const, + requireCredential: true, params: { limit: { @@ -20,54 +20,54 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, callbackUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, permission: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, secret: { - type: 'string' as const, - optional: true as const, nullable: false as const, + type: 'string', + optional: true, nullable: false, }, isAuthorized: { - type: 'object' as const, - optional: true as const, nullable: false as const, + type: 'object', + optional: true, nullable: false, properties: { appId: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index c853996a48..9edc6cb11c 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -43,15 +43,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index d6062f4ebb..088ef65e96 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -12,7 +12,7 @@ import { generateMutedInstanceQuery } from '../../common/generate-muted-instance export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -34,15 +34,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index 67bac3670b..b89c6db4a8 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -9,7 +9,7 @@ import { In } from 'typeorm'; export const meta = { tags: ['clips', 'notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -18,12 +18,12 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'Note', + type: 'object', + optional: false, nullable: false, + ref: 'Clip', }, }, @@ -34,7 +34,7 @@ export const meta = { id: '47db1a1c-b0af-458d-8fb4-986e4efafe1e', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index a465b08d3a..4bd89c32e7 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -9,7 +9,7 @@ import { Notes } from '@/models/index'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -28,11 +28,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -44,7 +44,7 @@ export const meta = { id: 'e1035875-9551-45ec-afa8-1ded1fcb53c8', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index ec05e5ea94..6adbc821e7 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -25,7 +25,7 @@ setInterval(() => { export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -113,12 +113,12 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { createdNote: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -173,7 +173,7 @@ export const meta = { id: 'b390d7e1-8a5e-46ed-b625-06271cafd3d3', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 0f6f99c7c1..9e080d9e99 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -10,7 +10,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notes', @@ -39,7 +39,7 @@ export const meta = { id: 'fe8d7103-0ea8-4ec3-814d-f8b401dc69e9', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index d5d39c3040..78da6a3b00 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -9,7 +9,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['notes', 'favorites'], - requireCredential: true as const, + requireCredential: true, kind: 'write:favorites', @@ -32,7 +32,7 @@ export const meta = { id: 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index 62f32633b3..3f3d50f0d5 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -8,7 +8,7 @@ import { NoteFavorites } from '@/models/index'; export const meta = { tags: ['notes', 'favorites'], - requireCredential: true as const, + requireCredential: true, kind: 'write:favorites', @@ -31,7 +31,7 @@ export const meta = { id: 'b625fc69-635e-45e9-86f4-dbefbef35af5', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index 8f45b541dc..5a47fb9e08 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -7,7 +7,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -22,15 +22,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 14831a127c..cac8b7d8a9 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -43,11 +43,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -59,7 +59,7 @@ export const meta = { id: '0332fc13-6ab2-4427-ae80-a9fadffd1a6b', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index bac2437056..9683df4611 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -18,7 +18,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { limit: { @@ -63,11 +63,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -79,7 +79,7 @@ export const meta = { id: '620763f4-f621-4533-ab33-0577a1a3c342', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 334e754d0d..7776644124 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -54,11 +54,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -70,7 +70,7 @@ export const meta = { id: '45a6eb02-7695-4393-b023-dd3be9aaaefd', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 8ae90233a9..81b3844365 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -13,7 +13,7 @@ import { generateMutedNoteThreadQuery } from '../../common/generate-muted-note-t export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { following: { @@ -40,15 +40,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index 7562c6d7f8..79b558e65e 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -6,7 +6,7 @@ import { Brackets, In } from 'typeorm'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { limit: { @@ -21,15 +21,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index a08709e329..77387cacb2 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -17,7 +17,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:votes', @@ -68,7 +68,7 @@ export const meta = { id: '85a5377e-b1e9-4617-b0b9-5bea73331e49', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index d29893e203..5205a78171 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -10,7 +10,7 @@ import { NoteReaction } from '@/models/entities/note-reaction'; export const meta = { tags: ['notes', 'reactions'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -41,11 +41,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'NoteReaction', }, }, @@ -57,7 +57,7 @@ export const meta = { id: '263fff3d-d0e1-4af4-bea7-8408059b451a', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts index 28403d821f..1b42781ceb 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['reactions', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:reactions', @@ -41,7 +41,7 @@ export const meta = { id: '20ef5475-9f38-4e4c-bd33-de6d979498ec', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts index 2b1ce1e21e..1d686b5971 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts @@ -9,7 +9,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['reactions', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:reactions', @@ -38,7 +38,7 @@ export const meta = { id: '92f4426d-4196-4125-aa5b-02943e2ec8fc', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index abfd928db1..f71d23146a 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -12,7 +12,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -34,11 +34,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -50,7 +50,7 @@ export const meta = { id: '12908022-2e21-46cd-ba6a-3edaf6093f46', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index 12efe94be1..62c56534e1 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -10,7 +10,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -32,15 +32,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index 85daf7e48e..87eaffe2f1 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -56,15 +56,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index 5ff3258954..e75212b14b 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -13,7 +13,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { query: { @@ -50,18 +50,18 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, errors: { }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index b645f86793..feb94be1a1 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -8,7 +8,7 @@ import { Notes } from '@/models/index'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -17,8 +17,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, @@ -29,7 +29,7 @@ export const meta = { id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index 5bbe3ec86d..c3e9090bbf 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -6,7 +6,7 @@ import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { noteId: { @@ -15,24 +15,24 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { isFavorited: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isWatching: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isMutedThread: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index 0bd55f8988..a8b50d90f6 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -10,7 +10,7 @@ import readNote from '@/services/note/read'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -27,7 +27,7 @@ export const meta = { id: '5ff67ada-ed3b-2e71-8e87-a1a421e177d2', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index 756f1b9fd4..f76b526ce1 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -8,7 +8,7 @@ import { NoteThreadMutings } from '@/models'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,7 +25,7 @@ export const meta = { id: 'bddd57ac-ceb3-b29d-4334-86ea5fae481a', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 4acfed35e9..8be2861aec 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -16,7 +16,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { limit: { @@ -61,15 +61,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index 98a45ace18..ed069cb75a 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -13,7 +13,7 @@ import { Notes } from '@/models'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -25,8 +25,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, }, errors: { @@ -36,7 +36,7 @@ export const meta = { id: 'bea9b03f-36e0-49c5-a4db-627a029f8971', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 2c1e1c7390..8db543d328 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -10,7 +10,7 @@ import { Notes, Users } from '@/models/index'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notes', @@ -33,7 +33,7 @@ export const meta = { id: 'efd4a259-2442-496b-8dd7-b255aa1a160f', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index c3b273937c..89de73fb9d 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -11,7 +11,7 @@ import { Brackets } from 'typeorm'; export const meta = { tags: ['notes', 'lists'], - requireCredential: true as const, + requireCredential: true, params: { listId: { @@ -60,11 +60,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -76,7 +76,7 @@ export const meta = { id: '8fb1fbd5-e476-4c37-9fb0-43d55b63a2ff', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts index d55ae20023..6433c6bc2a 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/create.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,7 +25,7 @@ export const meta = { id: 'ea0e37a6-90a3-4f58-ba6b-c328ca206fc7', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts index 522ae6360a..3e9faa2b23 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,7 +25,7 @@ export const meta = { id: '09b3695c-f72c-4731-a428-7cff825fc82e', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notifications/create.ts b/packages/backend/src/server/api/endpoints/notifications/create.ts index f78c6e1219..bd8a7ba1b7 100644 --- a/packages/backend/src/server/api/endpoints/notifications/create.ts +++ b/packages/backend/src/server/api/endpoints/notifications/create.ts @@ -5,7 +5,7 @@ import { createNotification } from '@/services/create-notification'; export const meta = { tags: ['notifications'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notifications', @@ -25,7 +25,7 @@ export const meta = { errors: { }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user, token) => { diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts index f14a91a3dc..4cec38a95d 100644 --- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -5,10 +5,10 @@ import { Notifications } from '@/models/index'; export const meta = { tags: ['notifications', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notifications', -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts index 891bfd30d6..7e23bc234d 100644 --- a/packages/backend/src/server/api/endpoints/notifications/read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/read.ts @@ -9,7 +9,7 @@ import { ApiError } from '../../error'; export const meta = { tags: ['notifications', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notifications', @@ -26,7 +26,7 @@ export const meta = { id: 'efa929d5-05b5-47d1-beec-e6a4dbed011e', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts index f5da35e783..61c0160f83 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -6,7 +6,7 @@ import { Users, Pages } from '@/models/index'; import { ApiError } from '../error'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, params: { @@ -30,7 +30,7 @@ export const meta = { id: '4a13ad31-6729-46b4-b9af-e86b265c2e74', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index dcf916909d..7ee50fbdfa 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -10,7 +10,7 @@ import { ApiError } from '../../error'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:pages', @@ -65,8 +65,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, @@ -82,7 +82,7 @@ export const meta = { id: '4650348e-301c-499a-83c9-6aa988c66bc1', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index b3a37446d2..aeda823e52 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -7,7 +7,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:pages', @@ -30,7 +30,7 @@ export const meta = { id: '8b741b3e-2c22-44b3-a15f-29949aa1601e', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/pages/featured.ts b/packages/backend/src/server/api/endpoints/pages/featured.ts index b32964c49d..7f0d58b350 100644 --- a/packages/backend/src/server/api/endpoints/pages/featured.ts +++ b/packages/backend/src/server/api/endpoints/pages/featured.ts @@ -4,18 +4,18 @@ import { Pages } from '@/models/index'; export const meta = { tags: ['pages'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index deff8cc021..c479f637a9 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -8,7 +8,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:page-likes', @@ -37,7 +37,7 @@ export const meta = { id: 'cc98a8a2-0dc3-4123-b198-62c71df18ed3', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index 4a98ec0736..5cda5386d5 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -8,7 +8,7 @@ import { Page } from '@/models/entities/page'; export const meta = { tags: ['pages'], - requireCredential: false as const, + requireCredential: false, params: { pageId: { @@ -25,8 +25,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, @@ -37,7 +37,7 @@ export const meta = { id: '222120c0-3ead-4528-811b-b96f233388d7', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts index f6e74dcf1a..cca5e5b5a9 100644 --- a/packages/backend/src/server/api/endpoints/pages/unlike.ts +++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts @@ -7,7 +7,7 @@ import { Pages, PageLikes } from '@/models/index'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:page-likes', @@ -30,7 +30,7 @@ export const meta = { id: 'f5e586b0-ce93-4050-b0e3-7f31af5259ee', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index 06c63706ae..991085ee09 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -9,7 +9,7 @@ import { Not } from 'typeorm'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:pages', @@ -88,7 +88,7 @@ export const meta = { id: '2298a392-d4a1-44c5-9ebb-ac1aeaa5a9ab', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/ping.ts b/packages/backend/src/server/api/endpoints/ping.ts index 2130ce54e0..3eab70ae2e 100644 --- a/packages/backend/src/server/api/endpoints/ping.ts +++ b/packages/backend/src/server/api/endpoints/ping.ts @@ -1,7 +1,7 @@ import define from '../define'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['meta'], @@ -9,16 +9,16 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { pong: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async () => { diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 548871a02c..ff0e22555f 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -7,21 +7,21 @@ import { User } from '@/models/entities/user'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index 271d887899..8d8c60d755 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -9,7 +9,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { noteId: { @@ -24,7 +24,7 @@ export const meta = { id: 'd785b897-fcd3-4fe9-8fc3-b85c26e6c932', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 2a29e58914..af1aeb4311 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -11,7 +11,7 @@ import { genId } from '@/misc/gen-id'; import { IsNull } from 'typeorm'; export const meta = { - requireCredential: false as const, + requireCredential: false, limit: { duration: ms('1hour'), @@ -31,7 +31,7 @@ export const meta = { errors: { }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts index caae137d76..e99dc9db15 100644 --- a/packages/backend/src/server/api/endpoints/reset-db.ts +++ b/packages/backend/src/server/api/endpoints/reset-db.ts @@ -4,7 +4,7 @@ import { ApiError } from '../error'; import { resetDb } from '@/db/postgre'; export const meta = { - requireCredential: false as const, + requireCredential: false, params: { }, @@ -12,7 +12,7 @@ export const meta = { errors: { }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 3cf89f3758..a7366584b1 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -6,7 +6,7 @@ import { Users, UserProfiles, PasswordResetRequests } from '@/models/index'; import { ApiError } from '../error'; export const meta = { - requireCredential: false as const, + requireCredential: false, params: { token: { @@ -21,7 +21,7 @@ export const meta = { errors: { }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index 2613416ee3..1ad2c54ab5 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -3,7 +3,7 @@ import * as si from 'systeminformation'; import define from '../define'; export const meta = { - requireCredential: false as const, + requireCredential: false, desc: { }, @@ -12,7 +12,7 @@ export const meta = { params: { }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async () => { diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index 75f7a94985..9879ef2adf 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -3,7 +3,7 @@ import { NoteReactions, Notes, Users } from '@/models/index'; import { federationChart, driveChart } from '@/services/chart/index'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['meta'], @@ -11,40 +11,40 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { notesCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, originalNotesCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, usersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, originalUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, instances: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, driveUsageLocal: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, driveUsageRemote: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async () => { diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 8657a0b77c..ae3e9ce77a 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -7,7 +7,7 @@ import { SwSubscriptions } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, params: { endpoint: { @@ -24,21 +24,21 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { state: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, enum: ['already-subscribed', 'subscribed'], }, key: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index 67a2ed0a96..6f569e9417 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -5,14 +5,14 @@ import { SwSubscriptions } from '../../../../models'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, params: { endpoint: { validator: $.str, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index ede03eeb10..74120fc406 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -5,7 +5,7 @@ import { Users, UsedUsernames } from '@/models/index'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { username: { @@ -14,16 +14,16 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { available: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index fdcf59f777..6b11ec0f01 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -7,7 +7,7 @@ import { generateBlockQueryForUsers } from '../common/generate-block-query'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -53,15 +53,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index 91da853e6e..d4152fbf50 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -25,7 +25,7 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index d9c0ed7623..6214ab40ba 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -9,7 +9,7 @@ import { toPunyNullable } from '@/misc/convert-host'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -39,11 +39,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Following', }, }, @@ -61,7 +61,7 @@ export const meta = { id: '3c6a84db-d619-26af-ca14-06232a21df8a', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index a77fedd68e..76112eab25 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -9,7 +9,7 @@ import { toPunyNullable } from '@/misc/convert-host'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -39,11 +39,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Following', }, }, @@ -61,7 +61,7 @@ export const meta = { id: 'f6cdb0df-c19f-ec5c-7dbb-0ba84a1f92ba', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts index bffa4a502e..c5f08b4c94 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -25,7 +25,7 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index 5852da1849..d886d3355a 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -10,7 +10,7 @@ import { Notes, Users } from '@/models/index'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -24,12 +24,22 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + properties: { + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', + }, + weight: { + type: 'number', + optional: false, nullable: false, + }, + }, }, }, @@ -40,7 +50,7 @@ export const meta = { id: 'e6965129-7b2a-40a4-bae2-cd84cd434822', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index bbe041122e..25e29de01c 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -8,7 +8,7 @@ import { UserGroupJoining } from '@/models/entities/user-group-joining'; export const meta = { tags: ['groups'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -19,11 +19,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts index 5a38428ca4..f30ab78ca0 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -7,7 +7,7 @@ import { UserGroups } from '@/models/index'; export const meta = { tags: ['groups'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -24,7 +24,7 @@ export const meta = { id: '63dbd64c-cd77-413f-8e08-61781e210b38', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index 45b6c39dc3..7061db538b 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -9,7 +9,7 @@ import { UserGroupJoining } from '@/models/entities/user-group-joining'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -26,7 +26,7 @@ export const meta = { id: '98c11eca-c890-4f42-9806-c8c8303ebb5e', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index 03ce90aa3d..f5ca3dec8b 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -7,7 +7,7 @@ import { UserGroupInvitations } from '@/models/index'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -24,7 +24,7 @@ export const meta = { id: 'ad7471d4-2cd9-44b4-ac68-e7136b4ce656', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 68ce0f1adc..3b7a4edb81 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -11,7 +11,7 @@ import { createNotification } from '@/services/create-notification'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -50,7 +50,7 @@ export const meta = { id: 'ee0f58b4-b529-4d13-b761-b9a3e69f97e6', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts index ad18a2f121..ab48b1910d 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -5,20 +5,20 @@ import { Not, In } from 'typeorm'; export const meta = { tags: ['groups', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:user-groups', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts index af259d28ef..d2fcdab301 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -7,7 +7,7 @@ import { UserGroups, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -30,7 +30,7 @@ export const meta = { id: 'b6d6e0c2-ef8a-9bb8-653d-79f4a3107c69', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts index d11d04b84d..6193a71019 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -4,20 +4,20 @@ import { UserGroups } from '@/models/index'; export const meta = { tags: ['groups', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:user-groups', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index e4c5d1f9ee..785bea140d 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -8,7 +8,7 @@ import { UserGroups, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -41,7 +41,7 @@ export const meta = { id: '1546eed5-4414-4dea-81c1-b0aec4f6d2af', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts index 55b86d0e53..eb26eac2a8 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -7,7 +7,7 @@ import { UserGroups, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['groups', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:user-groups', @@ -18,8 +18,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, @@ -30,7 +30,7 @@ export const meta = { id: 'ea04751e-9b7e-487b-a509-330fb6bd6b9b', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index 6795f1dd21..4b1c8fbbdb 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -8,7 +8,7 @@ import { UserGroups, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -23,8 +23,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, @@ -47,7 +47,7 @@ export const meta = { id: 'd31bebee-196d-42c2-9a3e-9474d4be6cc4', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts index 0d188af738..6caf903555 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -7,7 +7,7 @@ import { UserGroups } from '@/models/index'; export const meta = { tags: ['groups'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -22,8 +22,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, @@ -34,7 +34,7 @@ export const meta = { id: '9081cda3-7a9e-4fac-a6ce-908d70f282f6', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index 4c89d1b7eb..945b511628 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -7,7 +7,7 @@ import { UserList } from '@/models/entities/user-list'; export const meta = { tags: ['lists'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -18,11 +18,11 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserList', }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts index 0f2495dc29..3183d2a09c 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -7,7 +7,7 @@ import { UserLists } from '@/models/index'; export const meta = { tags: ['lists'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -24,7 +24,7 @@ export const meta = { id: '78436795-db79-42f5-b1e2-55ea2cf19166', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index 4cf7db69a3..ae66b0aacc 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -4,20 +4,20 @@ import { UserLists } from '@/models/index'; export const meta = { tags: ['lists', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserList', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index 980d90c614..4c74aefa8a 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -9,7 +9,7 @@ import { UserLists, UserListJoinings, Users } from '@/models/index'; export const meta = { tags: ['lists', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -36,7 +36,7 @@ export const meta = { id: '588e7f72-c744-4a61-b180-d354e912bda2', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index a0ca314f36..8b50c475b0 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -9,7 +9,7 @@ import { UserLists, UserListJoinings, Blockings } from '@/models/index'; export const meta = { tags: ['lists', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -48,7 +48,7 @@ export const meta = { id: '990232c5-3f9d-4d83-9f3f-ef27b6332a4b', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 5822c06971..06555c1a88 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -7,7 +7,7 @@ import { UserLists } from '@/models/index'; export const meta = { tags: ['lists', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -18,8 +18,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserList', }, @@ -30,7 +30,7 @@ export const meta = { id: '7bc05c21-1d7a-41ae-88f1-66820f4dc686', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index 50fcf6ff54..02b0d5fe18 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -7,7 +7,7 @@ import { UserLists } from '@/models/index'; export const meta = { tags: ['lists'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -22,8 +22,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserList', }, @@ -34,7 +34,7 @@ export const meta = { id: '796666fe-3dff-4d39-becb-8a5932c1d5b7', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 2069eb60ab..99158fb0ae 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -66,11 +66,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -82,7 +82,7 @@ export const meta = { id: '27e494ba-2ac2-48e8-893b-10d4d8c2387b', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index 9fb985dc12..6e003dd1af 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -25,7 +25,7 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index 6f68aca184..312d4dbf23 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -9,7 +9,7 @@ import { ApiError } from '../../error'; export const meta = { tags: ['users', 'reactions'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -39,11 +39,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'NoteReaction', }, }, @@ -55,7 +55,7 @@ export const meta = { id: '673a7dd2-6924-1093-e0c0-e68456ceae5c', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index 5e640330c8..9ea39eb2dd 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -8,7 +8,7 @@ import { generateBlockedUserQuery, generateBlockQueryForUsers } from '../../comm export const meta = { tags: ['users'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -25,15 +25,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index 629c1d0c1d..7e319ca105 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -6,7 +6,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['users'], - requireCredential: true as const, + requireCredential: true, params: { userId: { @@ -15,92 +15,91 @@ export const meta = { }, res: { + optional: false, nullable: false, oneOf: [ { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, isFollowing: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hasPendingFollowRequestFromYou: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hasPendingFollowRequestToYou: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isFollowed: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isBlocking: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isBlocked: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isMuted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, isFollowing: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hasPendingFollowRequestFromYou: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hasPendingFollowRequestToYou: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isFollowed: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isBlocking: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isBlocked: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isMuted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, }, ], }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index bd80710fec..ed2aa7bb26 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -13,7 +13,7 @@ import { fetchMeta } from '@/misc/fetch-meta'; export const meta = { tags: ['users'], - requireCredential: true as const, + requireCredential: true, params: { userId: { @@ -44,7 +44,7 @@ export const meta = { id: '35e166f5-05fb-4f87-a2d5-adb42676d48f', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index b9cf332f47..d67625e624 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -8,7 +8,7 @@ import { User } from '@/models/entities/user'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { username: { @@ -31,15 +31,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'User', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { @@ -112,6 +112,6 @@ export default define(meta, async (ps, me) => { .getMany(); } - return await Users.packMany(users, me, { detail: ps.detail }); + return await Users.packMany(users, me, { detail: !!ps.detail }); } }); diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 35f1988fce..26f818afcc 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -7,7 +7,7 @@ import { Brackets } from 'typeorm'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { query: { @@ -36,15 +36,15 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'User', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index e8b2a781f7..92910e9ed8 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -11,7 +11,7 @@ import { User } from '@/models/entities/user'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -32,9 +32,20 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + optional: false, nullable: false, + oneOf: [ + { + type: 'object', + ref: 'UserDetailed', + }, + { + type: 'array', + items: { + type: 'object', + ref: 'UserDetailed', + } + }, + ] }, errors: { @@ -42,7 +53,7 @@ export const meta = { message: 'Failed to resolve remote user.', code: 'FAILED_TO_RESOLVE_REMOTE_USER', id: 'ef7b9be4-9cba-4e6f-ab41-90ed171c7d3c', - kind: 'server' as const, + kind: 'server', }, noSuchUser: { @@ -51,7 +62,7 @@ export const meta = { id: '4362f8dc-731f-4ad8-a694-be5a88922a24', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index 84bbd16f0b..381e433479 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -7,7 +7,7 @@ import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -22,7 +22,7 @@ export const meta = { id: '9e638e45-3b25-4ef7-8f95-07e8498f1819', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts index 723b3e884a..eb42667fd5 100644 --- a/packages/backend/src/server/api/openapi/schemas.ts +++ b/packages/backend/src/server/api/openapi/schemas.ts @@ -1,6 +1,6 @@ -import { refs, Schema } from '@/misc/schema'; +import { refs, MinimumSchema } from '@/misc/schema'; -export function convertSchemaToOpenApiSchema(schema: Schema) { +export function convertSchemaToOpenApiSchema(schema: MinimumSchema) { const res: any = schema; if (schema.type === 'object' && schema.properties) { @@ -15,6 +15,10 @@ export function convertSchemaToOpenApiSchema(schema: Schema) { res.items = convertSchemaToOpenApiSchema(schema.items); } + if (schema.anyOf) res.anyOf = schema.anyOf.map(convertSchemaToOpenApiSchema); + if (schema.oneOf) res.oneOf = schema.oneOf.map(convertSchemaToOpenApiSchema); + if (schema.allOf) res.allOf = schema.allOf.map(convertSchemaToOpenApiSchema); + if (schema.ref) { res.$ref = `#/components/schemas/${schema.ref}`; } diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 168f1b3448..e70c26f5e5 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -36,7 +36,7 @@ export interface UserStreamTypes { updateUserProfile: UserProfile; mute: User; unmute: User; - follow: Packed<'User'>; + follow: Packed<'UserDetailedNotMe'>; unfollow: Packed<'User'>; userAdded: Packed<'User'>; } @@ -46,7 +46,7 @@ export interface MainStreamTypes { mention: Packed<'Note'>; reply: Packed<'Note'>; renote: Packed<'Note'>; - follow: Packed<'User'>; + follow: Packed<'UserDetailedNotMe'>; followed: Packed<'User'>; unfollow: Packed<'User'>; meUpdated: Packed<'User'>; diff --git a/packages/backend/src/services/chart/core.ts b/packages/backend/src/services/chart/core.ts index f97fa521d3..e406449f4f 100644 --- a/packages/backend/src/services/chart/core.ts +++ b/packages/backend/src/services/chart/core.ts @@ -7,7 +7,7 @@ import * as nestedProperty from 'nested-property'; import autobind from 'autobind-decorator'; import Logger from '../logger'; -import { SimpleSchema } from '@/misc/simple-schema'; +import { Schema } from '@/misc/schema'; import { EntitySchema, getRepository, Repository, LessThan, Between } from 'typeorm'; import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '@/prelude/time'; import { getChartInsertLock } from '@/misc/app-lock'; @@ -57,7 +57,7 @@ export default abstract class Chart<T extends Record<string, any>> { diff: DeepPartial<T>; group: string | null; }[] = []; - public schema: SimpleSchema; + public schema: Schema; protected repositoryForHour: Repository<Log>; protected repositoryForDay: Repository<Log>; @@ -71,7 +71,7 @@ export default abstract class Chart<T extends Record<string, any>> { protected abstract fetchActual(group: string | null): Promise<DeepPartial<T>>; @autobind - private static convertSchemaToFlatColumnDefinitions(schema: SimpleSchema) { + private static convertSchemaToFlatColumnDefinitions(schema: Schema) { const columns = {} as Record<string, unknown>; const flatColumns = (x: Obj, path?: string) => { for (const [k, v] of Object.entries(x)) { @@ -183,7 +183,7 @@ export default abstract class Chart<T extends Record<string, any>> { } @autobind - public static schemaToEntity(name: string, schema: SimpleSchema, grouped = false): { + public static schemaToEntity(name: string, schema: Schema, grouped = false): { hour: EntitySchema, day: EntitySchema, } { @@ -233,7 +233,7 @@ export default abstract class Chart<T extends Record<string, any>> { }; } - constructor(name: string, schema: SimpleSchema, grouped = false) { + constructor(name: string, schema: Schema, grouped = false) { this.name = name; this.schema = schema; @@ -573,8 +573,8 @@ export default abstract class Chart<T extends Record<string, any>> { } } -export function convertLog(logSchema: SimpleSchema): SimpleSchema { - const v: SimpleSchema = JSON.parse(JSON.stringify(logSchema)); // copy +export function convertLog(logSchema: Schema): Schema { + const v: Schema = JSON.parse(JSON.stringify(logSchema)); // copy if (v.type === 'number') { v.type = 'array'; v.items = { diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts index 45bd226588..bc5ac275b5 100644 --- a/packages/backend/src/services/following/create.ts +++ b/packages/backend/src/services/following/create.ts @@ -14,6 +14,7 @@ import { instanceChart, perUserFollowingChart } from '@/services/chart/index'; import { genId } from '@/misc/gen-id'; import { createNotification } from '../create-notification'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error'; +import { Packed } from '@/misc/schema'; const logger = new Logger('following/create'); @@ -89,8 +90,8 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ Users.pack(followee.id, follower, { detail: true, }).then(packed => { - publishUserEvent(follower.id, 'follow', packed); - publishMainStream(follower.id, 'follow', packed); + publishUserEvent(follower.id, 'follow', packed as Packed<"UserDetailedNotMe">); + publishMainStream(follower.id, 'follow', packed as Packed<"UserDetailedNotMe">); }); } From 7be09a4af9f57981298cb20c32a69755241dc227 Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Tue, 18 Jan 2022 23:06:16 +0900 Subject: [PATCH 54/81] =?UTF-8?q?refactor:=20Composition=20API=E3=81=B8?= =?UTF-8?q?=E7=A7=BB=E8=A1=8C=20(#8138)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * components/drive-file-thumbnail.vue * components/drive-select-dialog.vue * components/drive-window.vue * wip * wip drive.file.vue, drive.vue * fix prop * wip( * components/drive.folder.vue * maybe ok * :v: * fix variable * FIX FOLDER VARIABLE * components/emoji-picker-dialog.vue * Hate `$emit` * hate global property * components/emoji-picker-window.vue * components/emoji-picker.section.vue * fix * fixx * wip components/emoji-picker.vue * fix * defineExpose * ユニコード絵文字の型をもっといい感じに * components/featured-photos.vue * components/follow-button.vue * forgot-password.vue * forgot-password.vue * :art: * fix --- packages/client/src/components/dialog.vue | 7 +- .../src/components/drive-file-thumbnail.vue | 95 +- .../src/components/drive-select-dialog.vue | 81 +- .../client/src/components/drive-window.vue | 39 +- packages/client/src/components/drive.file.vue | 320 +++-- .../client/src/components/drive.folder.vue | 448 +++---- .../src/components/drive.nav-folder.vue | 187 ++- packages/client/src/components/drive.vue | 1176 ++++++++--------- .../src/components/emoji-picker-dialog.vue | 101 +- .../src/components/emoji-picker-window.vue | 53 +- .../src/components/emoji-picker.section.vue | 38 +- .../client/src/components/emoji-picker.vue | 531 ++++---- .../client/src/components/featured-photos.vue | 22 +- .../client/src/components/follow-button.vue | 183 ++- .../client/src/components/forgot-password.vue | 76 +- packages/client/src/os.ts | 2 +- packages/client/src/scripts/emojilist.ts | 12 +- 17 files changed, 1608 insertions(+), 1763 deletions(-) diff --git a/packages/client/src/components/dialog.vue b/packages/client/src/components/dialog.vue index 9cd5234684..b6b649cde9 100644 --- a/packages/client/src/components/dialog.vue +++ b/packages/client/src/components/dialog.vue @@ -1,5 +1,5 @@ <template> -<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="done(true)" @closed="$emit('closed')"> +<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="done(true)" @closed="emit('closed')"> <div class="mk-dialog"> <div v-if="icon" class="icon"> <i :class="icon"></i> @@ -28,8 +28,8 @@ </template> </MkSelect> <div v-if="(showOkButton || showCancelButton) && !actions" class="buttons"> - <MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" @click="ok">{{ (showCancelButton || input || select) ? $ts.ok : $ts.gotIt }}</MkButton> - <MkButton v-if="showCancelButton || input || select" inline @click="cancel">{{ $ts.cancel }}</MkButton> + <MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" @click="ok">{{ (showCancelButton || input || select) ? i18n.locale.ok : i18n.locale.gotIt }}</MkButton> + <MkButton v-if="showCancelButton || input || select" inline @click="cancel">{{ i18n.locale.cancel }}</MkButton> </div> <div v-if="actions" class="buttons"> <MkButton v-for="action in actions" :key="action.text" inline :primary="action.primary" @click="() => { action.callback(); close(); }">{{ action.text }}</MkButton> @@ -44,6 +44,7 @@ import MkModal from '@/components/ui/modal.vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; +import { i18n } from '@/i18n'; type Input = { type: HTMLInputElement['type']; diff --git a/packages/client/src/components/drive-file-thumbnail.vue b/packages/client/src/components/drive-file-thumbnail.vue index e94b6b8bcb..81b80e7e8e 100644 --- a/packages/client/src/components/drive-file-thumbnail.vue +++ b/packages/client/src/components/drive-file-thumbnail.vue @@ -14,71 +14,42 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as Misskey from 'misskey-js'; import ImgWithBlurhash from '@/components/img-with-blurhash.vue'; -import { ColdDeviceStorage } from '@/store'; -export default defineComponent({ - components: { - ImgWithBlurhash - }, - props: { - file: { - type: Object, - required: true - }, - fit: { - type: String, - required: false, - default: 'cover' - }, - }, - data() { - return { - isContextmenuShowing: false, - isDragging: false, +const props = defineProps<{ + file: Misskey.entities.DriveFile; + fit: string; +}>(); - }; - }, - computed: { - is(): 'image' | 'video' | 'midi' | 'audio' | 'csv' | 'pdf' | 'textfile' | 'archive' | 'unknown' { - if (this.file.type.startsWith('image/')) return 'image'; - if (this.file.type.startsWith('video/')) return 'video'; - if (this.file.type === 'audio/midi') return 'midi'; - if (this.file.type.startsWith('audio/')) return 'audio'; - if (this.file.type.endsWith('/csv')) return 'csv'; - if (this.file.type.endsWith('/pdf')) return 'pdf'; - if (this.file.type.startsWith('text/')) return 'textfile'; - if ([ - "application/zip", - "application/x-cpio", - "application/x-bzip", - "application/x-bzip2", - "application/java-archive", - "application/x-rar-compressed", - "application/x-tar", - "application/gzip", - "application/x-7z-compressed" - ].some(e => e === this.file.type)) return 'archive'; - return 'unknown'; - }, - isThumbnailAvailable(): boolean { - return this.file.thumbnailUrl - ? (this.is === 'image' || this.is === 'video') - : false; - }, - }, - mounted() { - const audioTag = this.$refs.volumectrl as HTMLAudioElement; - if (audioTag) audioTag.volume = ColdDeviceStorage.get('mediaVolume'); - }, - methods: { - volumechange() { - const audioTag = this.$refs.volumectrl as HTMLAudioElement; - ColdDeviceStorage.set('mediaVolume', audioTag.volume); - } - } +const is = computed(() => { + if (props.file.type.startsWith('image/')) return 'image'; + if (props.file.type.startsWith('video/')) return 'video'; + if (props.file.type === 'audio/midi') return 'midi'; + if (props.file.type.startsWith('audio/')) return 'audio'; + if (props.file.type.endsWith('/csv')) return 'csv'; + if (props.file.type.endsWith('/pdf')) return 'pdf'; + if (props.file.type.startsWith('text/')) return 'textfile'; + if ([ + "application/zip", + "application/x-cpio", + "application/x-bzip", + "application/x-bzip2", + "application/java-archive", + "application/x-rar-compressed", + "application/x-tar", + "application/gzip", + "application/x-7z-compressed" + ].some(e => e === props.file.type)) return 'archive'; + return 'unknown'; +}); + +const isThumbnailAvailable = computed(() => { + return props.file.thumbnailUrl + ? (is.value === 'image' as const || is.value === 'video') + : false; }); </script> diff --git a/packages/client/src/components/drive-select-dialog.vue b/packages/client/src/components/drive-select-dialog.vue index 75537dfe3e..6d84511277 100644 --- a/packages/client/src/components/drive-select-dialog.vue +++ b/packages/client/src/components/drive-select-dialog.vue @@ -7,64 +7,51 @@ @click="cancel()" @close="cancel()" @ok="ok()" - @closed="$emit('closed')" + @closed="emit('closed')" > <template #header> - {{ multiple ? ((type === 'file') ? $ts.selectFiles : $ts.selectFolders) : ((type === 'file') ? $ts.selectFile : $ts.selectFolder) }} + {{ multiple ? ((type === 'file') ? i18n.locale.selectFiles : i18n.locale.selectFolders) : ((type === 'file') ? i18n.locale.selectFile : i18n.locale.selectFolder) }} <span v-if="selected.length > 0" style="margin-left: 8px; opacity: 0.5;">({{ number(selected.length) }})</span> </template> <XDrive :multiple="multiple" :select="type" @changeSelection="onChangeSelection" @selected="ok()"/> </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; import XDrive from './drive.vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import number from '@/filters/number'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XDrive, - XModalWindow, - }, - - props: { - type: { - type: String, - required: false, - default: 'file' - }, - multiple: { - type: Boolean, - default: false - } - }, - - emits: ['done', 'closed'], - - data() { - return { - selected: [] - }; - }, - - methods: { - ok() { - this.$emit('done', this.selected); - this.$refs.dialog.close(); - }, - - cancel() { - this.$emit('done'); - this.$refs.dialog.close(); - }, - - onChangeSelection(xs) { - this.selected = xs; - }, - - number - } +withDefaults(defineProps<{ + type?: 'file' | 'folder'; + multiple: boolean; +}>(), { + type: 'file', }); + +const emit = defineEmits<{ + (e: 'done', r?: Misskey.entities.DriveFile[]): void; + (e: 'closed'): void; +}>(); + +const dialog = ref<InstanceType<typeof XModalWindow>>(); + +const selected = ref<Misskey.entities.DriveFile[]>([]); + +function ok() { + emit('done', selected.value); + dialog.value?.close(); +} + +function cancel() { + emit('done'); + dialog.value?.close(); +} + +function onChangeSelection(files: Misskey.entities.DriveFile[]) { + selected.value = files; +} </script> diff --git a/packages/client/src/components/drive-window.vue b/packages/client/src/components/drive-window.vue index 43f07ebe76..8b60bf7794 100644 --- a/packages/client/src/components/drive-window.vue +++ b/packages/client/src/components/drive-window.vue @@ -3,42 +3,27 @@ :initial-width="800" :initial-height="500" :can-resize="true" - @closed="$emit('closed')" + @closed="emit('closed')" > <template #header> - {{ $ts.drive }} + {{ i18n.locale.drive }} </template> <XDrive :initial-folder="initialFolder"/> </XWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as Misskey from 'misskey-js'; import XDrive from './drive.vue'; import XWindow from '@/components/ui/window.vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XDrive, - XWindow, - }, +defineProps<{ + initialFolder?: Misskey.entities.DriveFolder; +}>(); - props: { - initialFolder: { - type: Object, - required: false - }, - }, - - emits: ['closed'], - - data() { - return { - }; - }, - - methods: { - - } -}); +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); </script> diff --git a/packages/client/src/components/drive.file.vue b/packages/client/src/components/drive.file.vue index 511647229e..fd6a813838 100644 --- a/packages/client/src/components/drive.file.vue +++ b/packages/client/src/components/drive.file.vue @@ -8,17 +8,17 @@ @dragstart="onDragstart" @dragend="onDragend" > - <div v-if="$i.avatarId == file.id" class="label"> + <div v-if="$i?.avatarId == file.id" class="label"> <img src="/client-assets/label.svg"/> - <p>{{ $ts.avatar }}</p> + <p>{{ i18n.locale.avatar }}</p> </div> - <div v-if="$i.bannerId == file.id" class="label"> + <div v-if="$i?.bannerId == file.id" class="label"> <img src="/client-assets/label.svg"/> - <p>{{ $ts.banner }}</p> + <p>{{ i18n.locale.banner }}</p> </div> <div v-if="file.isSensitive" class="label red"> <img src="/client-assets/label-red.svg"/> - <p>{{ $ts.nsfw }}</p> + <p>{{ i18n.locale.nsfw }}</p> </div> <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/> @@ -30,179 +30,155 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, ref } from 'vue'; +import * as Misskey from 'misskey-js'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import MkDriveFileThumbnail from './drive-file-thumbnail.vue'; import bytes from '@/filters/bytes'; import * as os from '@/os'; +import { i18n } from '@/i18n'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - MkDriveFileThumbnail - }, - - props: { - file: { - type: Object, - required: true, - }, - isSelected: { - type: Boolean, - required: false, - default: false, - }, - selectMode: { - type: Boolean, - required: false, - default: false, - } - }, - - emits: ['chosen'], - - data() { - return { - isDragging: false - }; - }, - - computed: { - // TODO: parentへの参照を無くす - browser(): any { - return this.$parent; - }, - title(): string { - return `${this.file.name}\n${this.file.type} ${bytes(this.file.size)}`; - } - }, - - methods: { - getMenu() { - return [{ - text: this.$ts.rename, - icon: 'fas fa-i-cursor', - action: this.rename - }, { - text: this.file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive, - icon: this.file.isSensitive ? 'fas fa-eye' : 'fas fa-eye-slash', - action: this.toggleSensitive - }, { - text: this.$ts.describeFile, - icon: 'fas fa-i-cursor', - action: this.describe - }, null, { - text: this.$ts.copyUrl, - icon: 'fas fa-link', - action: this.copyUrl - }, { - type: 'a', - href: this.file.url, - target: '_blank', - text: this.$ts.download, - icon: 'fas fa-download', - download: this.file.name - }, null, { - text: this.$ts.delete, - icon: 'fas fa-trash-alt', - danger: true, - action: this.deleteFile - }]; - }, - - onClick(ev) { - if (this.selectMode) { - this.$emit('chosen', this.file); - } else { - os.popupMenu(this.getMenu(), ev.currentTarget || ev.target); - } - }, - - onContextmenu(e) { - os.contextMenu(this.getMenu(), e); - }, - - onDragstart(e) { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(this.file)); - this.isDragging = true; - - // 親ブラウザに対して、ドラッグが開始されたフラグを立てる - // (=あなたの子供が、ドラッグを開始しましたよ) - this.browser.isDragSource = true; - }, - - onDragend(e) { - this.isDragging = false; - this.browser.isDragSource = false; - }, - - rename() { - os.inputText({ - title: this.$ts.renameFile, - placeholder: this.$ts.inputNewFileName, - default: this.file.name, - allowEmpty: false - }).then(({ canceled, result: name }) => { - if (canceled) return; - os.api('drive/files/update', { - fileId: this.file.id, - name: name - }); - }); - }, - - describe() { - os.popup(import('@/components/media-caption.vue'), { - title: this.$ts.describeFile, - input: { - placeholder: this.$ts.inputNewDescription, - default: this.file.comment !== null ? this.file.comment : '', - }, - image: this.file - }, { - done: result => { - if (!result || result.canceled) return; - let comment = result.result; - os.api('drive/files/update', { - fileId: this.file.id, - comment: comment.length == 0 ? null : comment - }); - } - }, 'closed'); - }, - - toggleSensitive() { - os.api('drive/files/update', { - fileId: this.file.id, - isSensitive: !this.file.isSensitive - }); - }, - - copyUrl() { - copyToClipboard(this.file.url); - os.success(); - }, - - addApp() { - alert('not implemented yet'); - }, - - async deleteFile() { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('driveFileDeleteConfirm', { name: this.file.name }), - }); - if (canceled) return; - - os.api('drive/files/delete', { - fileId: this.file.id - }); - }, - - bytes - } +const props = withDefaults(defineProps<{ + file: Misskey.entities.DriveFile; + isSelected?: boolean; + selectMode?: boolean; +}>(), { + isSelected: false, + selectMode: false, }); + +const emit = defineEmits<{ + (e: 'chosen', r: Misskey.entities.DriveFile): void; + (e: 'dragstart'): void; + (e: 'dragend'): void; +}>(); + +const isDragging = ref(false); + +const title = computed(() => `${props.file.name}\n${props.file.type} ${bytes(props.file.size)}`); + +function getMenu() { + return [{ + text: i18n.locale.rename, + icon: 'fas fa-i-cursor', + action: rename + }, { + text: props.file.isSensitive ? i18n.locale.unmarkAsSensitive : i18n.locale.markAsSensitive, + icon: props.file.isSensitive ? 'fas fa-eye' : 'fas fa-eye-slash', + action: toggleSensitive + }, { + text: i18n.locale.describeFile, + icon: 'fas fa-i-cursor', + action: describe + }, null, { + text: i18n.locale.copyUrl, + icon: 'fas fa-link', + action: copyUrl + }, { + type: 'a', + href: props.file.url, + target: '_blank', + text: i18n.locale.download, + icon: 'fas fa-download', + download: props.file.name + }, null, { + text: i18n.locale.delete, + icon: 'fas fa-trash-alt', + danger: true, + action: deleteFile + }]; +} + +function onClick(ev: MouseEvent) { + if (props.selectMode) { + emit('chosen', props.file); + } else { + os.popupMenu(getMenu(), (ev.currentTarget || ev.target || undefined) as HTMLElement | undefined); + } +} + +function onContextmenu(e: MouseEvent) { + os.contextMenu(getMenu(), e); +} + +function onDragstart(e: DragEvent) { + if (e.dataTransfer) { + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file)); + } + isDragging.value = true; + + emit('dragstart'); +} + +function onDragend() { + isDragging.value = false; + emit('dragend'); +} + +function rename() { + os.inputText({ + title: i18n.locale.renameFile, + placeholder: i18n.locale.inputNewFileName, + default: props.file.name, + }).then(({ canceled, result: name }) => { + if (canceled) return; + os.api('drive/files/update', { + fileId: props.file.id, + name: name + }); + }); +} + +function describe() { + os.popup(import('@/components/media-caption.vue'), { + title: i18n.locale.describeFile, + input: { + placeholder: i18n.locale.inputNewDescription, + default: props.file.comment !== null ? props.file.comment : '', + }, + image: props.file + }, { + done: result => { + if (!result || result.canceled) return; + let comment = result.result; + os.api('drive/files/update', { + fileId: props.file.id, + comment: comment.length == 0 ? null : comment + }); + } + }, 'closed'); +} + +function toggleSensitive() { + os.api('drive/files/update', { + fileId: props.file.id, + isSensitive: !props.file.isSensitive + }); +} + +function copyUrl() { + copyToClipboard(props.file.url); + os.success(); +} +/* +function addApp() { + alert('not implemented yet'); +} +*/ +async function deleteFile() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('driveFileDeleteConfirm', { name: props.file.name }), + }); + + if (canceled) return; + os.api('drive/files/delete', { + fileId: props.file.id + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/drive.folder.vue b/packages/client/src/components/drive.folder.vue index aaba736cf8..8e8d9f3bb3 100644 --- a/packages/client/src/components/drive.folder.vue +++ b/packages/client/src/components/drive.folder.vue @@ -19,243 +19,233 @@ <template v-if="!hover"><i class="fas fa-folder fa-fw"></i></template> {{ folder.name }} </p> - <p v-if="$store.state.uploadFolder == folder.id" class="upload"> - {{ $ts.uploadFolder }} + <p v-if="defaultStore.state.uploadFolder == folder.id" class="upload"> + {{ i18n.locale.uploadFolder }} </p> <button v-if="selectMode" class="checkbox _button" :class="{ checked: isSelected }" @click.prevent.stop="checkboxClicked"></button> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, ref } from 'vue'; +import * as Misskey from 'misskey-js'; import * as os from '@/os'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; -export default defineComponent({ - props: { - folder: { - type: Object, - required: true, - }, - isSelected: { - type: Boolean, - required: false, - default: false, - }, - selectMode: { - type: Boolean, - required: false, - default: false, - } - }, - - emits: ['chosen'], - - data() { - return { - hover: false, - draghover: false, - isDragging: false, - }; - }, - - computed: { - browser(): any { - return this.$parent; - }, - title(): string { - return this.folder.name; - } - }, - - methods: { - checkboxClicked(e) { - this.$emit('chosen', this.folder); - }, - - onClick() { - this.browser.move(this.folder); - }, - - onMouseover() { - this.hover = true; - }, - - onMouseout() { - this.hover = false - }, - - onDragover(e) { - // 自分自身がドラッグされている場合 - if (this.isDragging) { - // 自分自身にはドロップさせない - e.dataTransfer.dropEffect = 'none'; - return; - } - - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; - - if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } else { - e.dataTransfer.dropEffect = 'none'; - } - }, - - onDragenter() { - if (!this.isDragging) this.draghover = true; - }, - - onDragleave() { - this.draghover = false; - }, - - onDrop(e) { - this.draghover = false; - - // ファイルだったら - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { - this.browser.upload(file, this.folder); - } - return; - } - - //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - this.browser.removeFile(file.id); - os.api('drive/files/update', { - fileId: file.id, - folderId: this.folder.id - }); - } - //#endregion - - //#region ドライブのフォルダ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { - const folder = JSON.parse(driveFolder); - - // 移動先が自分自身ならreject - if (folder.id == this.folder.id) return; - - this.browser.removeFolder(folder.id); - os.api('drive/folders/update', { - folderId: folder.id, - parentId: this.folder.id - }).then(() => { - // noop - }).catch(err => { - switch (err) { - case 'detected-circular-definition': - os.alert({ - title: this.$ts.unableToProcess, - text: this.$ts.circularReferenceFolder - }); - break; - default: - os.alert({ - type: 'error', - text: this.$ts.somethingHappened - }); - } - }); - } - //#endregion - }, - - onDragstart(e) { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FOLDER_, JSON.stringify(this.folder)); - this.isDragging = true; - - // 親ブラウザに対して、ドラッグが開始されたフラグを立てる - // (=あなたの子供が、ドラッグを開始しましたよ) - this.browser.isDragSource = true; - }, - - onDragend() { - this.isDragging = false; - this.browser.isDragSource = false; - }, - - go() { - this.browser.move(this.folder.id); - }, - - newWindow() { - this.browser.newWindow(this.folder); - }, - - rename() { - os.inputText({ - title: this.$ts.renameFolder, - placeholder: this.$ts.inputNewFolderName, - default: this.folder.name - }).then(({ canceled, result: name }) => { - if (canceled) return; - os.api('drive/folders/update', { - folderId: this.folder.id, - name: name - }); - }); - }, - - deleteFolder() { - os.api('drive/folders/delete', { - folderId: this.folder.id - }).then(() => { - if (this.$store.state.uploadFolder === this.folder.id) { - this.$store.set('uploadFolder', null); - } - }).catch(err => { - switch(err.id) { - case 'b0fc8a17-963c-405d-bfbc-859a487295e1': - os.alert({ - type: 'error', - title: this.$ts.unableToDelete, - text: this.$ts.hasChildFilesOrFolders - }); - break; - default: - os.alert({ - type: 'error', - text: this.$ts.unableToDelete - }); - } - }); - }, - - setAsUploadFolder() { - this.$store.set('uploadFolder', this.folder.id); - }, - - onContextmenu(e) { - os.contextMenu([{ - text: this.$ts.openInWindow, - icon: 'fas fa-window-restore', - action: () => { - os.popup(import('./drive-window.vue'), { - initialFolder: this.folder - }, { - }, 'closed'); - } - }, null, { - text: this.$ts.rename, - icon: 'fas fa-i-cursor', - action: this.rename - }, null, { - text: this.$ts.delete, - icon: 'fas fa-trash-alt', - danger: true, - action: this.deleteFolder - }], e); - }, - } +const props = withDefaults(defineProps<{ + folder: Misskey.entities.DriveFolder; + isSelected?: boolean; + selectMode?: boolean; +}>(), { + isSelected: false, + selectMode: false, }); + +const emit = defineEmits<{ + (e: 'chosen', v: Misskey.entities.DriveFolder): void; + (e: 'move', v: Misskey.entities.DriveFolder): void; + (e: 'upload', file: File, folder: Misskey.entities.DriveFolder); + (e: 'removeFile', v: Misskey.entities.DriveFile['id']): void; + (e: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void; + (e: 'dragstart'): void; + (e: 'dragend'): void; +}>(); + +const hover = ref(false); +const draghover = ref(false); +const isDragging = ref(false); + +const title = computed(() => props.folder.name); + +function checkboxClicked(e) { + emit('chosen', props.folder); +} + +function onClick() { + emit('move', props.folder); +} + +function onMouseover() { + hover.value = true; +} + +function onMouseout() { + hover.value = false +} + +function onDragover(e: DragEvent) { + if (!e.dataTransfer) return; + + // 自分自身がドラッグされている場合 + if (isDragging.value) { + // 自分自身にはドロップさせない + e.dataTransfer.dropEffect = 'none'; + return; + } + + const isFile = e.dataTransfer.items[0].kind == 'file'; + const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + + if (isFile || isDriveFile || isDriveFolder) { + e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + } else { + e.dataTransfer.dropEffect = 'none'; + } +} + +function onDragenter() { + if (!isDragging.value) draghover.value = true; +} + +function onDragleave() { + draghover.value = false; +} + +function onDrop(e: DragEvent) { + draghover.value = false; + + if (!e.dataTransfer) return; + + // ファイルだったら + if (e.dataTransfer.files.length > 0) { + for (const file of Array.from(e.dataTransfer.files)) { + emit('upload', file, props.folder); + } + return; + } + + //#region ドライブのファイル + const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile != '') { + const file = JSON.parse(driveFile); + emit('removeFile', file.id); + os.api('drive/files/update', { + fileId: file.id, + folderId: props.folder.id + }); + } + //#endregion + + //#region ドライブのフォルダ + const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + if (driveFolder != null && driveFolder != '') { + const folder = JSON.parse(driveFolder); + + // 移動先が自分自身ならreject + if (folder.id == props.folder.id) return; + + emit('removeFolder', folder.id); + os.api('drive/folders/update', { + folderId: folder.id, + parentId: props.folder.id + }).then(() => { + // noop + }).catch(err => { + switch (err) { + case 'detected-circular-definition': + os.alert({ + title: i18n.locale.unableToProcess, + text: i18n.locale.circularReferenceFolder + }); + break; + default: + os.alert({ + type: 'error', + text: i18n.locale.somethingHappened + }); + } + }); + } + //#endregion +} + +function onDragstart(e: DragEvent) { + if (!e.dataTransfer) return; + + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FOLDER_, JSON.stringify(props.folder)); + isDragging.value = true; + + // 親ブラウザに対して、ドラッグが開始されたフラグを立てる + // (=あなたの子供が、ドラッグを開始しましたよ) + emit('dragstart'); +} + +function onDragend() { + isDragging.value = false; + emit('dragend'); +} + +function go() { + emit('move', props.folder.id); +} + +function rename() { + os.inputText({ + title: i18n.locale.renameFolder, + placeholder: i18n.locale.inputNewFolderName, + default: props.folder.name + }).then(({ canceled, result: name }) => { + if (canceled) return; + os.api('drive/folders/update', { + folderId: props.folder.id, + name: name + }); + }); +} + +function deleteFolder() { + os.api('drive/folders/delete', { + folderId: props.folder.id + }).then(() => { + if (defaultStore.state.uploadFolder === props.folder.id) { + defaultStore.set('uploadFolder', null); + } + }).catch(err => { + switch(err.id) { + case 'b0fc8a17-963c-405d-bfbc-859a487295e1': + os.alert({ + type: 'error', + title: i18n.locale.unableToDelete, + text: i18n.locale.hasChildFilesOrFolders + }); + break; + default: + os.alert({ + type: 'error', + text: i18n.locale.unableToDelete + }); + } + }); +} + +function setAsUploadFolder() { + defaultStore.set('uploadFolder', props.folder.id); +} + +function onContextmenu(e) { + os.contextMenu([{ + text: i18n.locale.openInWindow, + icon: 'fas fa-window-restore', + action: () => { + os.popup(import('./drive-window.vue'), { + initialFolder: props.folder + }, { + }, 'closed'); + } + }, null, { + text: i18n.locale.rename, + icon: 'fas fa-i-cursor', + action: rename, + }, null, { + text: i18n.locale.delete, + icon: 'fas fa-trash-alt', + danger: true, + action: deleteFolder, + }], e); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/drive.nav-folder.vue b/packages/client/src/components/drive.nav-folder.vue index 4f0e6ce0e9..7c35c5d3da 100644 --- a/packages/client/src/components/drive.nav-folder.vue +++ b/packages/client/src/components/drive.nav-folder.vue @@ -8,114 +8,111 @@ @drop.stop="onDrop" > <i v-if="folder == null" class="fas fa-cloud"></i> - <span>{{ folder == null ? $ts.drive : folder.name }}</span> + <span>{{ folder == null ? i18n.locale.drive : folder.name }}</span> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - folder: { - type: Object, - required: false, - } - }, +const props = defineProps<{ + folder?: Misskey.entities.DriveFolder; + parentFolder: Misskey.entities.DriveFolder | null; +}>(); - data() { - return { - hover: false, - draghover: false, - }; - }, +const emit = defineEmits<{ + (e: 'move', v?: Misskey.entities.DriveFolder): void; + (e: 'upload', file: File, folder?: Misskey.entities.DriveFolder | null): void; + (e: 'removeFile', v: Misskey.entities.DriveFile['id']): void; + (e: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void; +}>(); - computed: { - browser(): any { - return this.$parent; - } - }, +const hover = ref(false); +const draghover = ref(false); - methods: { - onClick() { - this.browser.move(this.folder); - }, +function onClick() { + emit('move', props.folder); +} - onMouseover() { - this.hover = true; - }, +function onMouseover() { + hover.value = true; +} - onMouseout() { - this.hover = false; - }, +function onMouseout() { + hover.value = false; +} - onDragover(e) { - // このフォルダがルートかつカレントディレクトリならドロップ禁止 - if (this.folder == null && this.browser.folder == null) { - e.dataTransfer.dropEffect = 'none'; - } +function onDragover(e: DragEvent) { + if (!e.dataTransfer) return; - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; - - if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } else { - e.dataTransfer.dropEffect = 'none'; - } - - return false; - }, - - onDragenter() { - if (this.folder || this.browser.folder) this.draghover = true; - }, - - onDragleave() { - if (this.folder || this.browser.folder) this.draghover = false; - }, - - onDrop(e) { - this.draghover = false; - - // ファイルだったら - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { - this.browser.upload(file, this.folder); - } - return; - } - - //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - this.browser.removeFile(file.id); - os.api('drive/files/update', { - fileId: file.id, - folderId: this.folder ? this.folder.id : null - }); - } - //#endregion - - //#region ドライブのフォルダ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { - const folder = JSON.parse(driveFolder); - // 移動先が自分自身ならreject - if (this.folder && folder.id == this.folder.id) return; - this.browser.removeFolder(folder.id); - os.api('drive/folders/update', { - folderId: folder.id, - parentId: this.folder ? this.folder.id : null - }); - } - //#endregion - } + // このフォルダがルートかつカレントディレクトリならドロップ禁止 + if (props.folder == null && props.parentFolder == null) { + e.dataTransfer.dropEffect = 'none'; } -}); + + const isFile = e.dataTransfer.items[0].kind == 'file'; + const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + + if (isFile || isDriveFile || isDriveFolder) { + e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + } else { + e.dataTransfer.dropEffect = 'none'; + } + + return false; +} + +function onDragenter() { + if (props.folder || props.parentFolder) draghover.value = true; +} + +function onDragleave() { + if (props.folder || props.parentFolder) draghover.value = false; +} + +function onDrop(e: DragEvent) { + draghover.value = false; + + if (!e.dataTransfer) return; + + // ファイルだったら + if (e.dataTransfer.files.length > 0) { + for (const file of Array.from(e.dataTransfer.files)) { + emit('upload', file, props.folder); + } + return; + } + + //#region ドライブのファイル + const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile != '') { + const file = JSON.parse(driveFile); + emit('removeFile', file.id); + os.api('drive/files/update', { + fileId: file.id, + folderId: props.folder ? props.folder.id : null + }); + } + //#endregion + + //#region ドライブのフォルダ + const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + if (driveFolder != null && driveFolder != '') { + const folder = JSON.parse(driveFolder); + // 移動先が自分自身ならreject + if (props.folder && folder.id == props.folder.id) return; + emit('removeFolder', folder.id); + os.api('drive/folders/update', { + folderId: folder.id, + parentId: props.folder ? props.folder.id : null + }); + } + //#endregion +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue index f8d3d810b7..e27b0a5fbb 100644 --- a/packages/client/src/components/drive.vue +++ b/packages/client/src/components/drive.vue @@ -2,10 +2,24 @@ <div class="yfudmmck"> <nav> <div class="path" @contextmenu.prevent.stop="() => {}"> - <XNavFolder :class="{ current: folder == null }"/> + <XNavFolder + :class="{ current: folder == null }" + :parent-folder="folder" + @move="move" + @upload="upload" + @removeFile="removeFile" + @removeFolder="removeFolder" + /> <template v-for="f in hierarchyFolders"> <span class="separator"><i class="fas fa-angle-right"></i></span> - <XNavFolder :folder="f"/> + <XNavFolder + :folder="f" + :parent-folder="folder" + @move="move" + @upload="upload" + @removeFile="removeFile" + @removeFolder="removeFolder" + /> </template> <span v-if="folder != null" class="separator"><i class="fas fa-angle-right"></i></span> <span v-if="folder != null" class="folder current">{{ folder.name }}</span> @@ -22,616 +36,600 @@ > <div ref="contents" class="contents"> <div v-show="folders.length > 0" ref="foldersContainer" class="folders"> - <XFolder v-for="(f, i) in folders" :key="f.id" v-anim="i" class="folder" :folder="f" :select-mode="select === 'folder'" :is-selected="selectedFolders.some(x => x.id === f.id)" @chosen="chooseFolder"/> + <XFolder + v-for="(f, i) in folders" + :key="f.id" + v-anim="i" + class="folder" + :folder="f" + :select-mode="select === 'folder'" + :is-selected="selectedFolders.some(x => x.id === f.id)" + @chosen="chooseFolder" + @move="move" + @upload="upload" + @removeFile="removeFile" + @removeFolder="removeFolder" + @dragstart="isDragSource = true" + @dragend="isDragSource = false" + /> <!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid --> <div v-for="(n, i) in 16" :key="i" class="padding"></div> - <MkButton v-if="moreFolders" ref="moreFolders">{{ $ts.loadMore }}</MkButton> + <MkButton v-if="moreFolders" ref="moreFolders">{{ i18n.locale.loadMore }}</MkButton> </div> <div v-show="files.length > 0" ref="filesContainer" class="files"> - <XFile v-for="(file, i) in files" :key="file.id" v-anim="i" class="file" :file="file" :select-mode="select === 'file'" :is-selected="selectedFiles.some(x => x.id === file.id)" @chosen="chooseFile"/> + <XFile + v-for="(file, i) in files" + :key="file.id" + v-anim="i" + class="file" + :file="file" + :select-mode="select === 'file'" + :is-selected="selectedFiles.some(x => x.id === file.id)" + @chosen="chooseFile" + @dragstart="isDragSource = true" + @dragend="isDragSource = false" + /> <!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid --> <div v-for="(n, i) in 16" :key="i" class="padding"></div> - <MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ $ts.loadMore }}</MkButton> + <MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ i18n.locale.loadMore }}</MkButton> </div> <div v-if="files.length == 0 && folders.length == 0 && !fetching" class="empty"> - <p v-if="draghover">{{ $t('empty-draghover') }}</p> - <p v-if="!draghover && folder == null"><strong>{{ $ts.emptyDrive }}</strong><br/>{{ $t('empty-drive-description') }}</p> - <p v-if="!draghover && folder != null">{{ $ts.emptyFolder }}</p> + <p v-if="draghover">{{ i18n.t('empty-draghover') }}</p> + <p v-if="!draghover && folder == null"><strong>{{ i18n.locale.emptyDrive }}</strong><br/>{{ i18n.t('empty-drive-description') }}</p> + <p v-if="!draghover && folder != null">{{ i18n.locale.emptyFolder }}</p> </div> </div> <MkLoading v-if="fetching"/> </div> <div v-if="draghover" class="dropzone"></div> - <input ref="fileInput" type="file" accept="*/*" multiple="multiple" tabindex="-1" @change="onChangeFileInput"/> + <input ref="fileInput" type="file" accept="*/*" multiple tabindex="-1" @change="onChangeFileInput"/> </div> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { markRaw, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue'; +import * as Misskey from 'misskey-js'; import XNavFolder from './drive.nav-folder.vue'; import XFolder from './drive.folder.vue'; import XFile from './drive.file.vue'; import MkButton from './ui/button.vue'; import * as os from '@/os'; import { stream } from '@/stream'; - -export default defineComponent({ - components: { - XNavFolder, - XFolder, - XFile, - MkButton, - }, - - props: { - initialFolder: { - type: Object, - required: false - }, - type: { - type: String, - required: false, - default: undefined - }, - multiple: { - type: Boolean, - required: false, - default: false - }, - select: { - type: String, - required: false, - default: null - } - }, - - emits: ['selected', 'change-selection', 'move-root', 'cd', 'open-folder'], - - data() { - return { - /** - * 現在の階層(フォルダ) - * * null でルートを表す - */ - folder: null, - - files: [], - folders: [], - moreFiles: false, - moreFolders: false, - hierarchyFolders: [], - selectedFiles: [], - selectedFolders: [], - uploadings: os.uploads, - connection: null, - - /** - * ドロップされようとしているか - */ - draghover: false, - - /** - * 自信の所有するアイテムがドラッグをスタートさせたか - * (自分自身の階層にドロップできないようにするためのフラグ) - */ - isDragSource: false, - - fetching: true, - - ilFilesObserver: new IntersectionObserver( - (entries) => entries.some((entry) => entry.isIntersecting) - && !this.fetching && this.moreFiles && - this.fetchMoreFiles() - ), - moreFilesElement: null as Element, - - }; - }, - - watch: { - folder() { - this.$emit('cd', this.folder); - } - }, - - mounted() { - if (this.$store.state.enableInfiniteScroll && this.$refs.loadMoreFiles) { - this.$nextTick(() => { - this.ilFilesObserver.observe((this.$refs.loadMoreFiles as Vue).$el) - }); - } - - this.connection = markRaw(stream.useChannel('drive')); - - this.connection.on('fileCreated', this.onStreamDriveFileCreated); - this.connection.on('fileUpdated', this.onStreamDriveFileUpdated); - this.connection.on('fileDeleted', this.onStreamDriveFileDeleted); - this.connection.on('folderCreated', this.onStreamDriveFolderCreated); - this.connection.on('folderUpdated', this.onStreamDriveFolderUpdated); - this.connection.on('folderDeleted', this.onStreamDriveFolderDeleted); - - if (this.initialFolder) { - this.move(this.initialFolder); - } else { - this.fetch(); - } - }, - - activated() { - if (this.$store.state.enableInfiniteScroll) { - this.$nextTick(() => { - this.ilFilesObserver.observe((this.$refs.loadMoreFiles as Vue).$el) - }); - } - }, - - beforeUnmount() { - this.connection.dispose(); - this.ilFilesObserver.disconnect(); - }, - - methods: { - onStreamDriveFileCreated(file) { - this.addFile(file, true); - }, - - onStreamDriveFileUpdated(file) { - const current = this.folder ? this.folder.id : null; - if (current != file.folderId) { - this.removeFile(file); - } else { - this.addFile(file, true); - } - }, - - onStreamDriveFileDeleted(fileId) { - this.removeFile(fileId); - }, - - onStreamDriveFolderCreated(folder) { - this.addFolder(folder, true); - }, - - onStreamDriveFolderUpdated(folder) { - const current = this.folder ? this.folder.id : null; - if (current != folder.parentId) { - this.removeFolder(folder); - } else { - this.addFolder(folder, true); - } - }, - - onStreamDriveFolderDeleted(folderId) { - this.removeFolder(folderId); - }, - - onDragover(e): any { - // ドラッグ元が自分自身の所有するアイテムだったら - if (this.isDragSource) { - // 自分自身にはドロップさせない - e.dataTransfer.dropEffect = 'none'; - return; - } - - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; - - if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } else { - e.dataTransfer.dropEffect = 'none'; - } - - return false; - }, - - onDragenter(e) { - if (!this.isDragSource) this.draghover = true; - }, - - onDragleave(e) { - this.draghover = false; - }, - - onDrop(e): any { - this.draghover = false; - - // ドロップされてきたものがファイルだったら - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { - this.upload(file, this.folder); - } - return; - } - - //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - if (this.files.some(f => f.id == file.id)) return; - this.removeFile(file.id); - os.api('drive/files/update', { - fileId: file.id, - folderId: this.folder ? this.folder.id : null - }); - } - //#endregion - - //#region ドライブのフォルダ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { - const folder = JSON.parse(driveFolder); - - // 移動先が自分自身ならreject - if (this.folder && folder.id == this.folder.id) return false; - if (this.folders.some(f => f.id == folder.id)) return false; - this.removeFolder(folder.id); - os.api('drive/folders/update', { - folderId: folder.id, - parentId: this.folder ? this.folder.id : null - }).then(() => { - // noop - }).catch(err => { - switch (err) { - case 'detected-circular-definition': - os.alert({ - title: this.$ts.unableToProcess, - text: this.$ts.circularReferenceFolder - }); - break; - default: - os.alert({ - type: 'error', - text: this.$ts.somethingHappened - }); - } - }); - } - //#endregion - }, - - selectLocalFile() { - (this.$refs.fileInput as any).click(); - }, - - urlUpload() { - os.inputText({ - title: this.$ts.uploadFromUrl, - type: 'url', - placeholder: this.$ts.uploadFromUrlDescription - }).then(({ canceled, result: url }) => { - if (canceled) return; - os.api('drive/files/upload-from-url', { - url: url, - folderId: this.folder ? this.folder.id : undefined - }); - - os.alert({ - title: this.$ts.uploadFromUrlRequested, - text: this.$ts.uploadFromUrlMayTakeTime - }); - }); - }, - - createFolder() { - os.inputText({ - title: this.$ts.createFolder, - placeholder: this.$ts.folderName - }).then(({ canceled, result: name }) => { - if (canceled) return; - os.api('drive/folders/create', { - name: name, - parentId: this.folder ? this.folder.id : undefined - }).then(folder => { - this.addFolder(folder, true); - }); - }); - }, - - renameFolder(folder) { - os.inputText({ - title: this.$ts.renameFolder, - placeholder: this.$ts.inputNewFolderName, - default: folder.name - }).then(({ canceled, result: name }) => { - if (canceled) return; - os.api('drive/folders/update', { - folderId: folder.id, - name: name - }).then(folder => { - // FIXME: 画面を更新するために自分自身に移動 - this.move(folder); - }); - }); - }, - - deleteFolder(folder) { - os.api('drive/folders/delete', { - folderId: folder.id - }).then(() => { - // 削除時に親フォルダに移動 - this.move(folder.parentId); - }).catch(err => { - switch(err.id) { - case 'b0fc8a17-963c-405d-bfbc-859a487295e1': - os.alert({ - type: 'error', - title: this.$ts.unableToDelete, - text: this.$ts.hasChildFilesOrFolders - }); - break; - default: - os.alert({ - type: 'error', - text: this.$ts.unableToDelete - }); - } - }); - }, - - onChangeFileInput() { - for (const file of Array.from((this.$refs.fileInput as any).files)) { - this.upload(file, this.folder); - } - }, - - upload(file, folder) { - if (folder && typeof folder == 'object') folder = folder.id; - os.upload(file, folder).then(res => { - this.addFile(res, true); - }); - }, - - chooseFile(file) { - const isAlreadySelected = this.selectedFiles.some(f => f.id == file.id); - if (this.multiple) { - if (isAlreadySelected) { - this.selectedFiles = this.selectedFiles.filter(f => f.id != file.id); - } else { - this.selectedFiles.push(file); - } - this.$emit('change-selection', this.selectedFiles); - } else { - if (isAlreadySelected) { - this.$emit('selected', file); - } else { - this.selectedFiles = [file]; - this.$emit('change-selection', [file]); - } - } - }, - - chooseFolder(folder) { - const isAlreadySelected = this.selectedFolders.some(f => f.id == folder.id); - if (this.multiple) { - if (isAlreadySelected) { - this.selectedFolders = this.selectedFolders.filter(f => f.id != folder.id); - } else { - this.selectedFolders.push(folder); - } - this.$emit('change-selection', this.selectedFolders); - } else { - if (isAlreadySelected) { - this.$emit('selected', folder); - } else { - this.selectedFolders = [folder]; - this.$emit('change-selection', [folder]); - } - } - }, - - move(target) { - if (target == null) { - this.goRoot(); - return; - } else if (typeof target == 'object') { - target = target.id; - } - - this.fetching = true; - - os.api('drive/folders/show', { - folderId: target - }).then(folder => { - this.folder = folder; - this.hierarchyFolders = []; - - const dive = folder => { - this.hierarchyFolders.unshift(folder); - if (folder.parent) dive(folder.parent); - }; - - if (folder.parent) dive(folder.parent); - - this.$emit('open-folder', folder); - this.fetch(); - }); - }, - - addFolder(folder, unshift = false) { - const current = this.folder ? this.folder.id : null; - if (current != folder.parentId) return; - - if (this.folders.some(f => f.id == folder.id)) { - const exist = this.folders.map(f => f.id).indexOf(folder.id); - this.folders[exist] = folder; - return; - } - - if (unshift) { - this.folders.unshift(folder); - } else { - this.folders.push(folder); - } - }, - - addFile(file, unshift = false) { - const current = this.folder ? this.folder.id : null; - if (current != file.folderId) return; - - if (this.files.some(f => f.id == file.id)) { - const exist = this.files.map(f => f.id).indexOf(file.id); - this.files[exist] = file; - return; - } - - if (unshift) { - this.files.unshift(file); - } else { - this.files.push(file); - } - }, - - removeFolder(folder) { - if (typeof folder == 'object') folder = folder.id; - this.folders = this.folders.filter(f => f.id != folder); - }, - - removeFile(file) { - if (typeof file == 'object') file = file.id; - this.files = this.files.filter(f => f.id != file); - }, - - appendFile(file) { - this.addFile(file); - }, - - appendFolder(folder) { - this.addFolder(folder); - }, - - prependFile(file) { - this.addFile(file, true); - }, - - prependFolder(folder) { - this.addFolder(folder, true); - }, - - goRoot() { - // 既にrootにいるなら何もしない - if (this.folder == null) return; - - this.folder = null; - this.hierarchyFolders = []; - this.$emit('move-root'); - this.fetch(); - }, - - fetch() { - this.folders = []; - this.files = []; - this.moreFolders = false; - this.moreFiles = false; - this.fetching = true; - - let fetchedFolders = null; - let fetchedFiles = null; - - const foldersMax = 30; - const filesMax = 30; - - // フォルダ一覧取得 - os.api('drive/folders', { - folderId: this.folder ? this.folder.id : null, - limit: foldersMax + 1 - }).then(folders => { - if (folders.length == foldersMax + 1) { - this.moreFolders = true; - folders.pop(); - } - fetchedFolders = folders; - complete(); - }); - - // ファイル一覧取得 - os.api('drive/files', { - folderId: this.folder ? this.folder.id : null, - type: this.type, - limit: filesMax + 1 - }).then(files => { - if (files.length == filesMax + 1) { - this.moreFiles = true; - files.pop(); - } - fetchedFiles = files; - complete(); - }); - - let flag = false; - const complete = () => { - if (flag) { - for (const x of fetchedFolders) this.appendFolder(x); - for (const x of fetchedFiles) this.appendFile(x); - this.fetching = false; - } else { - flag = true; - } - }; - }, - - fetchMoreFiles() { - this.fetching = true; - - const max = 30; - - // ファイル一覧取得 - os.api('drive/files', { - folderId: this.folder ? this.folder.id : null, - type: this.type, - untilId: this.files[this.files.length - 1].id, - limit: max + 1 - }).then(files => { - if (files.length == max + 1) { - this.moreFiles = true; - files.pop(); - } else { - this.moreFiles = false; - } - for (const x of files) this.appendFile(x); - this.fetching = false; - }); - }, - - getMenu() { - return [{ - text: this.$ts.addFile, - type: 'label' - }, { - text: this.$ts.upload, - icon: 'fas fa-upload', - action: () => { this.selectLocalFile(); } - }, { - text: this.$ts.fromUrl, - icon: 'fas fa-link', - action: () => { this.urlUpload(); } - }, null, { - text: this.folder ? this.folder.name : this.$ts.drive, - type: 'label' - }, this.folder ? { - text: this.$ts.renameFolder, - icon: 'fas fa-i-cursor', - action: () => { this.renameFolder(this.folder); } - } : undefined, this.folder ? { - text: this.$ts.deleteFolder, - icon: 'fas fa-trash-alt', - action: () => { this.deleteFolder(this.folder); } - } : undefined, { - text: this.$ts.createFolder, - icon: 'fas fa-folder-plus', - action: () => { this.createFolder(); } - }]; - }, - - showMenu(ev) { - os.popupMenu(this.getMenu(), ev.currentTarget || ev.target); - }, - - onContextmenu(ev) { - os.contextMenu(this.getMenu(), ev); - }, +import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; + +const props = withDefaults(defineProps<{ + initialFolder?: Misskey.entities.DriveFolder; + type?: string; + multiple?: boolean; + select?: 'file' | 'folder' | null; +}>(), { + multiple: false, + select: null, +}); + +const emit = defineEmits<{ + (e: 'selected', v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder): void; + (e: 'change-selection', v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void; + (e: 'move-root'): void; + (e: 'cd', v: Misskey.entities.DriveFolder | null): void; + (e: 'open-folder', v: Misskey.entities.DriveFolder): void; +}>(); + +const loadMoreFiles = ref<InstanceType<typeof MkButton>>(); +const fileInput = ref<HTMLInputElement>(); + +const folder = ref<Misskey.entities.DriveFolder | null>(null); +const files = ref<Misskey.entities.DriveFile[]>([]); +const folders = ref<Misskey.entities.DriveFolder[]>([]); +const moreFiles = ref(false); +const moreFolders = ref(false); +const hierarchyFolders = ref<Misskey.entities.DriveFolder[]>([]); +const selectedFiles = ref<Misskey.entities.DriveFile[]>([]); +const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]); +const uploadings = os.uploads; +const connection = stream.useChannel('drive'); + +// ドロップされようとしているか +const draghover = ref(false); + +// 自身の所有するアイテムがドラッグをスタートさせたか +// (自分自身の階層にドロップできないようにするためのフラグ) +const isDragSource = ref(false); + +const fetching = ref(true); + +const ilFilesObserver = new IntersectionObserver( + (entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles() +) + +watch(folder, () => emit('cd', folder.value)); + +function onStreamDriveFileCreated(file: Misskey.entities.DriveFile) { + addFile(file, true); +} + +function onStreamDriveFileUpdated(file: Misskey.entities.DriveFile) { + const current = folder.value ? folder.value.id : null; + if (current != file.folderId) { + removeFile(file); + } else { + addFile(file, true); } +} + +function onStreamDriveFileDeleted(fileId: string) { + removeFile(fileId); +} + +function onStreamDriveFolderCreated(createdFolder: Misskey.entities.DriveFolder) { + addFolder(createdFolder, true); +} + +function onStreamDriveFolderUpdated(updatedFolder: Misskey.entities.DriveFolder) { + const current = folder.value ? folder.value.id : null; + if (current != updatedFolder.parentId) { + removeFolder(updatedFolder); + } else { + addFolder(updatedFolder, true); + } +} + +function onStreamDriveFolderDeleted(folderId: string) { + removeFolder(folderId); +} + +function onDragover(e: DragEvent): any { + if (!e.dataTransfer) return; + + // ドラッグ元が自分自身の所有するアイテムだったら + if (isDragSource.value) { + // 自分自身にはドロップさせない + e.dataTransfer.dropEffect = 'none'; + return; + } + + const isFile = e.dataTransfer.items[0].kind == 'file'; + const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + if (isFile || isDriveFile || isDriveFolder) { + e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + } else { + e.dataTransfer.dropEffect = 'none'; + } + + return false; +} + +function onDragenter() { + if (!isDragSource.value) draghover.value = true; +} + +function onDragleave() { + draghover.value = false; +} + +function onDrop(e: DragEvent): any { + draghover.value = false; + + if (!e.dataTransfer) return; + + // ドロップされてきたものがファイルだったら + if (e.dataTransfer.files.length > 0) { + for (const file of Array.from(e.dataTransfer.files)) { + upload(file, folder.value); + } + return; + } + + //#region ドライブのファイル + const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile != '') { + const file = JSON.parse(driveFile); + if (files.value.some(f => f.id == file.id)) return; + removeFile(file.id); + os.api('drive/files/update', { + fileId: file.id, + folderId: folder.value ? folder.value.id : null + }); + } + //#endregion + + //#region ドライブのフォルダ + const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + if (driveFolder != null && driveFolder != '') { + const droppedFolder = JSON.parse(driveFolder); + + // 移動先が自分自身ならreject + if (folder.value && droppedFolder.id == folder.value.id) return false; + if (folders.value.some(f => f.id == droppedFolder.id)) return false; + removeFolder(droppedFolder.id); + os.api('drive/folders/update', { + folderId: droppedFolder.id, + parentId: folder.value ? folder.value.id : null + }).then(() => { + // noop + }).catch(err => { + switch (err) { + case 'detected-circular-definition': + os.alert({ + title: i18n.locale.unableToProcess, + text: i18n.locale.circularReferenceFolder + }); + break; + default: + os.alert({ + type: 'error', + text: i18n.locale.somethingHappened + }); + } + }); + } + //#endregion +} + +function selectLocalFile() { + fileInput.value?.click(); +} + +function urlUpload() { + os.inputText({ + title: i18n.locale.uploadFromUrl, + type: 'url', + placeholder: i18n.locale.uploadFromUrlDescription + }).then(({ canceled, result: url }) => { + if (canceled || !url) return; + os.api('drive/files/upload-from-url', { + url: url, + folderId: folder.value ? folder.value.id : undefined + }); + + os.alert({ + title: i18n.locale.uploadFromUrlRequested, + text: i18n.locale.uploadFromUrlMayTakeTime + }); + }); +} + +function createFolder() { + os.inputText({ + title: i18n.locale.createFolder, + placeholder: i18n.locale.folderName + }).then(({ canceled, result: name }) => { + if (canceled) return; + os.api('drive/folders/create', { + name: name, + parentId: folder.value ? folder.value.id : undefined + }).then(createdFolder => { + addFolder(createdFolder, true); + }); + }); +} + +function renameFolder(folderToRename: Misskey.entities.DriveFolder) { + os.inputText({ + title: i18n.locale.renameFolder, + placeholder: i18n.locale.inputNewFolderName, + default: folderToRename.name + }).then(({ canceled, result: name }) => { + if (canceled) return; + os.api('drive/folders/update', { + folderId: folderToRename.id, + name: name + }).then(updatedFolder => { + // FIXME: 画面を更新するために自分自身に移動 + move(updatedFolder); + }); + }); +} + +function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) { + os.api('drive/folders/delete', { + folderId: folderToDelete.id + }).then(() => { + // 削除時に親フォルダに移動 + move(folderToDelete.parentId); + }).catch(err => { + switch(err.id) { + case 'b0fc8a17-963c-405d-bfbc-859a487295e1': + os.alert({ + type: 'error', + title: i18n.locale.unableToDelete, + text: i18n.locale.hasChildFilesOrFolders + }); + break; + default: + os.alert({ + type: 'error', + text: i18n.locale.unableToDelete + }); + } + }); +} + +function onChangeFileInput() { + if (!fileInput.value?.files) return; + for (const file of Array.from(fileInput.value.files)) { + upload(file, folder.value); + } +} + +function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null) { + os.upload(file, (folderToUpload && typeof folderToUpload == 'object') ? folderToUpload.id : null).then(res => { + addFile(res, true); + }); +} + +function chooseFile(file: Misskey.entities.DriveFile) { + const isAlreadySelected = selectedFiles.value.some(f => f.id == file.id); + if (props.multiple) { + if (isAlreadySelected) { + selectedFiles.value = selectedFiles.value.filter(f => f.id != file.id); + } else { + selectedFiles.value.push(file); + } + emit('change-selection', selectedFiles.value); + } else { + if (isAlreadySelected) { + emit('selected', file); + } else { + selectedFiles.value = [file]; + emit('change-selection', [file]); + } + } +} + +function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) { + const isAlreadySelected = selectedFolders.value.some(f => f.id == folderToChoose.id); + if (props.multiple) { + if (isAlreadySelected) { + selectedFolders.value = selectedFolders.value.filter(f => f.id != folderToChoose.id); + } else { + selectedFolders.value.push(folderToChoose); + } + emit('change-selection', selectedFolders.value); + } else { + if (isAlreadySelected) { + emit('selected', folderToChoose); + } else { + selectedFolders.value = [folderToChoose]; + emit('change-selection', [folderToChoose]); + } + } +} + +function move(target?: Misskey.entities.DriveFolder) { + if (!target) { + goRoot(); + return; + } else if (typeof target == 'object') { + target = target.id; + } + + fetching.value = true; + + os.api('drive/folders/show', { + folderId: target + }).then(folderToMove => { + folder.value = folderToMove; + hierarchyFolders.value = []; + + const dive = folderToDive => { + hierarchyFolders.value.unshift(folderToDive); + if (folderToDive.parent) dive(folderToDive.parent); + }; + + if (folderToMove.parent) dive(folderToMove.parent); + + emit('open-folder', folderToMove); + fetch(); + }); +} + +function addFolder(folderToAdd: Misskey.entities.DriveFolder, unshift = false) { + const current = folder.value ? folder.value.id : null; + if (current != folderToAdd.parentId) return; + + if (folders.value.some(f => f.id == folderToAdd.id)) { + const exist = folders.value.map(f => f.id).indexOf(folderToAdd.id); + folders.value[exist] = folderToAdd; + return; + } + + if (unshift) { + folders.value.unshift(folderToAdd); + } else { + folders.value.push(folderToAdd); + } +} + +function addFile(fileToAdd: Misskey.entities.DriveFile, unshift = false) { + const current = folder.value ? folder.value.id : null; + if (current != fileToAdd.folderId) return; + + if (files.value.some(f => f.id == fileToAdd.id)) { + const exist = files.value.map(f => f.id).indexOf(fileToAdd.id); + files.value[exist] = fileToAdd; + return; + } + + if (unshift) { + files.value.unshift(fileToAdd); + } else { + files.value.push(fileToAdd); + } +} + +function removeFolder(folderToRemove: Misskey.entities.DriveFolder | string) { + const folderIdToRemove = typeof folderToRemove === 'object' ? folderToRemove.id : folderToRemove; + folders.value = folders.value.filter(f => f.id != folderIdToRemove); +} + +function removeFile(file: Misskey.entities.DriveFile | string) { + const fileId = typeof file === 'object' ? file.id : file; + files.value = files.value.filter(f => f.id != fileId); +} + +function appendFile(file: Misskey.entities.DriveFile) { + addFile(file); +} + +function appendFolder(folderToAppend: Misskey.entities.DriveFolder) { + addFolder(folderToAppend); +} +/* +function prependFile(file: Misskey.entities.DriveFile) { + addFile(file, true); +} + +function prependFolder(folderToPrepend: Misskey.entities.DriveFolder) { + addFolder(folderToPrepend, true); +} +*/ +function goRoot() { + // 既にrootにいるなら何もしない + if (folder.value == null) return; + + folder.value = null; + hierarchyFolders.value = []; + emit('move-root'); + fetch(); +} + +async function fetch() { + folders.value = []; + files.value = []; + moreFolders.value = false; + moreFiles.value = false; + fetching.value = true; + + const foldersMax = 30; + const filesMax = 30; + + const foldersPromise = os.api('drive/folders', { + folderId: folder.value ? folder.value.id : null, + limit: foldersMax + 1 + }).then(fetchedFolders => { + if (fetchedFolders.length == foldersMax + 1) { + moreFolders.value = true; + fetchedFolders.pop(); + } + return fetchedFolders; + }); + + const filesPromise = os.api('drive/files', { + folderId: folder.value ? folder.value.id : null, + type: props.type, + limit: filesMax + 1 + }).then(fetchedFiles => { + if (fetchedFiles.length == filesMax + 1) { + moreFiles.value = true; + fetchedFiles.pop(); + } + return fetchedFiles; + }); + + const [fetchedFolders, fetchedFiles] = await Promise.all([foldersPromise, filesPromise]); + + for (const x of fetchedFolders) appendFolder(x); + for (const x of fetchedFiles) appendFile(x); + + fetching.value = false; +} + +function fetchMoreFiles() { + fetching.value = true; + + const max = 30; + + // ファイル一覧取得 + os.api('drive/files', { + folderId: folder.value ? folder.value.id : null, + type: props.type, + untilId: files.value[files.value.length - 1].id, + limit: max + 1 + }).then(files => { + if (files.length == max + 1) { + moreFiles.value = true; + files.pop(); + } else { + moreFiles.value = false; + } + for (const x of files) appendFile(x); + fetching.value = false; + }); +} + +function getMenu() { + return [{ + text: i18n.locale.addFile, + type: 'label' + }, { + text: i18n.locale.upload, + icon: 'fas fa-upload', + action: () => { selectLocalFile(); } + }, { + text: i18n.locale.fromUrl, + icon: 'fas fa-link', + action: () => { urlUpload(); } + }, null, { + text: folder.value ? folder.value.name : i18n.locale.drive, + type: 'label' + }, folder.value ? { + text: i18n.locale.renameFolder, + icon: 'fas fa-i-cursor', + action: () => { renameFolder(folder.value); } + } : undefined, folder.value ? { + text: i18n.locale.deleteFolder, + icon: 'fas fa-trash-alt', + action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); } + } : undefined, { + text: i18n.locale.createFolder, + icon: 'fas fa-folder-plus', + action: () => { createFolder(); } + }]; +} + +function showMenu(ev: MouseEvent) { + os.popupMenu(getMenu(), (ev.currentTarget || ev.target || undefined) as HTMLElement | undefined); +} + +function onContextmenu(ev: MouseEvent) { + os.contextMenu(getMenu(), ev); +} + +onMounted(() => { + if (defaultStore.state.enableInfiniteScroll && loadMoreFiles.value) { + nextTick(() => { + ilFilesObserver.observe(loadMoreFiles.value?.$el) + }); + } + + connection.on('fileCreated', onStreamDriveFileCreated); + connection.on('fileUpdated', onStreamDriveFileUpdated); + connection.on('fileDeleted', onStreamDriveFileDeleted); + connection.on('folderCreated', onStreamDriveFolderCreated); + connection.on('folderUpdated', onStreamDriveFolderUpdated); + connection.on('folderDeleted', onStreamDriveFolderDeleted); + + if (props.initialFolder) { + move(props.initialFolder); + } else { + fetch(); + } +}); + +onActivated(() => { + if (defaultStore.state.enableInfiniteScroll) { + nextTick(() => { + ilFilesObserver.observe(loadMoreFiles.value?.$el) + }); + } +}); + +onBeforeUnmount(() => { + connection.dispose(); + ilFilesObserver.disconnect(); }); </script> diff --git a/packages/client/src/components/emoji-picker-dialog.vue b/packages/client/src/components/emoji-picker-dialog.vue index 51c634dd8e..f06a24636c 100644 --- a/packages/client/src/components/emoji-picker-dialog.vue +++ b/packages/client/src/components/emoji-picker-dialog.vue @@ -1,58 +1,65 @@ <template> -<MkModal ref="modal" v-slot="{ type, maxHeight }" :z-priority="'middle'" :prefer-type="asReactionPicker && $store.state.reactionPickerUseDrawerForMobile === false ? 'popup' : 'auto'" :transparent-bg="true" :manual-showing="manualShowing" :src="src" @click="$refs.modal.close()" @opening="opening" @close="$emit('close')" @closed="$emit('closed')"> - <MkEmojiPicker ref="picker" class="ryghynhb _popup _shadow" :class="{ drawer: type === 'drawer' }" :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" :as-drawer="type === 'drawer'" :max-height="maxHeight" @chosen="chosen"/> +<MkModal + ref="modal" + v-slot="{ type, maxHeight }" + :z-priority="'middle'" + :prefer-type="asReactionPicker && defaultStore.state.reactionPickerUseDrawerForMobile === false ? 'popup' : 'auto'" + :transparent-bg="true" + :manual-showing="manualShowing" + :src="src" + @click="modal?.close()" + @opening="opening" + @close="emit('close')" + @closed="emit('closed')" +> + <MkEmojiPicker + ref="picker" + class="ryghynhb _popup _shadow" + :class="{ drawer: type === 'drawer' }" + :show-pinned="showPinned" + :as-reaction-picker="asReactionPicker" + :as-drawer="type === 'drawer'" + :max-height="maxHeight" + @chosen="chosen" + /> </MkModal> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; import MkModal from '@/components/ui/modal.vue'; import MkEmojiPicker from '@/components/emoji-picker.vue'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - MkModal, - MkEmojiPicker, - }, - - props: { - manualShowing: { - type: Boolean, - required: false, - default: null, - }, - src: { - required: false - }, - showPinned: { - required: false, - default: true - }, - asReactionPicker: { - required: false - }, - }, - - emits: ['done', 'close', 'closed'], - - data() { - return { - - }; - }, - - methods: { - chosen(emoji: any) { - this.$emit('done', emoji); - this.$refs.modal.close(); - }, - - opening() { - this.$refs.picker.reset(); - this.$refs.picker.focus(); - } - } +withDefaults(defineProps<{ + manualShowing?: boolean; + src?: HTMLElement; + showPinned?: boolean; + asReactionPicker?: boolean; +}>(), { + manualShowing: false, + showPinned: true, + asReactionPicker: false, }); + +const emit = defineEmits<{ + (e: 'done', v: any): void; + (e: 'close'): void; + (e: 'closed'): void; +}>(); + +const modal = ref<InstanceType<typeof MkModal>>(); +const picker = ref<InstanceType<typeof MkEmojiPicker>>(); + +function chosen(emoji: any) { + emit('done', emoji); + modal.value?.close(); +} + +function opening() { + picker.value?.reset(); + picker.value?.focus(); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/emoji-picker-window.vue b/packages/client/src/components/emoji-picker-window.vue index 0ffa0c1187..4d27fb48ba 100644 --- a/packages/client/src/components/emoji-picker-window.vue +++ b/packages/client/src/components/emoji-picker-window.vue @@ -5,50 +5,33 @@ :can-resize="false" :mini="true" :front="true" - @closed="$emit('closed')" + @closed="emit('closed')" > <MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen"/> </MkWindow> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkWindow from '@/components/ui/window.vue'; import MkEmojiPicker from '@/components/emoji-picker.vue'; -export default defineComponent({ - components: { - MkWindow, - MkEmojiPicker, - }, - - props: { - src: { - required: false - }, - showPinned: { - required: false, - default: true - }, - asReactionPicker: { - required: false - }, - }, - - emits: ['chosen', 'closed'], - - data() { - return { - - }; - }, - - methods: { - chosen(emoji: any) { - this.$emit('chosen', emoji); - }, - } +withDefaults(defineProps<{ + src?: HTMLElement; + showPinned?: boolean; + asReactionPicker?: boolean; +}>(), { + showPinned: true, }); + +const emit = defineEmits<{ + (e: 'chosen', v: any): void; + (e: 'closed'): void; +}>(); + +function chosen(emoji: any) { + emit('chosen', emoji); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/emoji-picker.section.vue b/packages/client/src/components/emoji-picker.section.vue index 08c4f6813d..1026e894d1 100644 --- a/packages/client/src/components/emoji-picker.section.vue +++ b/packages/client/src/components/emoji-picker.section.vue @@ -7,7 +7,7 @@ <button v-for="emoji in emojis" :key="emoji" class="_button" - @click="chosen(emoji, $event)" + @click="emit('chosen', emoji, $event)" > <MkEmoji :emoji="emoji" :normal="true"/> </button> @@ -15,35 +15,19 @@ </section> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import { getStaticImageUrl } from '@/scripts/get-static-image-url'; +<script lang="ts" setup> +import { ref } from 'vue'; -export default defineComponent({ - props: { - emojis: { - required: true, - }, - initialShown: { - required: false - } - }, +const props = defineProps<{ + emojis: string[]; + initialShown?: boolean; +}>(); - emits: ['chosen'], +const emit = defineEmits<{ + (e: 'chosen', v: string, ev: MouseEvent): void; +}>(); - data() { - return { - getStaticImageUrl, - shown: this.initialShown, - }; - }, - - methods: { - chosen(emoji: any, ev) { - this.$parent.chosen(emoji, ev); - }, - } -}); +const shown = ref(!!props.initialShown); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/emoji-picker.vue b/packages/client/src/components/emoji-picker.vue index a8eed1ca21..96670fa58c 100644 --- a/packages/client/src/components/emoji-picker.vue +++ b/packages/client/src/components/emoji-picker.vue @@ -1,18 +1,18 @@ <template> -<div class="omfetrab" :class="['w' + width, 'h' + height, { big, asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : null }"> - <input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="$ts.search" @paste.stop="paste" @keyup.enter="done()"> +<div class="omfetrab" :class="['w' + width, 'h' + height, { big, asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> + <input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.locale.search" @paste.stop="paste" @keyup.enter="done()"> <div ref="emojis" class="emojis"> <section class="result"> <div v-if="searchResultCustom.length > 0"> <button v-for="emoji in searchResultCustom" - :key="emoji" + :key="emoji.id" class="_button" :title="emoji.name" tabindex="0" @click="chosen(emoji, $event)" > - <MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/> - <img v-else :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> + <!--<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>--> + <img :src="disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> </button> </div> <div v-if="searchResultUnicode.length > 0"> @@ -43,9 +43,9 @@ </section> <section> - <header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ $ts.recentUsed }}</header> + <header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ i18n.locale.recentUsed }}</header> <div> - <button v-for="emoji in $store.state.recentlyUsedEmojis" + <button v-for="emoji in recentlyUsedEmojis" :key="emoji" class="_button" @click="chosen(emoji, $event)" @@ -56,12 +56,12 @@ </section> </div> <div> - <header class="_acrylic">{{ $ts.customEmojis }}</header> - <XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')">{{ category || $ts.other }}</XSection> + <header class="_acrylic">{{ i18n.locale.customEmojis }}</header> + <XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')" @chosen="chosen">{{ category || i18n.locale.other }}</XSection> </div> <div> - <header class="_acrylic">{{ $ts.emoji }}</header> - <XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)">{{ category }}</XSection> + <header class="_acrylic">{{ i18n.locale.emoji }}</header> + <XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection> </div> </div> <div class="tabs"> @@ -73,277 +73,272 @@ </div> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import { emojilist } from '@/scripts/emojilist'; +<script lang="ts" setup> +import { ref, computed, watch, onMounted } from 'vue'; +import * as Misskey from 'misskey-js'; +import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import Ripple from '@/components/ripple.vue'; import * as os from '@/os'; import { isTouchUsing } from '@/scripts/touch'; import { isMobile } from '@/scripts/is-mobile'; -import { emojiCategories } from '@/instance'; +import { emojiCategories, instance } from '@/instance'; import XSection from './emoji-picker.section.vue'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - XSection - }, +const props = withDefaults(defineProps<{ + showPinned?: boolean; + asReactionPicker?: boolean; + maxHeight?: number; + asDrawer?: boolean; +}>(), { + showPinned: true, +}); - props: { - showPinned: { - required: false, - default: true, - }, - asReactionPicker: { - required: false, - }, - maxHeight: { - type: Number, - required: false, - }, - asDrawer: { - type: Boolean, - required: false - }, - }, +const emit = defineEmits<{ + (e: 'chosen', v: string): void; +}>(); - emits: ['chosen'], +const search = ref<HTMLInputElement>(); +const emojis = ref<HTMLDivElement>(); - data() { - return { - emojilist: markRaw(emojilist), - getStaticImageUrl, - pinned: this.$store.reactiveState.reactions, - width: this.asReactionPicker ? this.$store.state.reactionPickerWidth : 3, - height: this.asReactionPicker ? this.$store.state.reactionPickerHeight : 2, - big: this.asReactionPicker ? isTouchUsing : false, - customEmojiCategories: emojiCategories, - customEmojis: this.$instance.emojis, - q: null, - searchResultCustom: [], - searchResultUnicode: [], - tab: 'index', - categories: ['face', 'people', 'animals_and_nature', 'food_and_drink', 'activity', 'travel_and_places', 'objects', 'symbols', 'flags'], - }; - }, +const { + reactions: pinned, + reactionPickerWidth, + reactionPickerHeight, + disableShowingAnimatedImages, + recentlyUsedEmojis, +} = defaultStore.reactiveState; - watch: { - q() { - this.$refs.emojis.scrollTop = 0; +const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3); +const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2); +const big = props.asReactionPicker ? isTouchUsing : false; +const customEmojiCategories = emojiCategories; +const customEmojis = instance.emojis; +const q = ref<string | null>(null); +const searchResultCustom = ref<Misskey.entities.CustomEmoji[]>([]); +const searchResultUnicode = ref<UnicodeEmojiDef[]>([]); +const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index'); - if (this.q == null || this.q === '') { - this.searchResultCustom = []; - this.searchResultUnicode = []; - return; - } +watch(q, () => { + if (emojis.value) emojis.value.scrollTop = 0; - const q = this.q.replace(/:/g, ''); - - const searchCustom = () => { - const max = 8; - const emojis = this.customEmojis; - const matches = new Set(); - - const exactMatch = emojis.find(e => e.name === q); - if (exactMatch) matches.add(exactMatch); - - if (q.includes(' ')) { // AND検索 - const keywords = q.split(' '); - - // 名前にキーワードが含まれている - for (const emoji of emojis) { - if (keywords.every(keyword => emoji.name.includes(keyword))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; - - // 名前またはエイリアスにキーワードが含まれている - for (const emoji of emojis) { - if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - } else { - for (const emoji of emojis) { - if (emoji.name.startsWith(q)) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; - - for (const emoji of emojis) { - if (emoji.aliases.some(alias => alias.startsWith(q))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; - - for (const emoji of emojis) { - if (emoji.name.includes(q)) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; - - for (const emoji of emojis) { - if (emoji.aliases.some(alias => alias.includes(q))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - } - - return matches; - }; - - const searchUnicode = () => { - const max = 8; - const emojis = this.emojilist; - const matches = new Set(); - - const exactMatch = emojis.find(e => e.name === q); - if (exactMatch) matches.add(exactMatch); - - if (q.includes(' ')) { // AND検索 - const keywords = q.split(' '); - - // 名前にキーワードが含まれている - for (const emoji of emojis) { - if (keywords.every(keyword => emoji.name.includes(keyword))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; - - // 名前またはエイリアスにキーワードが含まれている - for (const emoji of emojis) { - if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.keywords.some(alias => alias.includes(keyword)))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - } else { - for (const emoji of emojis) { - if (emoji.name.startsWith(q)) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; - - for (const emoji of emojis) { - if (emoji.keywords.some(keyword => keyword.startsWith(q))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; - - for (const emoji of emojis) { - if (emoji.name.includes(q)) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; - - for (const emoji of emojis) { - if (emoji.keywords.some(keyword => keyword.includes(q))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - } - - return matches; - }; - - this.searchResultCustom = Array.from(searchCustom()); - this.searchResultUnicode = Array.from(searchUnicode()); - } - }, - - mounted() { - this.focus(); - }, - - methods: { - focus() { - if (!isMobile && !isTouchUsing) { - this.$refs.search.focus({ - preventScroll: true - }); - } - }, - - reset() { - this.$refs.emojis.scrollTop = 0; - this.q = ''; - }, - - getKey(emoji: any) { - return typeof emoji === 'string' ? emoji : (emoji.char || `:${emoji.name}:`); - }, - - chosen(emoji: any, ev) { - if (ev) { - const el = ev.currentTarget || ev.target; - const rect = el.getBoundingClientRect(); - const x = rect.left + (el.offsetWidth / 2); - const y = rect.top + (el.offsetHeight / 2); - os.popup(Ripple, { x, y }, {}, 'end'); - } - - const key = this.getKey(emoji); - this.$emit('chosen', key); - - // 最近使った絵文字更新 - if (!this.pinned.includes(key)) { - let recents = this.$store.state.recentlyUsedEmojis; - recents = recents.filter((e: any) => e !== key); - recents.unshift(key); - this.$store.set('recentlyUsedEmojis', recents.splice(0, 32)); - } - }, - - paste(event) { - const paste = (event.clipboardData || window.clipboardData).getData('text'); - if (this.done(paste)) { - event.preventDefault(); - } - }, - - done(query) { - if (query == null) query = this.q; - if (query == null) return; - const q = query.replace(/:/g, ''); - const exactMatchCustom = this.customEmojis.find(e => e.name === q); - if (exactMatchCustom) { - this.chosen(exactMatchCustom); - return true; - } - const exactMatchUnicode = this.emojilist.find(e => e.char === q || e.name === q); - if (exactMatchUnicode) { - this.chosen(exactMatchUnicode); - return true; - } - if (this.searchResultCustom.length > 0) { - this.chosen(this.searchResultCustom[0]); - return true; - } - if (this.searchResultUnicode.length > 0) { - this.chosen(this.searchResultUnicode[0]); - return true; - } - }, + if (q.value == null || q.value === '') { + searchResultCustom.value = []; + searchResultUnicode.value = []; + return; } + + const newQ = q.value.replace(/:/g, ''); + + const searchCustom = () => { + const max = 8; + const emojis = customEmojis; + const matches = new Set<Misskey.entities.CustomEmoji>(); + + const exactMatch = emojis.find(e => e.name === newQ); + if (exactMatch) matches.add(exactMatch); + + if (newQ.includes(' ')) { // AND検索 + const keywords = newQ.split(' '); + + // 名前にキーワードが含まれている + for (const emoji of emojis) { + if (keywords.every(keyword => emoji.name.includes(keyword))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; + + // 名前またはエイリアスにキーワードが含まれている + for (const emoji of emojis) { + if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + } else { + for (const emoji of emojis) { + if (emoji.name.startsWith(newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; + + for (const emoji of emojis) { + if (emoji.aliases.some(alias => alias.startsWith(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; + + for (const emoji of emojis) { + if (emoji.name.includes(newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; + + for (const emoji of emojis) { + if (emoji.aliases.some(alias => alias.includes(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + } + + return matches; + }; + + const searchUnicode = () => { + const max = 8; + const emojis = emojilist; + const matches = new Set<UnicodeEmojiDef>(); + + const exactMatch = emojis.find(e => e.name === newQ); + if (exactMatch) matches.add(exactMatch); + + if (newQ.includes(' ')) { // AND検索 + const keywords = newQ.split(' '); + + // 名前にキーワードが含まれている + for (const emoji of emojis) { + if (keywords.every(keyword => emoji.name.includes(keyword))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; + + // 名前またはエイリアスにキーワードが含まれている + for (const emoji of emojis) { + if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.keywords.some(alias => alias.includes(keyword)))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + } else { + for (const emoji of emojis) { + if (emoji.name.startsWith(newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; + + for (const emoji of emojis) { + if (emoji.keywords.some(keyword => keyword.startsWith(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; + + for (const emoji of emojis) { + if (emoji.name.includes(newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; + + for (const emoji of emojis) { + if (emoji.keywords.some(keyword => keyword.includes(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + } + + return matches; + }; + + searchResultCustom.value = Array.from(searchCustom()); + searchResultUnicode.value = Array.from(searchUnicode()); +}); + +function focus() { + if (!isMobile && !isTouchUsing) { + search.value?.focus({ + preventScroll: true + }); + } +} + +function reset() { + if (emojis.value) emojis.value.scrollTop = 0; + q.value = ''; +} + +function getKey(emoji: string | Misskey.entities.CustomEmoji | UnicodeEmojiDef): string { + return typeof emoji === 'string' ? emoji : (emoji.char || `:${emoji.name}:`); +} + +function chosen(emoji: any, ev?: MouseEvent) { + const el = ev && (ev.currentTarget || ev.target) as HTMLElement | null | undefined; + if (el) { + const rect = el.getBoundingClientRect(); + const x = rect.left + (el.offsetWidth / 2); + const y = rect.top + (el.offsetHeight / 2); + os.popup(Ripple, { x, y }, {}, 'end'); + } + + const key = getKey(emoji); + emit('chosen', key); + + // 最近使った絵文字更新 + if (!pinned.value.includes(key)) { + let recents = defaultStore.state.recentlyUsedEmojis; + recents = recents.filter((e: any) => e !== key); + recents.unshift(key); + defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32)); + } +} + +function paste(event: ClipboardEvent) { + const paste = (event.clipboardData || window.clipboardData).getData('text'); + if (done(paste)) { + event.preventDefault(); + } +} + +function done(query?: any): boolean | void { + if (query == null) query = q.value; + if (query == null || typeof query !== 'string') return; + + const q2 = query.replace(/:/g, ''); + const exactMatchCustom = customEmojis.find(e => e.name === q2); + if (exactMatchCustom) { + chosen(exactMatchCustom); + return true; + } + const exactMatchUnicode = emojilist.find(e => e.char === q2 || e.name === q2); + if (exactMatchUnicode) { + chosen(exactMatchUnicode); + return true; + } + if (searchResultCustom.value.length > 0) { + chosen(searchResultCustom.value[0]); + return true; + } + if (searchResultUnicode.value.length > 0) { + chosen(searchResultUnicode.value[0]); + return true; + } +} + +onMounted(() => { + focus(); +}); + +defineExpose({ + focus, + reset, }); </script> diff --git a/packages/client/src/components/featured-photos.vue b/packages/client/src/components/featured-photos.vue index af5892c98e..e58b5d2849 100644 --- a/packages/client/src/components/featured-photos.vue +++ b/packages/client/src/components/featured-photos.vue @@ -2,25 +2,15 @@ <div v-if="meta" class="xfbouadm" :style="{ backgroundImage: `url(${ meta.backgroundImageUrl })` }"></div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; import * as os from '@/os'; -export default defineComponent({ - components: { - }, +const meta = ref<Misskey.entities.DetailedInstanceMetadata>(); - data() { - return { - meta: null, - }; - }, - - created() { - os.api('meta', { detail: true }).then(meta => { - this.meta = meta; - }); - }, +os.api('meta', { detail: true }).then(gotMeta => { + meta.value = gotMeta; }); </script> diff --git a/packages/client/src/components/follow-button.vue b/packages/client/src/components/follow-button.vue index b16b22f26f..345edb6441 100644 --- a/packages/client/src/components/follow-button.vue +++ b/packages/client/src/components/follow-button.vue @@ -6,129 +6,110 @@ > <template v-if="!wait"> <template v-if="hasPendingFollowRequestFromYou && user.isLocked"> - <span v-if="full">{{ $ts.followRequestPending }}</span><i class="fas fa-hourglass-half"></i> + <span v-if="full">{{ i18n.locale.followRequestPending }}</span><i class="fas fa-hourglass-half"></i> </template> <template v-else-if="hasPendingFollowRequestFromYou && !user.isLocked"> <!-- つまりリモートフォローの場合。 --> - <span v-if="full">{{ $ts.processing }}</span><i class="fas fa-spinner fa-pulse"></i> + <span v-if="full">{{ i18n.locale.processing }}</span><i class="fas fa-spinner fa-pulse"></i> </template> <template v-else-if="isFollowing"> - <span v-if="full">{{ $ts.unfollow }}</span><i class="fas fa-minus"></i> + <span v-if="full">{{ i18n.locale.unfollow }}</span><i class="fas fa-minus"></i> </template> <template v-else-if="!isFollowing && user.isLocked"> - <span v-if="full">{{ $ts.followRequest }}</span><i class="fas fa-plus"></i> + <span v-if="full">{{ i18n.locale.followRequest }}</span><i class="fas fa-plus"></i> </template> <template v-else-if="!isFollowing && !user.isLocked"> - <span v-if="full">{{ $ts.follow }}</span><i class="fas fa-plus"></i> + <span v-if="full">{{ i18n.locale.follow }}</span><i class="fas fa-plus"></i> </template> </template> <template v-else> - <span v-if="full">{{ $ts.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> + <span v-if="full">{{ i18n.locale.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> </template> </button> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { onBeforeUnmount, onMounted, ref } from 'vue'; +import * as Misskey from 'misskey-js'; import * as os from '@/os'; import { stream } from '@/stream'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - user: { - type: Object, - required: true - }, - full: { - type: Boolean, - required: false, - default: false, - }, - large: { - type: Boolean, - required: false, - default: false, - }, - }, +const props = withDefaults(defineProps<{ + user: Misskey.entities.UserDetailed, + full?: boolean, + large?: boolean, +}>(), { + full: false, + large: false, +}); - data() { - return { - isFollowing: this.user.isFollowing, - hasPendingFollowRequestFromYou: this.user.hasPendingFollowRequestFromYou, - wait: false, - connection: null, - }; - }, +const isFollowing = ref(props.user.isFollowing); +const hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFromYou); +const wait = ref(false); +const connection = stream.useChannel('main'); - created() { - // 渡されたユーザー情報が不完全な場合 - if (this.user.isFollowing == null) { - os.api('users/show', { - userId: this.user.id - }).then(u => { - this.isFollowing = u.isFollowing; - this.hasPendingFollowRequestFromYou = u.hasPendingFollowRequestFromYou; - }); - } - }, +if (props.user.isFollowing == null) { + os.api('users/show', { + userId: props.user.id + }).then(u => { + isFollowing.value = u.isFollowing; + hasPendingFollowRequestFromYou.value = u.hasPendingFollowRequestFromYou; + }); +} - mounted() { - this.connection = markRaw(stream.useChannel('main')); - - this.connection.on('follow', this.onFollowChange); - this.connection.on('unfollow', this.onFollowChange); - }, - - beforeUnmount() { - this.connection.dispose(); - }, - - methods: { - onFollowChange(user) { - if (user.id == this.user.id) { - this.isFollowing = user.isFollowing; - this.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; - } - }, - - async onClick() { - this.wait = true; - - try { - if (this.isFollowing) { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('unfollowConfirm', { name: this.user.name || this.user.username }), - }); - - if (canceled) return; - - await os.api('following/delete', { - userId: this.user.id - }); - } else { - if (this.hasPendingFollowRequestFromYou) { - await os.api('following/requests/cancel', { - userId: this.user.id - }); - } else if (this.user.isLocked) { - await os.api('following/create', { - userId: this.user.id - }); - this.hasPendingFollowRequestFromYou = true; - } else { - await os.api('following/create', { - userId: this.user.id - }); - this.hasPendingFollowRequestFromYou = true; - } - } - } catch (e) { - console.error(e); - } finally { - this.wait = false; - } - } +function onFollowChange(user: Misskey.entities.UserDetailed) { + if (user.id == props.user.id) { + isFollowing.value = user.isFollowing; + hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou; } +} + +async function onClick() { + wait.value = true; + + try { + if (isFollowing.value) { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }), + }); + + if (canceled) return; + + await os.api('following/delete', { + userId: props.user.id + }); + } else { + if (hasPendingFollowRequestFromYou.value) { + await os.api('following/requests/cancel', { + userId: props.user.id + }); + } else if (props.user.isLocked) { + await os.api('following/create', { + userId: props.user.id + }); + hasPendingFollowRequestFromYou.value = true; + } else { + await os.api('following/create', { + userId: props.user.id + }); + hasPendingFollowRequestFromYou.value = true; + } + } + } catch (e) { + console.error(e); + } finally { + wait.value = false; + } +} + +onMounted(() => { + connection.on('follow', onFollowChange); + connection.on('unfollow', onFollowChange); +}); + +onBeforeUnmount(() => { + connection.dispose(); }); </script> diff --git a/packages/client/src/components/forgot-password.vue b/packages/client/src/components/forgot-password.vue index b03a6133b4..c74e1ac75e 100644 --- a/packages/client/src/components/forgot-password.vue +++ b/packages/client/src/components/forgot-password.vue @@ -2,72 +2,64 @@ <XModalWindow ref="dialog" :width="370" :height="400" - @close="$refs.dialog.close()" - @closed="$emit('closed')" + @close="dialog.close()" + @closed="emit('closed')" > - <template #header>{{ $ts.forgotPassword }}</template> + <template #header>{{ i18n.locale.forgotPassword }}</template> - <form v-if="$instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit"> + <form v-if="instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit"> <div class="main _formRoot"> <MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required> - <template #label>{{ $ts.username }}</template> + <template #label>{{ i18n.locale.username }}</template> <template #prefix>@</template> </MkInput> <MkInput v-model="email" class="_formBlock" type="email" spellcheck="false" required> - <template #label>{{ $ts.emailAddress }}</template> - <template #caption>{{ $ts._forgotPassword.enterEmail }}</template> + <template #label>{{ i18n.locale.emailAddress }}</template> + <template #caption>{{ i18n.locale._forgotPassword.enterEmail }}</template> </MkInput> - <MkButton class="_formBlock" type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ $ts.send }}</MkButton> + <MkButton class="_formBlock" type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ i18n.locale.send }}</MkButton> </div> <div class="sub"> - <MkA to="/about" class="_link">{{ $ts._forgotPassword.ifNoEmail }}</MkA> + <MkA to="/about" class="_link">{{ i18n.locale._forgotPassword.ifNoEmail }}</MkA> </div> </form> - <div v-else> - {{ $ts._forgotPassword.contactAdmin }} + <div v-else class="bafecedb"> + {{ i18n.locale._forgotPassword.contactAdmin }} </div> </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import * as os from '@/os'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XModalWindow, - MkButton, - MkInput, - }, +const emit = defineEmits<{ + (e: 'done'): void; + (e: 'closed'): void; +}>(); - emits: ['done', 'closed'], +let dialog: InstanceType<typeof XModalWindow> = $ref(); - data() { - return { - username: '', - email: '', - processing: false, - }; - }, +let username = $ref(''); +let email = $ref(''); +let processing = $ref(false); - methods: { - async onSubmit() { - this.processing = true; - await os.apiWithDialog('request-reset-password', { - username: this.username, - email: this.email, - }); - - this.$emit('done'); - this.$refs.dialog.close(); - } - } -}); +async function onSubmit() { + processing = true; + await os.apiWithDialog('request-reset-password', { + username, + email, + }); + emit('done'); + dialog.close(); +} </script> <style lang="scss" scoped> @@ -81,4 +73,8 @@ export default defineComponent({ padding: 24px; } } + +.bafecedb { + padding: 24px; +} </style> diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index dd7fdea4bd..378523e1bc 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -541,7 +541,7 @@ export const uploads = ref<{ img: string; }[]>([]); -export function upload(file: File, folder?: any, name?: string) { +export function upload(file: File, folder?: any, name?: string): Promise<Misskey.entities.DriveFile> { if (folder && typeof folder == 'object') folder = folder.id; return new Promise((resolve, reject) => { diff --git a/packages/client/src/scripts/emojilist.ts b/packages/client/src/scripts/emojilist.ts index de7591f5a0..bd8689e4f8 100644 --- a/packages/client/src/scripts/emojilist.ts +++ b/packages/client/src/scripts/emojilist.ts @@ -1,7 +1,11 @@ -// initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb -export const emojilist = require('../emojilist.json') as { +export const unicodeEmojiCategories = ['face', 'people', 'animals_and_nature', 'food_and_drink', 'activity', 'travel_and_places', 'objects', 'symbols', 'flags'] as const; + +export type UnicodeEmojiDef = { name: string; keywords: string[]; char: string; - category: 'people' | 'animals_and_nature' | 'food_and_drink' | 'activity' | 'travel_and_places' | 'objects' | 'symbols' | 'flags'; -}[]; + category: typeof unicodeEmojiCategories[number]; +} + +// initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb +export const emojilist = require('../emojilist.json') as UnicodeEmojiDef[]; From f14aba65c56132925f8d1c35d861504b91b3ace2 Mon Sep 17 00:00:00 2001 From: xianon <xianon@hotmail.co.jp> Date: Wed, 19 Jan 2022 00:25:29 +0900 Subject: [PATCH 55/81] =?UTF-8?q?=E3=83=A2=E3=83=90=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E7=94=BB=E9=9D=A2=E3=81=A7=E8=A1=A8=E7=A4=BA=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=9B=B4=E5=BE=8C=E3=81=AB=E3=83=98=E3=83=83=E3=83=80=E3=83=BC?= =?UTF-8?q?=E3=83=A1=E3=83=8B=E3=83=A5=E3=83=BC=E3=82=92=E3=82=BF=E3=83=83?= =?UTF-8?q?=E3=83=97=E3=81=97=E3=81=A6=E3=82=82=E3=83=9D=E3=83=83=E3=83=97?= =?UTF-8?q?=E3=82=A2=E3=83=83=E3=83=97=E3=81=AB=E3=81=AA=E3=82=89=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B=20(#8160?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/scripts/touch.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/client/src/scripts/touch.ts b/packages/client/src/scripts/touch.ts index 06b4f8b2ed..5251bc2e27 100644 --- a/packages/client/src/scripts/touch.ts +++ b/packages/client/src/scripts/touch.ts @@ -14,6 +14,10 @@ if (isTouchSupported) { }, { passive: true }); window.addEventListener('touchend', () => { + // 子要素のtouchstartイベントでstopPropagation()が呼ばれると親要素に伝搬されずタッチされたと判定されないため、 + // touchendイベントでもtouchstartイベントと同様にtrueにする + isTouchUsing = true; + isScreenTouching = false; }, { passive: true }); } From d7d533f9def7ce5a8d80f5e3599480784410eb70 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Wed, 19 Jan 2022 23:51:28 +0900 Subject: [PATCH 56/81] fix #8158 --- packages/client/src/components/post-form.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue index 3fcb1d906b..04a93d7388 100644 --- a/packages/client/src/components/post-form.vue +++ b/packages/client/src/components/post-form.vue @@ -339,8 +339,8 @@ function focus() { } function chooseFileFrom(ev) { - selectFiles(ev.currentTarget || ev.target, i18n.locale.attachFile).then(files => { - for (const file of files) { + selectFiles(ev.currentTarget || ev.target, i18n.locale.attachFile).then(files_ => { + for (const file of files_) { files.push(file); } }); From b5ccac3cb56fc34187813d1d276bc5b134885f96 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Wed, 19 Jan 2022 23:51:37 +0900 Subject: [PATCH 57/81] refactor --- packages/client/src/components/drive.folder.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/drive.folder.vue b/packages/client/src/components/drive.folder.vue index 8e8d9f3bb3..5b8b9ef5a9 100644 --- a/packages/client/src/components/drive.folder.vue +++ b/packages/client/src/components/drive.folder.vue @@ -225,7 +225,7 @@ function setAsUploadFolder() { defaultStore.set('uploadFolder', props.folder.id); } -function onContextmenu(e) { +function onContextmenu(ev: MouseEvent) { os.contextMenu([{ text: i18n.locale.openInWindow, icon: 'fas fa-window-restore', @@ -244,7 +244,7 @@ function onContextmenu(e) { icon: 'fas fa-trash-alt', danger: true, action: deleteFolder, - }], e); + }], ev); } </script> From 2a1d9a7428e0f1581dbbba29aa3c96ff6fdd864d Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 20 Jan 2022 01:27:41 +0900 Subject: [PATCH 58/81] refactor --- .../client/src/components/drive.folder.vue | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/client/src/components/drive.folder.vue b/packages/client/src/components/drive.folder.vue index 5b8b9ef5a9..20a6343cfe 100644 --- a/packages/client/src/components/drive.folder.vue +++ b/packages/client/src/components/drive.folder.vue @@ -43,13 +43,13 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'chosen', v: Misskey.entities.DriveFolder): void; - (e: 'move', v: Misskey.entities.DriveFolder): void; - (e: 'upload', file: File, folder: Misskey.entities.DriveFolder); - (e: 'removeFile', v: Misskey.entities.DriveFile['id']): void; - (e: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void; - (e: 'dragstart'): void; - (e: 'dragend'): void; + (ev: 'chosen', v: Misskey.entities.DriveFolder): void; + (ev: 'move', v: Misskey.entities.DriveFolder): void; + (ev: 'upload', file: File, folder: Misskey.entities.DriveFolder); + (ev: 'removeFile', v: Misskey.entities.DriveFile['id']): void; + (ev: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void; + (ev: 'dragstart'): void; + (ev: 'dragend'): void; }>(); const hover = ref(false); @@ -58,7 +58,7 @@ const isDragging = ref(false); const title = computed(() => props.folder.name); -function checkboxClicked(e) { +function checkboxClicked() { emit('chosen', props.folder); } @@ -74,24 +74,24 @@ function onMouseout() { hover.value = false } -function onDragover(e: DragEvent) { - if (!e.dataTransfer) return; +function onDragover(ev: DragEvent) { + if (!ev.dataTransfer) return; // 自分自身がドラッグされている場合 if (isDragging.value) { // 自分自身にはドロップさせない - e.dataTransfer.dropEffect = 'none'; + ev.dataTransfer.dropEffect = 'none'; return; } - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + const isFile = ev.dataTransfer.items[0].kind == 'file'; + const isDriveFile = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; } else { - e.dataTransfer.dropEffect = 'none'; + ev.dataTransfer.dropEffect = 'none'; } } @@ -103,21 +103,21 @@ function onDragleave() { draghover.value = false; } -function onDrop(e: DragEvent) { +function onDrop(ev: DragEvent) { draghover.value = false; - if (!e.dataTransfer) return; + if (!ev.dataTransfer) return; // ファイルだったら - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { + if (ev.dataTransfer.files.length > 0) { + for (const file of Array.from(ev.dataTransfer.files)) { emit('upload', file, props.folder); } return; } //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); if (driveFile != null && driveFile != '') { const file = JSON.parse(driveFile); emit('removeFile', file.id); @@ -129,7 +129,7 @@ function onDrop(e: DragEvent) { //#endregion //#region ドライブのフォルダ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); if (driveFolder != null && driveFolder != '') { const folder = JSON.parse(driveFolder); @@ -161,11 +161,11 @@ function onDrop(e: DragEvent) { //#endregion } -function onDragstart(e: DragEvent) { - if (!e.dataTransfer) return; +function onDragstart(ev: DragEvent) { + if (!ev.dataTransfer) return; - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FOLDER_, JSON.stringify(props.folder)); + ev.dataTransfer.effectAllowed = 'move'; + ev.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FOLDER_, JSON.stringify(props.folder)); isDragging.value = true; // 親ブラウザに対して、ドラッグが開始されたフラグを立てる From 8b5a1faaa4724da596bfab371763dc632995c8ed Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 20 Jan 2022 02:33:20 +0900 Subject: [PATCH 59/81] refactor(server): use insert instead of save --- packages/backend/src/services/drive/add-file.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index 38793412fa..1153cf7375 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -107,7 +107,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.size = size; file.storedInternal = false; - return await DriveFiles.save(file); + return await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); } else { // use internal storage const accessKey = uuid(); const thumbnailAccessKey = 'thumbnail-' + uuid(); @@ -140,7 +140,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.md5 = hash; file.size = size; - return await DriveFiles.save(file); + return await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); } } @@ -436,7 +436,7 @@ export default async function( file.type = info.type.mime; file.storedInternal = false; - file = await DriveFiles.save(file); + file = await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); } catch (e) { // duplicate key error (when already registered) if (isDuplicateKeyValueError(e)) { From 53937e09a0bef968dfd6d5b0f2580de876685aa0 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 20 Jan 2022 02:40:13 +0900 Subject: [PATCH 60/81] feat(server): store mime type of webpublic --- .../1642613870898-drive-file-webpublic-type.js | 13 +++++++++++++ packages/backend/src/models/entities/drive-file.ts | 5 +++++ packages/backend/src/services/drive/add-file.ts | 2 ++ 3 files changed, 20 insertions(+) create mode 100644 packages/backend/migration/1642613870898-drive-file-webpublic-type.js diff --git a/packages/backend/migration/1642613870898-drive-file-webpublic-type.js b/packages/backend/migration/1642613870898-drive-file-webpublic-type.js new file mode 100644 index 0000000000..e10c2ac2d2 --- /dev/null +++ b/packages/backend/migration/1642613870898-drive-file-webpublic-type.js @@ -0,0 +1,13 @@ +const { MigrationInterface, QueryRunner } = require("typeorm"); + +module.exports = class driveFileWebpublicType1642613870898 { + name = 'driveFileWebpublicType1642613870898' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "drive_file" ADD "webpublicType" character varying(128)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "webpublicType"`); + } +} diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts index 0af52d7cc0..cec86880f5 100644 --- a/packages/backend/src/models/entities/drive-file.ts +++ b/packages/backend/src/models/entities/drive-file.ts @@ -101,6 +101,11 @@ export class DriveFile { }) public webpublicUrl: string | null; + @Column('varchar', { + length: 128, nullable: true, + }) + public webpublicType: string | null; + @Index({ unique: true }) @Column('varchar', { length: 256, nullable: true, diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index 1153cf7375..6ac4f6d11d 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -101,6 +101,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.accessKey = key; file.thumbnailAccessKey = thumbnailKey; file.webpublicAccessKey = webpublicKey; + file.webpublicType = alts.webpublic?.type ?? null; file.name = name; file.type = type; file.md5 = hash; @@ -135,6 +136,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.accessKey = accessKey; file.thumbnailAccessKey = thumbnailAccessKey; file.webpublicAccessKey = webpublicAccessKey; + file.webpublicType = alts.webpublic?.type ?? null; file.name = name; file.type = type; file.md5 = hash; From 59520c31fde073f7cb0ede81162898f2bc340b4f Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 20 Jan 2022 02:43:13 +0900 Subject: [PATCH 61/81] refactor(server): use named export --- packages/backend/src/queue/processors/db/export-blocking.ts | 2 +- .../backend/src/queue/processors/db/export-custom-emojis.ts | 2 +- packages/backend/src/queue/processors/db/export-following.ts | 2 +- packages/backend/src/queue/processors/db/export-mute.ts | 2 +- packages/backend/src/queue/processors/db/export-notes.ts | 2 +- packages/backend/src/queue/processors/db/export-user-lists.ts | 2 +- .../backend/src/queue/processors/db/import-custom-emojis.ts | 2 +- .../backend/src/server/api/endpoints/drive/files/create.ts | 4 ++-- packages/backend/src/services/drive/add-file.ts | 2 +- packages/backend/src/services/drive/upload-from-url.ts | 4 ++-- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/backend/src/queue/processors/db/export-blocking.ts b/packages/backend/src/queue/processors/db/export-blocking.ts index 8c886d3b4e..af5c7eba15 100644 --- a/packages/backend/src/queue/processors/db/export-blocking.ts +++ b/packages/backend/src/queue/processors/db/export-blocking.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { getFullApAccount } from '@/misc/convert-host'; import { Users, Blockings } from '@/models/index'; diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index a420866dcf..764356385c 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -6,7 +6,7 @@ import { ulid } from 'ulid'; const mime = require('mime-types'); const archiver = require('archiver'); import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { Users, Emojis } from '@/models/index'; import { } from '@/queue/types'; diff --git a/packages/backend/src/queue/processors/db/export-following.ts b/packages/backend/src/queue/processors/db/export-following.ts index fbb9e25247..0c088dc371 100644 --- a/packages/backend/src/queue/processors/db/export-following.ts +++ b/packages/backend/src/queue/processors/db/export-following.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { getFullApAccount } from '@/misc/convert-host'; import { Users, Followings, Mutings } from '@/models/index'; diff --git a/packages/backend/src/queue/processors/db/export-mute.ts b/packages/backend/src/queue/processors/db/export-mute.ts index 0b1fd24fe0..f5928b875d 100644 --- a/packages/backend/src/queue/processors/db/export-mute.ts +++ b/packages/backend/src/queue/processors/db/export-mute.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { getFullApAccount } from '@/misc/convert-host'; import { Users, Mutings } from '@/models/index'; diff --git a/packages/backend/src/queue/processors/db/export-notes.ts b/packages/backend/src/queue/processors/db/export-notes.ts index e64e763513..df7675dec7 100644 --- a/packages/backend/src/queue/processors/db/export-notes.ts +++ b/packages/backend/src/queue/processors/db/export-notes.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { Users, Notes, Polls } from '@/models/index'; import { MoreThan } from 'typeorm'; diff --git a/packages/backend/src/queue/processors/db/export-user-lists.ts b/packages/backend/src/queue/processors/db/export-user-lists.ts index 44a8f9f671..b9b6cb0de6 100644 --- a/packages/backend/src/queue/processors/db/export-user-lists.ts +++ b/packages/backend/src/queue/processors/db/export-user-lists.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { getFullApAccount } from '@/misc/convert-host'; import { Users, UserLists, UserListJoinings } from '@/models/index'; diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts index eb386bbb42..ba4e3f0c08 100644 --- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts @@ -8,7 +8,7 @@ import { queueLogger } from '../../logger'; import { downloadUrl } from '@/misc/download-url'; import { DriveFiles, Emojis } from '@/models/index'; import { DbUserImportJobData } from '@/queue/types'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import { genId } from '@/misc/gen-id'; const logger = queueLogger.createSubLogger('import-custom-emojis'); diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index f598f18ed5..3799181540 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -1,7 +1,7 @@ import ms from 'ms'; import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; -import create from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import define from '../../../define'; import { apiLogger } from '../../../logger'; import { ApiError } from '../../../error'; @@ -79,7 +79,7 @@ export default define(meta, async (ps, user, _, file, cleanup) => { try { // Create file - const driveFile = await create(user, file.path, name, null, ps.folderId, ps.force, false, null, null, ps.isSensitive); + const driveFile = await addFile(user, file.path, name, null, ps.folderId, ps.force, false, null, null, ps.isSensitive); return await DriveFiles.pack(driveFile, { self: true }); } catch (e) { apiLogger.error(e); diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index 6ac4f6d11d..9de4465eb9 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -312,7 +312,7 @@ async function deleteOldFile(user: IRemoteUser) { * @param sensitive Mark file as sensitive * @return Created drive file */ -export default async function( +export async function addFile( user: { id: User['id']; host: User['host'] } | null, path: string, name: string | null = null, diff --git a/packages/backend/src/services/drive/upload-from-url.ts b/packages/backend/src/services/drive/upload-from-url.ts index 28f42bc344..a723c3e9ac 100644 --- a/packages/backend/src/services/drive/upload-from-url.ts +++ b/packages/backend/src/services/drive/upload-from-url.ts @@ -1,5 +1,5 @@ import { URL } from 'url'; -import create from './add-file'; +import { addFile } from './add-file'; import { User } from '@/models/entities/user'; import { driveLogger } from './logger'; import { createTemp } from '@/misc/create-temp'; @@ -41,7 +41,7 @@ export default async ( let error; try { - driveFile = await create(user, path, name, comment, folderId, force, link, url, uri, sensitive); + driveFile = await addFile(user, path, name, comment, folderId, force, link, url, uri, sensitive); logger.succ(`Got: ${driveFile.id}`); } catch (e) { error = e; From 74df9b57ecbf5c2557fe0454f1bed7c27eb6aa2f Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Thu, 20 Jan 2022 03:03:28 +0900 Subject: [PATCH 62/81] =?UTF-8?q?fix:=20proxy=E3=81=A7svg=E3=82=92png?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=8F=9B=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=20(#8106)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * revert send-drive-file change * fix * Update packages/backend/src/server/proxy/proxy-media.ts Co-authored-by: MeiMei <30769358+mei23@users.noreply.github.com> Co-authored-by: MeiMei <30769358+mei23@users.noreply.github.com> --- packages/backend/src/server/proxy/proxy-media.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/server/proxy/proxy-media.ts b/packages/backend/src/server/proxy/proxy-media.ts index aba08bb805..c234b70c55 100644 --- a/packages/backend/src/server/proxy/proxy-media.ts +++ b/packages/backend/src/server/proxy/proxy-media.ts @@ -19,15 +19,16 @@ export async function proxyMedia(ctx: Koa.Context) { const { mime, ext } = await detectType(path); - if (!mime.startsWith('image/')) throw 403; - if (!FILE_TYPE_BROWSERSAFE.includes(mime)) throw 403; - let image: IImage; - if ('static' in ctx.query && ['image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp'].includes(mime)) { + if ('static' in ctx.query && ['image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/svg+xml'].includes(mime)) { image = await convertToPng(path, 498, 280); - } else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng'].includes(mime)) { + } else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/svg+xml'].includes(mime)) { image = await convertToJpeg(path, 200, 200); + } else if (['image/svg+xml'].includes(mime)) { + image = await convertToPng(path, 2048, 2048); + } else if (!mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(mime)) { + throw new StatusError('Rejected type', 403, 'Rejected type'); } else { image = { data: fs.readFileSync(path), From 7627c43deee3ef952b072b35cd5d2dc32eae48f7 Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Thu, 20 Jan 2022 03:05:52 +0900 Subject: [PATCH 63/81] send-drive-file svg as png (#8107) --- packages/backend/src/server/file/send-drive-file.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts index 7bfc36e25a..f3c6c518fa 100644 --- a/packages/backend/src/server/file/send-drive-file.ts +++ b/packages/backend/src/server/file/send-drive-file.ts @@ -11,7 +11,7 @@ import { DriveFiles } from '@/models/index'; import { InternalStorage } from '@/services/drive/internal-storage'; import { downloadUrl } from '@/misc/download-url'; import { detectType } from '@/misc/get-file-info'; -import { convertToJpeg, convertToPngOrJpeg } from '@/services/drive/image-processor'; +import { convertToJpeg, convertToPng, convertToPngOrJpeg } from '@/services/drive/image-processor'; import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail'; import { StatusError } from '@/misc/fetch'; import { FILE_TYPE_BROWSERSAFE } from '@/const'; @@ -67,13 +67,19 @@ export default async function(ctx: Koa.Context) { if (isThumbnail) { if (['image/jpeg', 'image/webp'].includes(mime)) { return await convertToJpeg(path, 498, 280); - } else if (['image/png'].includes(mime)) { + } else if (['image/png', 'image/svg+xml'].includes(mime)) { return await convertToPngOrJpeg(path, 498, 280); } else if (mime.startsWith('video/')) { return await GenerateVideoThumbnail(path); } } + if (isWebpublic) { + if (['image/svg+xml'].includes(mime)) { + return await convertToPng(path, 2048, 2048); + } + } + return { data: fs.readFileSync(path), ext, From 1bd7c55c88133c4a3e7f5f6cfa424c47795347c2 Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Thu, 20 Jan 2022 23:15:08 +0900 Subject: [PATCH 64/81] post-form.vue (#8164) --- packages/client/src/components/post-form.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue index 04a93d7388..60b062f054 100644 --- a/packages/client/src/components/post-form.vue +++ b/packages/client/src/components/post-form.vue @@ -350,8 +350,8 @@ function detachFile(id) { files = files.filter(x => x.id != id); } -function updateFiles(files) { - files = files; +function updateFiles(_files) { + files = _files; } function updateFileSensitive(file, sensitive) { From 7a999f2289bdf9d20c61a18863e6aa0fcb4dbb3f Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 21 Jan 2022 02:56:08 +0900 Subject: [PATCH 65/81] feat(server): add more metadata for emoji export --- .../backend/src/queue/processors/db/export-custom-emojis.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index 764356385c..92344430d6 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -11,6 +11,7 @@ import * as dateFormat from 'dateformat'; import { Users, Emojis } from '@/models/index'; import { } from '@/queue/types'; import { downloadUrl } from '@/misc/download-url'; +import config from '@/config/index'; const logger = queueLogger.createSubLogger('export-custom-emojis'); @@ -52,7 +53,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi }); }; - await writeMeta(`{"metaVersion":2,"emojis":[`); + await writeMeta(`{"metaVersion":2,"host":"${config.host}","exportedAt":"${new Date().toString()}","emojis":[`); const customEmojis = await Emojis.find({ where: { From e2d2a4e2e45db02a0037b774872232093ca5da74 Mon Sep 17 00:00:00 2001 From: Johann150 <johann.galle@protonmail.com> Date: Thu, 20 Jan 2022 19:01:45 +0100 Subject: [PATCH 66/81] fix: code url in documentation (#8117) It seems this was not changed while refactoring the modules apart. --- packages/backend/src/server/api/openapi/gen-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index 1c521f212f..1efef8d26d 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -118,7 +118,7 @@ export function genOpenapiSpec(lang = 'ja-JP') { description: desc, externalDocs: { description: 'Source code', - url: `https://github.com/misskey-dev/misskey/blob/develop/src/server/api/endpoints/${endpoint.name}.ts`, + url: `https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`, }, ...(endpoint.meta.tags ? { tags: [endpoint.meta.tags[0]], From cbb7e95d82d363d96462b90943bf329469ad08df Mon Sep 17 00:00:00 2001 From: Johann150 <johann.galle@protonmail.com> Date: Thu, 20 Jan 2022 19:06:38 +0100 Subject: [PATCH 67/81] enhance: Forward report (#8001) * implement sending AP Flag object Optionally allow a user to select to forward a report about a remote user to the other instance. This is added in a backwards-compatible way. * add locale string * forward report only for moderators * add switch to moderator UI to forward report * fix report note url * return forwarded status from API apparently forgot to carry this over from my testing environment * object in Flag activity has to be an array For correct interoperability with Pleroma the "object" property of the Flag activity has to be an array. This array will in the future also hold the link to respective notes, so it makes sense to correct this on our side. * Update get-note-menu.ts Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> --- locales/ja-JP.yml | 3 + .../1637320813000-forwarded-report.js | 13 +++ .../src/models/entities/abuse-user-report.ts | 5 + .../models/repositories/abuse-user-report.ts | 1 + .../src/remote/activitypub/renderer/flag.ts | 15 +++ .../api/endpoints/admin/abuse-user-reports.ts | 5 + .../admin/resolve-abuse-user-report.ts | 20 +++- .../client/src/components/abuse-report.vue | 102 ++++++++++++++++++ packages/client/src/pages/admin/abuses.vue | 62 +---------- packages/client/src/scripts/get-note-menu.ts | 2 +- 10 files changed, 169 insertions(+), 59 deletions(-) create mode 100644 packages/backend/migration/1637320813000-forwarded-report.js create mode 100644 packages/backend/src/remote/activitypub/renderer/flag.ts create mode 100644 packages/client/src/components/abuse-report.vue diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index dd853aa83a..59ce5ee84f 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -619,8 +619,11 @@ reportAbuse: "通報" reportAbuseOf: "{name}を通報する" fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。" abuseReported: "内容が送信されました。ご報告ありがとうございました。" +reporter: "通報者" reporteeOrigin: "通報先" reporterOrigin: "通報元" +forwardReport: "リモートインスタンスに通報を転送する" +forwardReportIsAnonymous: "リモートインスタンスからはあなたの情報は見れず、匿名のシステムアカウントとして表示されます。" send: "送信" abuseMarkAsResolved: "対応済みにする" openInNewTab: "新しいタブで開く" diff --git a/packages/backend/migration/1637320813000-forwarded-report.js b/packages/backend/migration/1637320813000-forwarded-report.js new file mode 100644 index 0000000000..4056f7b5f4 --- /dev/null +++ b/packages/backend/migration/1637320813000-forwarded-report.js @@ -0,0 +1,13 @@ +const { QueryRunner } = require('typeorm'); + +module.exports = class forwardedReport1637320813000 { + name = 'forwardedReport1637320813000'; + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "forwarded" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "forwarded"`); + } +}; diff --git a/packages/backend/src/models/entities/abuse-user-report.ts b/packages/backend/src/models/entities/abuse-user-report.ts index 019d613f76..27c1e47fd8 100644 --- a/packages/backend/src/models/entities/abuse-user-report.ts +++ b/packages/backend/src/models/entities/abuse-user-report.ts @@ -51,6 +51,11 @@ export class AbuseUserReport { }) public resolved: boolean; + @Column('boolean', { + default: false + }) + public forwarded: boolean; + @Column('varchar', { length: 2048, }) diff --git a/packages/backend/src/models/repositories/abuse-user-report.ts b/packages/backend/src/models/repositories/abuse-user-report.ts index 5e267b3c2b..943b65eb64 100644 --- a/packages/backend/src/models/repositories/abuse-user-report.ts +++ b/packages/backend/src/models/repositories/abuse-user-report.ts @@ -27,6 +27,7 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> { assignee: report.assigneeId ? Users.pack(report.assignee || report.assigneeId, null, { detail: true, }) : null, + forwarded: report.forwarded, }); } diff --git a/packages/backend/src/remote/activitypub/renderer/flag.ts b/packages/backend/src/remote/activitypub/renderer/flag.ts new file mode 100644 index 0000000000..60ac496509 --- /dev/null +++ b/packages/backend/src/remote/activitypub/renderer/flag.ts @@ -0,0 +1,15 @@ +import config from '@/config/index'; +import { IObject, IActivity } from '@/remote/activitypub/type'; +import { ILocalUser, IRemoteUser } from '@/models/entities/user'; +import { getInstanceActor } from '@/services/instance-actor'; + +// to anonymise reporters, the reporting actor must be a system user +// object has to be a uri or array of uris +export const renderFlag = (user: ILocalUser, object: [string], content: string): IActivity => { + return { + type: 'Flag', + actor: `${config.url}/users/${user.id}`, + content, + object, + }; +}; diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 807afd2690..ed7b146d03 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -46,6 +46,11 @@ export const meta = { ]), default: 'combined', }, + + forwarded: { + validator: $.optional.bool, + default: false, + }, }, res: { diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index 3b47d7d35e..b00457f092 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -1,7 +1,11 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import define from '../../define'; -import { AbuseUserReports } from '@/models/index'; +import { AbuseUserReports, Users } from '@/models/index'; +import { getInstanceActor } from '@/services/instance-actor'; +import { deliver } from '@/queue/index'; +import { renderActivity } from '@/remote/activitypub/renderer/index'; +import { renderFlag } from '@/remote/activitypub/renderer/flag'; export const meta = { tags: ['admin'], @@ -13,6 +17,12 @@ export const meta = { reportId: { validator: $.type(ID), }, + + forward: { + validator: $.optional.boolean, + required: false, + default: false, + }, }, } as const; @@ -24,8 +34,16 @@ export default define(meta, async (ps, me) => { throw new Error('report not found'); } + if (ps.forward && report.targetUserHost != null) { + const actor = await getInstanceActor(); + const targetUser = await Users.findOne(report.targetUserId); + + deliver(actor, renderActivity(renderFlag(actor, [targetUser.uri], report.comment)), targetUser.inbox); + } + await AbuseUserReports.update(report.id, { resolved: true, assigneeId: me.id, + forwarded: ps.forward && report.targetUserHost != null, }); }); diff --git a/packages/client/src/components/abuse-report.vue b/packages/client/src/components/abuse-report.vue new file mode 100644 index 0000000000..b67cef209b --- /dev/null +++ b/packages/client/src/components/abuse-report.vue @@ -0,0 +1,102 @@ +<template> +<div class="bcekxzvu _card _gap"> + <div class="_content target"> + <MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/> + <MkA class="info" :to="userPage(report.targetUser)" v-user-preview="report.targetUserId"> + <MkUserName class="name" :user="report.targetUser"/> + <MkAcct class="acct" :user="report.targetUser" style="display: block;"/> + </MkA> + </div> + <div class="_content"> + <div> + <Mfm :text="report.comment"/> + </div> + <hr/> + <div>{{ $ts.reporter }}: <MkAcct :user="report.reporter"/></div> + <div v-if="report.assignee"> + {{ $ts.moderator }}: + <MkAcct :user="report.assignee"/> + </div> + <div><MkTime :time="report.createdAt"/></div> + </div> + <div class="_footer"> + <MkSwitch v-model="forward" :disabled="report.targetUser.host == null || report.resolved"> + {{ $ts.forwardReport }} + <template #caption>{{ $ts.forwardReportIsAnonymous }}</template> + </MkSwitch> + <MkButton v-if="!report.resolved" primary @click="resolve">{{ $ts.abuseMarkAsResolved }}</MkButton> + </div> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; + +import MkButton from '@/components/ui/button.vue'; +import MkSwitch from '@/components/form/switch.vue'; +import { acct, userPage } from '@/filters/user'; +import * as os from '@/os'; + +export default defineComponent({ + components: { + MkButton, + MkSwitch, + }, + + emits: ['resolved'], + + props: { + report: { + type: Object, + required: true, + } + } + + data() { + return { + forward: this.report.forwarded, + }; + } + + methods: { + acct, + userPage, + + resolve() { + os.apiWithDialog('admin/resolve-abuse-user-report', { + forward: this.forward, + reportId: this.report.id, + }).then(() => { + this.$emit('resolved', this.report.id); + }); + } + } +}); +</script> + +<style lang="scss" scoped> +.bcekxzvu { + > .target { + display: flex; + width: 100%; + box-sizing: border-box; + text-align: left; + align-items: center; + + > .avatar { + width: 42px; + height: 42px; + } + + > .info { + margin-left: 0.3em; + padding: 0 8px; + flex: 1; + + > .name { + font-weight: bold; + } + } + } +} +</style> diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue index 31cdef492a..92f93797ce 100644 --- a/packages/client/src/pages/admin/abuses.vue +++ b/packages/client/src/pages/admin/abuses.vue @@ -34,27 +34,7 @@ --> <MkPagination v-slot="{items}" ref="reports" :pagination="pagination" style="margin-top: var(--margin);"> - <div v-for="report in items" :key="report.id" class="bcekxzvu _card _gap"> - <div class="_content target"> - <MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/> - <div class="info"> - <MkUserName class="name" :user="report.targetUser"/> - <div class="acct">@{{ acct(report.targetUser) }}</div> - </div> - </div> - <div class="_content"> - <div> - <Mfm :text="report.comment"/> - </div> - <hr> - <div>Reporter: <MkAcct :user="report.reporter"/></div> - <div><MkTime :time="report.createdAt"/></div> - </div> - <div class="_footer"> - <div v-if="report.assignee">Assignee: <MkAcct :user="report.assignee"/></div> - <MkButton v-if="!report.resolved" primary @click="resolve(report)">{{ $ts.abuseMarkAsResolved }}</MkButton> - </div> - </div> + <XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/> </MkPagination> </div> </div> @@ -64,20 +44,19 @@ <script lang="ts"> import { computed, defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; import MkPagination from '@/components/ui/pagination.vue'; -import { acct } from '@/filters/user'; +import XAbuseReport from '@/components/abuse-report.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - MkButton, MkInput, MkSelect, MkPagination, + XAbuseReport, }, emits: ['info'], @@ -107,14 +86,8 @@ export default defineComponent({ }, methods: { - acct, - - resolve(report) { - os.apiWithDialog('admin/resolve-abuse-user-report', { - reportId: report.id, - }).then(() => { - this.$refs.reports.removeItem(item => item.id === report.id); - }); + resolved(reportId) { + this.$refs.reports.removeItem(item => item.id === reportId); }, } }); @@ -124,29 +97,4 @@ export default defineComponent({ .lcixvhis { margin: var(--margin); } - -.bcekxzvu { - > .target { - display: flex; - width: 100%; - box-sizing: border-box; - text-align: left; - align-items: center; - - > .avatar { - width: 42px; - height: 42px; - } - - > .info { - margin-left: 0.3em; - padding: 0 8px; - flex: 1; - - > .name { - font-weight: bold; - } - } - } -} </style> diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts index 61120d53ba..3634f39632 100644 --- a/packages/client/src/scripts/get-note-menu.ts +++ b/packages/client/src/scripts/get-note-menu.ts @@ -252,7 +252,7 @@ export function getNoteMenu(props: { icon: 'fas fa-exclamation-circle', text: i18n.locale.reportAbuse, action: () => { - const u = `${url}/notes/${appearNote.id}`; + const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`; os.popup(import('@/components/abuse-report-window.vue'), { user: appearNote.user, initialComment: `Note: ${u}\n-----\n` From e1d69e236fba90279f2931502eeaecbae8d23703 Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Fri, 21 Jan 2022 16:43:56 +0900 Subject: [PATCH 68/81] =?UTF-8?q?enhance:=20e2e=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=92=E3=81=A7=E3=81=8D=E3=82=8B=E3=81=A0=E3=81=91=E6=94=B9?= =?UTF-8?q?=E8=89=AF=E3=81=97=E3=81=A6=E3=81=BF=E3=81=9F=20(#8159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update docker image? * 続 * serial run delete from "${table}" cascade * use cypress official github action * refuse install by cypress action * clean up * use wait? * use more wait? * Revert "use more wait?" This reverts commit 18d0fcae9c7d8f98a4cafb4a846a031ece57350c. * Revert "use wait?" This reverts commit 5aa8feec9cdc3e2f79e566249f0a0eff6c0df6a0. * fix * test * test * log? * 握りつぶしてみる * clean up * env? * clean up? * disable video * add comment * remove test * 成功? * test browser * nodeインストール無効化 * node16.13.0-chrome95-ff94 * node.js復活 * ? * ちょっと戻してみる * chrome? * cross browser test2 * --shm-size=2g * artifact? * misskey.local? * firefoxはあきらめる * not headless? * oops * fix * ?? * test1 * if? * fail-fast: false * headless: false * easy error ignoreing describe * エラーの解消 とちょっとリファクター * add browser name to artifact * Install mplayer for FireFox * no wait? * タイムアウトを甘くしてみる * firefoxをあきらめる(n回目) * remove timeout setting * wait復活 * Update basic.js * Update index.js Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> --- .github/workflows/test.yml | 39 ++++++++++++--- cypress/integration/basic.js | 44 +++++++---------- cypress/support/index.js | 12 +++-- packages/backend/src/db/postgre.ts | 4 +- packages/backend/test/docker-compose.yml | 4 +- packages/client/src/components/notes.vue | 4 +- packages/client/src/components/timeline.vue | 12 ++--- .../client/src/components/ui/pagination.vue | 47 +++++++++++-------- packages/client/src/pages/tag.vue | 2 +- .../client/src/pages/user/index.timeline.vue | 2 +- 10 files changed, 99 insertions(+), 71 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6a684eaa48..3e9585f96d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,14 +17,14 @@ jobs: services: postgres: - image: postgres:12.2-alpine + image: postgres:13 ports: - 54312:5432 env: POSTGRES_DB: test-misskey POSTGRES_HOST_AUTH_METHOD: trust redis: - image: redis:4.0-alpine + image: redis:6 ports: - 56312:6379 @@ -51,19 +51,21 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: node-version: [16.x] + browser: [chrome] services: postgres: - image: postgres:12.2-alpine + image: postgres:13 ports: - 54312:5432 env: POSTGRES_DB: test-misskey POSTGRES_HOST_AUTH_METHOD: trust redis: - image: redis:4.0-alpine + image: redis:6 ports: - 56312:6379 @@ -71,6 +73,12 @@ jobs: - uses: actions/checkout@v2 with: submodules: true + # https://github.com/cypress-io/cypress-docker-images/issues/150 + #- name: Install mplayer for FireFox + # run: sudo apt install mplayer -y + # if: ${{ matrix.browser == 'firefox' }} + #- uses: browser-actions/setup-firefox@latest + # if: ${{ matrix.browser == 'firefox' }} - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: @@ -87,5 +95,24 @@ jobs: run: cp .github/misskey/test.yml .config - name: Build run: yarn build - - name: Test - run: yarn e2e + # https://github.com/cypress-io/cypress/issues/4351#issuecomment-559489091 + - name: ALSA Env + run: echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc + - name: Cypress run + uses: cypress-io/github-action@v2 + with: + install: false + start: npm run start:test + wait-on: 'http://localhost:61812' + headless: false + browser: ${{ matrix.browser }} + - uses: actions/upload-artifact@v2 + if: failure() + with: + name: ${{ matrix.browser }}-cypress-screenshots + path: cypress/screenshots + - uses: actions/upload-artifact@v2 + if: always() + with: + name: ${{ matrix.browser }}-cypress-videos + path: cypress/videos diff --git a/cypress/integration/basic.js b/cypress/integration/basic.js index a754f41b98..aca44ef15d 100644 --- a/cypress/integration/basic.js +++ b/cypress/integration/basic.js @@ -41,8 +41,6 @@ describe('After setup instance', () => { username: 'admin', password: 'pass', }).its('body').as('admin'); - - cy.get('@admin'); }); afterEach(() => { @@ -82,15 +80,11 @@ describe('After user signup', () => { password: 'pass', }).its('body').as('admin'); - cy.get('@admin').then(() => { - // ユーザー作成 - cy.request('POST', '/api/signup', { - username: 'alice', - password: 'alice1234', - }).its('body').as('alice'); - }); - - cy.get('@alice'); + // ユーザー作成 + cy.request('POST', '/api/signup', { + username: 'alice', + password: 'alice1234', + }).its('body').as('alice'); }); afterEach(() => { @@ -145,27 +139,21 @@ describe('After user singed in', () => { password: 'pass', }).its('body').as('admin'); - cy.get('@admin').then(() => { - // ユーザー作成 - cy.request('POST', '/api/signup', { - username: 'alice', - password: 'alice1234', - }).its('body').as('alice'); - }); + // ユーザー作成 + cy.request('POST', '/api/signup', { + username: 'alice', + password: 'alice1234', + }).its('body').as('alice'); - cy.get('@alice').then(() => { - cy.visit('/'); + cy.visit('/'); - cy.intercept('POST', '/api/signin').as('signin'); + cy.intercept('POST', '/api/signin').as('signin'); - cy.get('[data-cy-signin]').click(); - cy.get('[data-cy-signin-username] input').type('alice'); - cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); + cy.get('[data-cy-signin]').click(); + cy.get('[data-cy-signin-username] input').type('alice'); + cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); - cy.wait('@signin').as('signedIn'); - }); - - cy.get('@signedIn'); + cy.wait('@signin').as('signedIn'); }); afterEach(() => { diff --git a/cypress/support/index.js b/cypress/support/index.js index a9ac34476d..9185be344c 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -20,7 +20,13 @@ import './commands' // require('./commands') Cypress.on('uncaught:exception', (err, runnable) => { - if (err.message.includes('ResizeObserver loop limit exceeded')) { - return false - } + if ([ + // Chrome + 'ResizeObserver loop limit exceeded', + + // Firefox + 'ResizeObserver loop completed with undelivered notifications', + ].some(msg => err.message.includes(msg))) { + return false; + } }); diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index 1692b26219..69336c2a46 100644 --- a/packages/backend/src/db/postgre.ts +++ b/packages/backend/src/db/postgre.ts @@ -220,7 +220,9 @@ export async function resetDb() { WHERE nspname NOT IN ('pg_catalog', 'information_schema') AND C.relkind = 'r' AND nspname !~ '^pg_toast';`); - await Promise.all(tables.map(t => t.table).map(x => conn.query(`DELETE FROM "${x}" CASCADE`))); + for (const table of tables) { + await conn.query(`DELETE FROM "${table.table}" CASCADE`); + } }; for (let i = 1; i <= 3; i++) { diff --git a/packages/backend/test/docker-compose.yml b/packages/backend/test/docker-compose.yml index c045e7c6c4..5f95bec4c0 100644 --- a/packages/backend/test/docker-compose.yml +++ b/packages/backend/test/docker-compose.yml @@ -2,12 +2,12 @@ version: "3" services: redistest: - image: redis:4.0-alpine + image: redis:6 ports: - "127.0.0.1:56312:6379" dbtest: - image: postgres:12.2-alpine + image: postgres:13 ports: - "127.0.0.1:54312:5432" environment: diff --git a/packages/client/src/components/notes.vue b/packages/client/src/components/notes.vue index aec478ac95..41bec5a579 100644 --- a/packages/client/src/components/notes.vue +++ b/packages/client/src/components/notes.vue @@ -32,9 +32,7 @@ const props = defineProps<{ const pagingComponent = ref<InstanceType<typeof MkPagination>>(); defineExpose({ - prepend: (note) => { - pagingComponent.value?.prepend(note); - }, + pagingComponent, }); </script> diff --git a/packages/client/src/components/timeline.vue b/packages/client/src/components/timeline.vue index a7af02c30b..59956b9526 100644 --- a/packages/client/src/components/timeline.vue +++ b/packages/client/src/components/timeline.vue @@ -25,10 +25,10 @@ const emit = defineEmits<{ provide('inChannel', computed(() => props.src === 'channel')); -const tlComponent = ref<InstanceType<typeof XNotes>>(); +const tlComponent: InstanceType<typeof XNotes> = $ref(); const prepend = note => { - tlComponent.value.prepend(note); + tlComponent.pagingComponent?.prepend(note); emit('note'); @@ -38,16 +38,16 @@ const prepend = note => { }; const onUserAdded = () => { - tlComponent.value.reload(); + tlComponent.pagingComponent?.reload(); }; const onUserRemoved = () => { - tlComponent.value.reload(); + tlComponent.pagingComponent?.reload(); }; const onChangeFollowing = () => { - if (!tlComponent.value.backed) { - tlComponent.value.reload(); + if (!tlComponent.pagingComponent?.backed) { + tlComponent.pagingComponent?.reload(); } }; diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue index 571ef71eab..9c18fc5ce5 100644 --- a/packages/client/src/components/ui/pagination.vue +++ b/packages/client/src/components/ui/pagination.vue @@ -73,12 +73,11 @@ const queue = ref<Item[]>([]); const offset = ref(0); const fetching = ref(true); const moreFetching = ref(false); -const inited = ref(false); const more = ref(false); const backed = ref(false); // 遡り中か否か const isBackTop = ref(false); -const empty = computed(() => items.value.length === 0 && !fetching.value && inited.value); -const error = computed(() => !fetching.value && !inited.value); +const empty = computed(() => items.value.length === 0); +const error = ref(false); const init = async (): Promise<void> => { queue.value = []; @@ -105,9 +104,10 @@ const init = async (): Promise<void> => { more.value = false; } offset.value = res.length; - inited.value = true; + error.value = false; fetching.value = false; }, e => { + error.value = true; fetching.value = false; }); }; @@ -183,30 +183,36 @@ const fetchMoreAhead = async (): Promise<void> => { }; const prepend = (item: Item): void => { - if (rootEl.value == null) return; - if (props.pagination.reversed) { - const container = getScrollContainer(rootEl.value); - if (container == null) return; // TODO? + if (rootEl.value) { + const container = getScrollContainer(rootEl.value); + if (container == null) return; // TODO? - const pos = getScrollPosition(rootEl.value); - const viewHeight = container.clientHeight; - const height = container.scrollHeight; - const isBottom = (pos + viewHeight > height - 32); - if (isBottom) { - // オーバーフローしたら古いアイテムは捨てる - if (items.value.length >= props.displayLimit) { - // このやり方だとVue 3.2以降アニメーションが動かなくなる - //items.value = items.value.slice(-props.displayLimit); - while (items.value.length >= props.displayLimit) { - items.value.shift(); + const pos = getScrollPosition(rootEl.value); + const viewHeight = container.clientHeight; + const height = container.scrollHeight; + const isBottom = (pos + viewHeight > height - 32); + if (isBottom) { + // オーバーフローしたら古いアイテムは捨てる + if (items.value.length >= props.displayLimit) { + // このやり方だとVue 3.2以降アニメーションが動かなくなる + //items.value = items.value.slice(-props.displayLimit); + while (items.value.length >= props.displayLimit) { + items.value.shift(); + } + more.value = true; } - more.value = true; } } items.value.push(item); // TODO } else { + // 初回表示時はunshiftだけでOK + if (!rootEl.value) { + items.value.unshift(item); + return; + } + const isTop = isBackTop.value || (document.body.contains(rootEl.value) && isTopVisible(rootEl.value)); if (isTop) { @@ -264,6 +270,7 @@ onDeactivated(() => { defineExpose({ items, + backed, reload, fetchMoreAhead, prepend, diff --git a/packages/client/src/pages/tag.vue b/packages/client/src/pages/tag.vue index 8d8dc0a65c..045f1ef259 100644 --- a/packages/client/src/pages/tag.vue +++ b/packages/client/src/pages/tag.vue @@ -1,6 +1,6 @@ <template> <div class="_section"> - <XNotes ref="notes" class="_content" :pagination="pagination"/> + <XNotes class="_content" :pagination="pagination"/> </div> </template> diff --git a/packages/client/src/pages/user/index.timeline.vue b/packages/client/src/pages/user/index.timeline.vue index 7396a76efe..a1329a7411 100644 --- a/packages/client/src/pages/user/index.timeline.vue +++ b/packages/client/src/pages/user/index.timeline.vue @@ -5,7 +5,7 @@ <option value="replies">{{ $ts.notesAndReplies }}</option> <option value="files">{{ $ts.withFiles }}</option> </MkTab> - <XNotes ref="timeline" :no-gap="true" :pagination="pagination"/> + <XNotes :no-gap="true" :pagination="pagination"/> </div> </template> From ecca0d69a4afde77d3858d3610f48a9120fe6aed Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 21 Jan 2022 17:04:48 +0900 Subject: [PATCH 69/81] update deps --- package.json | 10 +- packages/backend/package.json | 100 +- packages/backend/yarn.lock | 2020 +++++++++++---------------------- packages/client/package.json | 48 +- packages/client/yarn.lock | 765 +++++++------ yarn.lock | 253 ++--- 6 files changed, 1242 insertions(+), 1954 deletions(-) diff --git a/package.json b/package.json index c42b71582b..5a4ea315ba 100644 --- a/package.json +++ b/package.json @@ -42,12 +42,12 @@ "js-yaml": "4.1.0" }, "devDependencies": { - "@redocly/openapi-core": "1.0.0-beta.54", - "@types/fluent-ffmpeg": "2.1.17", - "@typescript-eslint/parser": "5.4.0", + "@redocly/openapi-core": "1.0.0-beta.79", + "@types/fluent-ffmpeg": "2.1.20", + "@typescript-eslint/parser": "5.10.0", "cross-env": "7.0.3", - "cypress": "9.1.0", + "cypress": "9.3.1", "start-server-and-test": "1.14.0", - "typescript": "4.5.2" + "typescript": "4.5.5" } } diff --git a/packages/backend/package.json b/packages/backend/package.json index c940e98301..3d3a901f34 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -22,85 +22,78 @@ "@sinonjs/fake-timers": "7.1.2", "@syuilo/aiscript": "0.11.1", "@types/bcryptjs": "2.4.2", - "@types/bull": "3.15.5", + "@types/bull": "3.15.7", "@types/cbor": "6.0.0", "@types/dateformat": "3.0.1", - "@types/escape-regexp": "0.0.0", + "@types/escape-regexp": "0.0.1", "@types/glob": "7.2.0", "@types/is-url": "1.2.30", - "@types/js-yaml": "4.0.4", - "@types/jsdom": "16.2.13", + "@types/js-yaml": "4.0.5", + "@types/jsdom": "16.2.14", "@types/jsonld": "1.5.6", "@types/koa": "2.13.4", - "@types/koa-bodyparser": "4.3.3", + "@types/koa-bodyparser": "4.3.5", "@types/koa-cors": "0.0.2", "@types/koa-favicon": "2.0.21", "@types/koa-logger": "3.1.2", "@types/koa-mount": "4.0.1", "@types/koa-send": "4.1.3", "@types/koa-views": "7.0.0", - "@types/koa__cors": "3.0.3", + "@types/koa__cors": "3.1.1", "@types/koa__multer": "2.0.4", - "@types/koa__router": "8.0.8", + "@types/koa__router": "8.0.11", "@types/mocha": "8.2.3", - "@types/node": "16.11.7", - "@types/node-fetch": "2.5.12", + "@types/node": "17.0.10", + "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.4", "@types/oauth": "0.9.1", "@types/parse5": "6.0.3", "@types/portscanner": "2.1.1", - "@types/pug": "2.0.5", + "@types/pug": "2.0.6", "@types/punycode": "2.1.0", - "@types/qrcode": "1.4.1", + "@types/qrcode": "1.4.2", "@types/random-seed": "0.3.3", - "@types/ratelimiter": "3.4.2", - "@types/redis": "2.8.32", + "@types/ratelimiter": "3.4.3", + "@types/redis": "4.0.11", "@types/rename": "1.0.4", "@types/request-stats": "3.0.0", - "@types/sanitize-html": "2.5.0", + "@types/sanitize-html": "2.6.2", "@types/seedrandom": "2.4.28", - "@types/sharp": "0.29.3", + "@types/sharp": "0.29.5", "@types/sinonjs__fake-timers": "6.0.4", - "@types/speakeasy": "2.0.6", + "@types/speakeasy": "2.0.7", "@types/throttle-debounce": "2.1.0", "@types/tinycolor2": "1.4.3", - "@types/tmp": "0.2.2", - "@types/uuid": "8.3.1", + "@types/tmp": "0.2.3", + "@types/uuid": "8.3.4", "@types/web-push": "3.3.2", "@types/webpack": "5.28.0", "@types/webpack-stream": "3.2.12", "@types/websocket": "1.0.4", - "@types/ws": "8.2.0", - "@typescript-eslint/eslint-plugin": "5.3.1", - "@typescript-eslint/parser": "5.1.0", + "@types/ws": "8.2.2", + "@typescript-eslint/eslint-plugin": "5.10.0", + "@typescript-eslint/parser": "5.10.0", "abort-controller": "3.0.0", "archiver": "5.3.0", "autobind-decorator": "2.4.0", "autwh": "0.1.0", - "aws-sdk": "2.1013.0", + "aws-sdk": "2.1061.0", "bcryptjs": "2.4.3", "blurhash": "1.1.4", - "broadcast-channel": "4.5.0", - "bull": "4.1.0", + "broadcast-channel": "4.9.0", + "bull": "4.2.1", "cacheable-lookup": "6.0.4", "cafy": "15.2.1", "cbor": "8.1.0", "chalk": "4.1.2", - "chart.js": "3.6.0", - "chartjs-adapter-date-fns": "2.0.0", - "chartjs-plugin-zoom": "1.1.1", "cli-highlight": "2.1.11", - "content-disposition": "0.5.3", + "content-disposition": "0.5.4", "crc-32": "1.2.0", - "css-loader": "6.5.1", - "cssnano": "5.0.10", - "date-fns": "2.25.0", "dateformat": "4.5.1", - "deep-email-validator": "0.1.18", + "deep-email-validator": "0.1.21", "escape-regexp": "0.0.1", - "eslint": "8.2.0", - "eslint-plugin-import": "2.25.3", - "eslint-plugin-vue": "8.0.3", + "eslint": "8.7.0", + "eslint-plugin-import": "2.25.4", "eventemitter3": "4.0.7", "feed": "4.2.2", "file-type": "16.5.3", @@ -108,9 +101,9 @@ "glob": "7.2.0", "got": "11.8.2", "hpagent": "0.1.2", - "http-signature": "1.3.5", + "http-signature": "1.3.6", "ip-cidr": "3.0.4", - "is-svg": "4.3.1", + "is-svg": "4.3.2", "js-yaml": "4.1.0", "jsdom": "16.7.0", "json5": "2.2.0", @@ -127,30 +120,29 @@ "koa-slow": "2.1.0", "koa-views": "7.0.2", "langmap": "0.0.16", - "mfm-js": "0.20.0", + "mfm-js": "0.21.0", "mime-types": "2.1.34", - "misskey-js": "0.0.12", + "misskey-js": "0.0.13", "mocha": "8.4.0", "ms": "3.0.0-canary.1", - "multer": "1.4.3", + "multer": "1.4.4", "nested-property": "4.0.0", "node-fetch": "2.6.1", - "nodemailer": "6.7.0", + "nodemailer": "6.7.2", "os-utils": "0.0.14", "parse5": "6.0.1", "pg": "8.7.1", "portscanner": "2.2.0", - "prismjs": "1.25.0", "private-ip": "2.3.3", - "probe-image-size": "7.2.1", + "probe-image-size": "7.2.2", "promise-limit": "2.7.0", "pug": "3.0.2", "punycode": "2.1.1", - "pureimage": "0.3.5", - "qrcode": "1.4.4", + "pureimage": "0.3.8", + "qrcode": "1.5.0", "random-seed": "0.3.0", "ratelimiter": "3.4.1", - "re2": "1.16.0", + "re2": "1.17.3", "redis": "3.1.2", "redis-lock": "0.1.4", "reflect-metadata": "0.1.13", @@ -159,9 +151,9 @@ "require-all": "3.0.0", "rndstr": "1.0.0", "s-age": "1.1.2", - "sanitize-html": "2.5.3", + "sanitize-html": "2.6.1", "seedrandom": "3.0.5", - "sharp": "0.29.2", + "sharp": "0.29.3", "speakeasy": "2.0.0", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", @@ -175,21 +167,21 @@ "ts-loader": "9.2.6", "ts-node": "10.4.0", "tsc-alias": "1.4.1", - "tsconfig-paths": "3.11.0", + "tsconfig-paths": "3.12.0", "twemoji-parser": "13.1.0", - "typeorm": "0.2.39", - "typescript": "4.4.4", + "typeorm": "0.2.41", + "typescript": "4.5.5", "ulid": "2.3.0", "unzipper": "0.10.11", "uuid": "8.3.2", "web-push": "3.4.5", "websocket": "1.0.34", - "ws": "8.2.3", + "ws": "8.4.2", "xev": "2.0.1" }, "devDependencies": { - "@redocly/openapi-core": "1.0.0-beta.54", - "@types/fluent-ffmpeg": "2.1.17", + "@redocly/openapi-core": "1.0.0-beta.79", + "@types/fluent-ffmpeg": "2.1.20", "cross-env": "7.0.3", "execa": "6.0.0" } diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index 9e21fb29e3..99e2e2306e 100644 --- a/packages/backend/yarn.lock +++ b/packages/backend/yarn.lock @@ -82,14 +82,14 @@ pump "^3.0.0" secure-json-parse "^2.1.0" -"@eslint/eslintrc@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.4.tgz#dfe0ff7ba270848d10c5add0715e04964c034b31" - integrity sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q== +"@eslint/eslintrc@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" + integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.0.0" + espree "^9.2.0" globals "^13.9.0" ignore "^4.0.6" import-fresh "^3.2.1" @@ -97,19 +97,24 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.6.0.tgz#b5621fdb3b32309d2d16575456cbc277fa8f021a" - integrity sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A== +"@gar/promisify@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" + integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw== + +"@humanwhocodes/config-array@^0.9.2": + version "0.9.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.2.tgz#68be55c737023009dfc5fe245d51181bb6476914" + integrity sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA== dependencies: - "@humanwhocodes/object-schema" "^1.2.0" + "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" minimatch "^3.0.4" -"@humanwhocodes/object-schema@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" - integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@koa/cors@3.1.0": version "3.1.0" @@ -134,6 +139,36 @@ methods "^1.1.2" path-to-regexp "^6.1.0" +"@node-redis/bloom@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@node-redis/bloom/-/bloom-1.0.1.tgz#144474a0b7dc4a4b91badea2cfa9538ce0a1854e" + integrity sha512-mXEBvEIgF4tUzdIN89LiYsbi6//EdpFA7L8M+DHCvePXg+bfHWi+ct5VI6nHUFQE5+ohm/9wmgihCH3HSkeKsw== + +"@node-redis/client@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@node-redis/client/-/client-1.0.2.tgz#7f09fb739675728fbc6e73536f7cd1be99bf7b8f" + integrity sha512-C+gkx68pmTnxfV+y4pzasvCH3s4UGHNOAUNhdJxGI27aMdnXNDZct7ffDHBL7bAZSGv9FSwCP5PeYvEIEKGbiA== + dependencies: + cluster-key-slot "1.1.0" + generic-pool "3.8.2" + redis-parser "3.0.0" + yallist "4.0.0" + +"@node-redis/json@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@node-redis/json/-/json-1.0.2.tgz#8ad2d0f026698dc1a4238cc3d1eb099a3bee5ab8" + integrity sha512-qVRgn8WfG46QQ08CghSbY4VhHFgaTY71WjpwRBGEuqGPfWwfRcIf3OqSpR7Q/45X+v3xd8mvYjywqh0wqJ8T+g== + +"@node-redis/search@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@node-redis/search/-/search-1.0.2.tgz#8cfc91006ea787df801d41410283e1f59027f818" + integrity sha512-gWhEeji+kTAvzZeguUNJdMSZNH2c5dv3Bci8Nn2f7VGuf6IvvwuZDSBOuOlirLVgayVuWzAG7EhwaZWK1VDnWQ== + +"@node-redis/time-series@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@node-redis/time-series/-/time-series-1.0.1.tgz#703149f8fa4f6fff377c61a0873911e7c1ba5cc3" + integrity sha512-+nTn6EewVj3GlUXPuD3dgheWqo219jTxlo6R+pg24OeVvFHx9aFGGiyOgj3vBPhWUdRZ0xMcujXV5ki4fbLyMw== + "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" @@ -155,6 +190,14 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" +"@npmcli/fs@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.0.tgz#bec1d1b89c170d40e1b73ad6c943b0b75e7d2951" + integrity sha512-VhP1qZLXcrXRIaPoqb4YA55JQxLNF3jNR4T55IdOJa3+IFJKNYHtPvtXx8slmeMavj37vCzCfrqQM1vWLsYKLA== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + "@npmcli/move-file@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" @@ -163,29 +206,30 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@redocly/ajv@^8.6.2": - version "8.6.2" - resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.2.tgz#8c4e485e72f7864f91fae40093bed548ec2619b2" - integrity sha512-tU8fQs0D76ZKhJ2cWtnfQthWqiZgGBx0gH0+5D8JvaBEBaqA8foPPBt3Nonwr3ygyv5xrw2IzKWgIY86BlGs+w== +"@redocly/ajv@^8.6.4": + version "8.6.4" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.4.tgz#94053e7a9d4146d1a4feacd3813892873f229a85" + integrity sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -"@redocly/openapi-core@1.0.0-beta.54": - version "1.0.0-beta.54" - resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.54.tgz#42575a849c4dd54b9d0c6413fb8ca547e087cd11" - integrity sha512-uYs0N1Trjkh7u8IMIuCU2VxCXhMyGWSZUkP/WNdTR1OgBUtvNdF9C32zoQV+hyCIH4gVu42ROHkjisy333ZX+w== +"@redocly/openapi-core@1.0.0-beta.79": + version "1.0.0-beta.79" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.79.tgz#7512b3507ab99dc78226f9069669c5302abb0969" + integrity sha512-do79vGt3iiHsaVG9LKY8dH+d1E7TLHr+3T+CQ1lqagtWVjYOxqGaoxAT8tRD7R1W0z8BmS4e2poNON6c1sxP5g== dependencies: - "@redocly/ajv" "^8.6.2" + "@redocly/ajv" "^8.6.4" "@types/node" "^14.11.8" colorette "^1.2.0" js-levenshtein "^1.1.6" - js-yaml "^3.14.1" + js-yaml "^4.1.0" lodash.isequal "^4.5.0" minimatch "^3.0.4" node-fetch "^2.6.1" + pluralize "^8.0.0" yaml-ast-parser "0.0.43" "@sindresorhus/is@^3.0.0": @@ -245,11 +289,6 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - "@tsconfig/node10@^1.0.7": version "1.0.7" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.7.tgz#1eb1de36c73478a2479cc661ef5af1c16d86d606" @@ -295,12 +334,13 @@ "@types/connect" "*" "@types/node" "*" -"@types/bull@3.15.5": - version "3.15.5" - resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.15.5.tgz#a4459c127c5b10fb847531579a2cd5db35751366" - integrity sha512-XgJQWJ03jyKMfdoL8IAIoHIo7JkkL74kcxuujTONkSJswm0giIJ9kuVgDNHS0OvD+OiPNcFmbBl0H3scj2+A8A== +"@types/bull@3.15.7": + version "3.15.7" + resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.15.7.tgz#a9d7fb332cc02dc021d0eb234b9604b356e9e6de" + integrity sha512-7NC7XN5NoS0A+leJ/dR69ZfKaegOlCZaii/xGgKnCyh1UYisRncibImb7VMwrc3OdJcbDJt6+4om70TeNl3J7g== dependencies: "@types/ioredis" "*" + "@types/redis" "^2.8.0" "@types/cacheable-request@^6.0.1": version "6.0.1" @@ -356,10 +396,10 @@ resolved "https://registry.yarnpkg.com/@types/disposable-email-domains/-/disposable-email-domains-1.0.2.tgz#0280f6b38fa7f14e54b056a434135ecd254483b1" integrity sha512-SDKwyYTjk3y5aZBxxc38yRecpJPjsqn57STz1bNxYYlv4k11bBe7QB8w4llXDTmQXKT1mFvgGmJv+8Zdu3YmJw== -"@types/escape-regexp@0.0.0": - version "0.0.0" - resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.0.tgz#bff0225f9ef30d0dbdbe0e2a24283ee5342990c3" - integrity sha512-HTansGo4tJ7K7W9I9LBdQqnHtPB/Y7tlS+EMrkboaAQLsRPhRpHaqAHe01K1HVXM5e1u1IplRd8EBh+pJrp7Dg== +"@types/escape-regexp@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.1.tgz#f1a977ccdf2ef059e9862bd3af5e92cbbe723e0e" + integrity sha512-ogj/ZTIdeFkiuxDwawYuZSIgC6suFGgBeZPr6Xs5lHEcvIXTjXGtH+/n8f1XhZhespaUwJ5LIGRICPji972FLw== "@types/eslint-scope@^3.7.0": version "3.7.0" @@ -400,10 +440,10 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/fluent-ffmpeg@2.1.17": - version "2.1.17" - resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.17.tgz#6958dda400fe1b33c21f3683db76905cb210d053" - integrity sha512-/bdvjKw/mtBHlJ2370d04nt4CsWqU5MrwS/NtO96V01jxitJ4+iq8OFNcqc5CegeV3TQOK3uueK02kvRK+zjUg== +"@types/fluent-ffmpeg@2.1.20": + version "2.1.20" + resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.20.tgz#3b5f42fc8263761d58284fa46ee6759a64ce54ac" + integrity sha512-B+OvhCdJ3LgEq2PhvWNOiB/EfwnXLElfMCgc4Z1K5zXgSfo9I6uGKwR/lqmNPFQuebNnes7re3gqkV77SyypLg== dependencies: "@types/node" "*" @@ -442,15 +482,15 @@ resolved "https://registry.yarnpkg.com/@types/is-url/-/is-url-1.2.30.tgz#85567e8bee4fee69202bc3448f9fb34b0d56c50a" integrity sha512-AnlNFwjzC8XLda5VjRl4ItSd8qp8pSNowvsut0WwQyBWHpOxjxRJm8iO6uETWqEyLdYdb9/1j+Qd9gQ4l5I4fw== -"@types/js-yaml@4.0.4": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.4.tgz#cc38781257612581a1a0eb25f1709d2b06812fce" - integrity sha512-AuHubXUmg0AzkXH0Mx6sIxeY/1C110mm/EkE/gB1sTRz3h2dao2W/63q42SlVST+lICxz5Oki2hzYA6+KnnieQ== +"@types/js-yaml@4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" + integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== -"@types/jsdom@16.2.13": - version "16.2.13" - resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.13.tgz#126c8b7441b159d6234610a48de77b6066f1823f" - integrity sha512-8JQCjdeAidptSsOcRWk2iTm9wCcwn9l+kRG6k5bzUacrnm1ezV4forq0kWjUih/tumAeoG+OspOvQEbbRucBTw== +"@types/jsdom@16.2.14": + version "16.2.14" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.14.tgz#26fe9da6a8870715b154bb84cd3b2e53433d8720" + integrity sha512-6BAy1xXEmMuHeAJ4Fv4yXKwBDTGTOseExKE3OaHiNycdHdZw59KfYzrt0DkDluvwmik1HRt6QS7bImxUmpSy+w== dependencies: "@types/node" "*" "@types/parse5" "*" @@ -493,10 +533,10 @@ dependencies: "@types/node" "*" -"@types/koa-bodyparser@4.3.3": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@types/koa-bodyparser/-/koa-bodyparser-4.3.3.tgz#9c7d4295576bc863d550002f732f1c57dd88cc58" - integrity sha512-/ileIpXsy1fFEzgZhZ07eZH8rAVL7jwuk/kaoVEfauO6s80g2LIDIJKEyDbuAL9S/BWflKzEC0PHD6aXkmaSbw== +"@types/koa-bodyparser@4.3.5": + version "4.3.5" + resolved "https://registry.yarnpkg.com/@types/koa-bodyparser/-/koa-bodyparser-4.3.5.tgz#0c5fa44d7150202ffc16b89bd730ce1b6c7bc250" + integrity sha512-NRqqoTtt7cfdDk/KNo+EwCIKRuzPAu/wsaZ7tgIvSIBtNfxuZHYueaLoWdxX3ZftWavQv07NE46TcpyoZGqpgQ== dependencies: "@types/koa" "*" @@ -577,10 +617,10 @@ "@types/koa-compose" "*" "@types/node" "*" -"@types/koa__cors@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-3.0.3.tgz#49d75813b443ba3d4da28ea6cf6244b7e99a3b23" - integrity sha512-74Xb4hJOPGKlrQ4PRBk1A/p0gfLpgbnpT0o67OMVbwyeMXvlBN+ZCRztAAmkKZs+8hKbgMutUlZVbA52Hr/0IA== +"@types/koa__cors@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-3.1.1.tgz#198b5abbc425a672ae57c311b420bc270e65bdef" + integrity sha512-O7MBkCocnLrpEvkMrYAp17arUDS+KuS5bXMG/Z4aPSbrO7vrYB6YrqcsTD3Dp2OnAL3j4WME2k/x2kOcyzwNUw== dependencies: "@types/koa" "*" @@ -591,10 +631,10 @@ dependencies: "@types/koa" "*" -"@types/koa__router@8.0.8": - version "8.0.8" - resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-8.0.8.tgz#b1e0e9a512498777d3366bbdf0e853df27ec831c" - integrity sha512-9pGCaDtzCsj4HJ8HmGuqzk8+s57sPj4njWd08GG5o92n5Xp9io2snc40CPpXFhoKcZ8OKhuu6ht4gNou9e1C2w== +"@types/koa__router@8.0.11": + version "8.0.11" + resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-8.0.11.tgz#d7b37e6db934fc072ea1baa2ab92bc8ac4564f3e" + integrity sha512-WXgKWpBsbS14kzmzD9LeFapOIa678h7zvUHxDwXwSx4ETKXhXLVUAToX6jZ/U7EihM7qwyD9W/BZvB0MRu7MTQ== dependencies: "@types/koa" "*" @@ -613,23 +653,22 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.3.tgz#bbeb55fbc73f28ea6de601fbfa4613f58d785323" integrity sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw== -"@types/node-fetch@2.5.12": - version "2.5.12" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66" - integrity sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw== +"@types/node-fetch@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-3.0.3.tgz#9d969c9a748e841554a40ee435d26e53fa3ee899" + integrity sha512-HhggYPH5N+AQe/OmN6fmhKmRRt2XuNJow+R3pQwJxOOF9GuwM7O2mheyGeIrs5MOIeNjDEdgdoyHBOrFeJBR3g== dependencies: - "@types/node" "*" - form-data "^3.0.0" + node-fetch "*" "@types/node@*": version "16.6.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50" integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA== -"@types/node@16.11.7": - version "16.11.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42" - integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw== +"@types/node@17.0.10": + version "17.0.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.10.tgz#616f16e9d3a2a3d618136b1be244315d95bd7cab" + integrity sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog== "@types/node@^14.11.8": version "14.17.9" @@ -665,20 +704,20 @@ resolved "https://registry.yarnpkg.com/@types/portscanner/-/portscanner-2.1.1.tgz#89d5094e16f3d941f20f3889dfa5d3a164b3dd3b" integrity sha512-1NsVIbgBKvrqxwtMN0V6CLji1ERwKSI/RWz0J3y++CzSwYNGBStCfpIFgxV3ZwxsDR5PoZqoUWhwraDm+Ztn0Q== -"@types/pug@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.5.tgz#69bc700934dd473c7ab97270bd2dbacefe562231" - integrity sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA== +"@types/pug@2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.6.tgz#f830323c88172e66826d0bde413498b61054b5a6" + integrity sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg== "@types/punycode@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/punycode/-/punycode-2.1.0.tgz#89e4f3d09b3f92e87a80505af19be7e0c31d4e83" integrity sha512-PG5aLpW6PJOeV2fHRslP4IOMWn+G+Uq8CfnyJ+PDS8ndCbU+soO+fB3NKCKo0p/Jh2Y4aPaiQZsrOXFdzpcA6g== -"@types/qrcode@1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.1.tgz#0689f400c3a95d2db040c99c99834faa09ee9dc1" - integrity sha512-vxMyr7JM7tYPxu8vUE83NiosWX5DZieCyYeJRoOIg0pAkyofCBzknJ2ycUZkPGDFis2RS8GN/BeJLnRnAPxeCA== +"@types/qrcode@1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.2.tgz#7d7142d6fa9921f195db342ed08b539181546c74" + integrity sha512-7uNT9L4WQTNJejHTSTdaJhfBSCN73xtXaHFyBJ8TSwiLhe4PRuTue7Iph0s2nG9R/ifUaSnGhLUOZavlBEqDWQ== dependencies: "@types/node" "*" @@ -697,10 +736,12 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== -"@types/ratelimiter@3.4.2": - version "3.4.2" - resolved "https://registry.yarnpkg.com/@types/ratelimiter/-/ratelimiter-3.4.2.tgz#adf1a6d0cbe72d42207efc510a9170602e23456c" - integrity sha512-iz+yyY+ViphaM8ZwrX1mUQzelIeC59zyaaLKTJ0YVOOCkCpIYpaysiIM4z5Xv9HdXYqIb80S+DhH7J22A0rW2w== +"@types/ratelimiter@3.4.3": + version "3.4.3" + resolved "https://registry.yarnpkg.com/@types/ratelimiter/-/ratelimiter-3.4.3.tgz#2159c234b9d75bcc2be39379f05c6af0a5e4a3b7" + integrity sha512-B/IRdHGcttRsDeDJ4+VFjzRA1mzqTxsYlg2X8GLQtTgRUMhQQc+bL8zFmuHhZkK4oA+Ldb4K1NogspNDxevWBA== + dependencies: + "@types/redis" "^2.8.0" "@types/readable-stream@^2.3.9": version "2.3.9" @@ -710,7 +751,14 @@ "@types/node" "*" safe-buffer "*" -"@types/redis@2.8.32": +"@types/redis@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@types/redis/-/redis-4.0.11.tgz#0bb4c11ac9900a21ad40d2a6768ec6aaf651c0e1" + integrity sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg== + dependencies: + redis "*" + +"@types/redis@^2.8.0": version "2.8.32" resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.32.tgz#1d3430219afbee10f8cfa389dad2571a05ecfb11" integrity sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w== @@ -736,10 +784,10 @@ dependencies: "@types/node" "*" -"@types/sanitize-html@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.5.0.tgz#bfef58fbcf2674b20ffcc23c3506faa68c3a13e3" - integrity sha512-PeFIEZsO9m1+ACJlXUaimgrR+5DEDiIXhz7Hso307jmq5Yz0lb5kDp8LiTr5dMMMliC/jNNx/qds7Zoxa4zexw== +"@types/sanitize-html@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.6.2.tgz#9c47960841b9def1e4c9dfebaaab010a3f6e97b9" + integrity sha512-7Lu2zMQnmHHQGKXVvCOhSziQMpa+R2hMHFefzbYoYMHeaXR0uXqNeOc3JeQQQ8/6Xa2Br/P1IQTLzV09xxAiUQ== dependencies: htmlparser2 "^6.0.0" @@ -756,10 +804,10 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" -"@types/sharp@0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.29.3.tgz#54ceb81b68bb99a0e62db2b52f4fb0bea84878ac" - integrity sha512-83Xp05eK2hvfNnmKLr2Fz0C2A0jrr2TnSLqKRbkLTYuAu+Erj6mKQLoEMGafE73Om8p3q3ryZxtHFM/7hy4Adg== +"@types/sharp@0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.29.5.tgz#9c7032d30d138ad16dde6326beaff2af757b91b3" + integrity sha512-3TC+S3H5RwnJmLYMHrcdfNjz/CaApKmujjY9b6PU/pE6n0qfooi99YqXGWoW8frU9EWYj/XTI35Pzxa+ThAZ5Q== dependencies: "@types/node" "*" @@ -773,10 +821,10 @@ resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== -"@types/speakeasy@2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/speakeasy/-/speakeasy-2.0.6.tgz#12540f7b64d08180393ae2c5c8c280866a85da61" - integrity sha512-2wIXZp5yJUddhsSZarYCZIakCvzwQgTVdtT29DYVdFzc0cHttanaQx9THRhtjY4kDqVaF2jhyFOEofozOioFdQ== +"@types/speakeasy@2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/speakeasy/-/speakeasy-2.0.7.tgz#cb087c501b3eef744a1ae620c19812dd1c3b2f3f" + integrity sha512-JEcOhN2SQCoX86ZfiZEe8px84sVJtivBXMZfOVyARTYEj0hrwwbj1nF0FwEL3nJSoEV6uTbcdLllMKBgAYHWCQ== dependencies: "@types/node" "*" @@ -795,10 +843,10 @@ resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.3.tgz#ed4a0901f954b126e6a914b4839c77462d56e706" integrity sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ== -"@types/tmp@0.2.2": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.2.tgz#424537a3b91828cb26aaf697f21ae3cd1b69f7e7" - integrity sha512-MhSa0yylXtVMsyT8qFpHA1DLHj4DvQGH5ntxrhHSh8PxUVNi35Wk+P5hVgqbO2qZqOotqr9jaoPRL+iRjWYm/A== +"@types/tmp@0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.3.tgz#908bfb113419fd6a42273674c00994d40902c165" + integrity sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA== "@types/tough-cookie@*": version "4.0.0" @@ -812,10 +860,10 @@ dependencies: source-map "^0.6.1" -"@types/uuid@8.3.1": - version "8.3.1" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f" - integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg== +"@types/uuid@8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/web-push@3.3.2": version "3.3.2" @@ -869,10 +917,10 @@ dependencies: "@types/node" "*" -"@types/ws@8.2.0": - version "8.2.0" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.0.tgz#75faefbe2328f3b833cb8dc640658328990d04f3" - integrity sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg== +"@types/ws@8.2.2": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" + integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg== dependencies: "@types/node" "*" @@ -881,13 +929,14 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.2.tgz#808c9fa7e4517274ed555fa158f2de4b4f468e71" integrity sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg== -"@typescript-eslint/eslint-plugin@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.3.1.tgz#d8ff412f10f54f6364e7fd7c1e70eb6767f434c3" - integrity sha512-cFImaoIr5Ojj358xI/SDhjog57OK2NqlpxwdcgyxDA3bJlZcJq5CPzUXtpD7CxI2Hm6ATU7w5fQnnkVnmwpHqw== +"@typescript-eslint/eslint-plugin@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.0.tgz#e90afea96dff8620892ad216b0e4ccdf8ee32d3a" + integrity sha512-XXVKnMsq2fuu9K2KsIxPUGqb6xAImz8MEChClbXmE3VbveFtBUU5bzM6IPVWqzyADIgdkS2Ws/6Xo7W2TeZWjQ== dependencies: - "@typescript-eslint/experimental-utils" "5.3.1" - "@typescript-eslint/scope-manager" "5.3.1" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/type-utils" "5.10.0" + "@typescript-eslint/utils" "5.10.0" debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" @@ -895,94 +944,69 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.3.1.tgz#bbd8f9b67b4d5fdcb9d2f90297d8fcda22561e05" - integrity sha512-RgFn5asjZ5daUhbK5Sp0peq0SSMytqcrkNfU4pnDma2D8P3ElZ6JbYjY8IMSFfZAJ0f3x3tnO3vXHweYg0g59w== +"@typescript-eslint/parser@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.10.0.tgz#8f59e036f5f1cffc178cacbd5ccdd02aeb96c91c" + integrity sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw== + dependencies: + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" + debug "^4.3.2" + +"@typescript-eslint/scope-manager@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz#bb5d872e8b9e36203908595507fbc4d3105329cb" + integrity sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg== + dependencies: + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" + +"@typescript-eslint/type-utils@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz#8524b9479c19c478347a7df216827e749e4a51e5" + integrity sha512-TzlyTmufJO5V886N+hTJBGIfnjQDQ32rJYxPaeiyWKdjsv2Ld5l8cbS7pxim4DeNs62fKzRSt8Q14Evs4JnZyQ== + dependencies: + "@typescript-eslint/utils" "5.10.0" + debug "^4.3.2" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.0.tgz#beb3cb345076f5b088afe996d57bcd1dfddaa75c" + integrity sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ== + +"@typescript-eslint/typescript-estree@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz#4be24a3dea0f930bb1397c46187d0efdd955a224" + integrity sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA== + dependencies: + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" + debug "^4.3.2" + globby "^11.0.4" + is-glob "^4.0.3" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.10.0.tgz#c3d152a85da77c400e37281355561c72fb1b5a65" + integrity sha512-IGYwlt1CVcFoE2ueW4/ioEwybR60RAdGeiJX/iDAw0t5w0wK3S7QncDwpmsM70nKgGTuVchEWB8lwZwHqPAWRg== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.3.1" - "@typescript-eslint/types" "5.3.1" - "@typescript-eslint/typescript-estree" "5.3.1" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/parser@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.1.0.tgz#6c7f837d210d2bc0a811e7ea742af414f4e00908" - integrity sha512-vx1P+mhCtYw3+bRHmbalq/VKP2Y3gnzNgxGxfEWc6OFpuEL7iQdAeq11Ke3Rhy8NjgB+AHsIWEwni3e+Y7djKA== +"@typescript-eslint/visitor-keys@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz#770215497ad67cd15a572b52089991d5dfe06281" + integrity sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ== dependencies: - "@typescript-eslint/scope-manager" "5.1.0" - "@typescript-eslint/types" "5.1.0" - "@typescript-eslint/typescript-estree" "5.1.0" - debug "^4.3.2" - -"@typescript-eslint/scope-manager@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.1.0.tgz#6f1f26ad66a8f71bbb33b635e74fec43f76b44df" - integrity sha512-yYlyVjvn5lvwCL37i4hPsa1s0ORsjkauhTqbb8MnpvUs7xykmcjGqwlNZ2Q5QpoqkJ1odlM2bqHqJwa28qV6Tw== - dependencies: - "@typescript-eslint/types" "5.1.0" - "@typescript-eslint/visitor-keys" "5.1.0" - -"@typescript-eslint/scope-manager@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.3.1.tgz#3cfbfbcf5488fb2a9a6fbbe97963ee1e8d419269" - integrity sha512-XksFVBgAq0Y9H40BDbuPOTUIp7dn4u8oOuhcgGq7EoDP50eqcafkMVGrypyVGvDYHzjhdUCUwuwVUK4JhkMAMg== - dependencies: - "@typescript-eslint/types" "5.3.1" - "@typescript-eslint/visitor-keys" "5.3.1" - -"@typescript-eslint/types@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.1.0.tgz#a8a75ddfc611660de6be17d3ad950302385607a9" - integrity sha512-sEwNINVxcB4ZgC6Fe6rUyMlvsB2jvVdgxjZEjQUQVlaSPMNamDOwO6/TB98kFt4sYYfNhdhTPBEQqNQZjMMswA== - -"@typescript-eslint/types@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.3.1.tgz#afaa715b69ebfcfde3af8b0403bf27527912f9b7" - integrity sha512-bG7HeBLolxKHtdHG54Uac750eXuQQPpdJfCYuw4ZI3bZ7+GgKClMWM8jExBtp7NSP4m8PmLRM8+lhzkYnSmSxQ== - -"@typescript-eslint/typescript-estree@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.1.0.tgz#132aea34372df09decda961cb42457433aa6e83d" - integrity sha512-SSz+l9YrIIsW4s0ZqaEfnjl156XQ4VRmJsbA0ZE1XkXrD3cRpzuZSVCyqeCMR3EBjF27IisWakbBDGhGNIOvfQ== - dependencies: - "@typescript-eslint/types" "5.1.0" - "@typescript-eslint/visitor-keys" "5.1.0" - debug "^4.3.2" - globby "^11.0.4" - is-glob "^4.0.3" - semver "^7.3.5" - tsutils "^3.21.0" - -"@typescript-eslint/typescript-estree@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.3.1.tgz#50cc4bfb93dc31bc75e08ae52e29fcb786d606ec" - integrity sha512-PwFbh/PKDVo/Wct6N3w+E4rLZxUDgsoII/GrWM2A62ETOzJd4M6s0Mu7w4CWsZraTbaC5UQI+dLeyOIFF1PquQ== - dependencies: - "@typescript-eslint/types" "5.3.1" - "@typescript-eslint/visitor-keys" "5.3.1" - debug "^4.3.2" - globby "^11.0.4" - is-glob "^4.0.3" - semver "^7.3.5" - tsutils "^3.21.0" - -"@typescript-eslint/visitor-keys@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.1.0.tgz#e01a01b27eb173092705ae983aa1451bd1842630" - integrity sha512-uqNXepKBg81JVwjuqAxYrXa1Ql/YDzM+8g/pS+TCPxba0wZttl8m5DkrasbfnmJGHs4lQ2jTbcZ5azGhI7kK+w== - dependencies: - "@typescript-eslint/types" "5.1.0" - eslint-visitor-keys "^3.0.0" - -"@typescript-eslint/visitor-keys@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.3.1.tgz#c2860ff22939352db4f3806f34b21d8ad00588ba" - integrity sha512-3cHUzUuVTuNHx0Gjjt5pEHa87+lzyqOiHXy/Gz+SJOCW1mpw9xQHIIEwnKn+Thph1mgWyZ90nboOcSuZr/jTTQ== - dependencies: - "@typescript-eslint/types" "5.3.1" + "@typescript-eslint/types" "5.10.0" eslint-visitor-keys "^3.0.0" "@ungap/promise-all-settled@1.1.2": @@ -1189,12 +1213,12 @@ acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== -acorn@^8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== +acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== -agent-base@6: +agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== @@ -1233,12 +1257,7 @@ ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -alphanum-sort@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= - -ansi-colors@4.1.1, ansi-colors@^4.1.1: +ansi-colors@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== @@ -1253,11 +1272,6 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - ansi-regex@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" @@ -1268,7 +1282,7 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -1311,6 +1325,11 @@ aproba@^1.0.3: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + archiver-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" @@ -1340,6 +1359,14 @@ archiver@5.3.0: tar-stream "^2.2.0" zip-stream "^4.1.0" +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" @@ -1353,13 +1380,6 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -1456,27 +1476,27 @@ autwh@0.1.0: dependencies: oauth "0.9.15" -aws-sdk@2.1013.0: - version "2.1013.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1013.0.tgz#85babc473b0bc90cc1160eb48baf616ddb86e346" - integrity sha512-TXxkp/meAdofpC15goFpNuur7fvh/mcMRfHJoP1jYzTtD0wcoB4FK16GLcny0uDYgkQgZuiO9QYv3Rq5bhGCqQ== +aws-sdk@2.1061.0: + version "2.1061.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1061.0.tgz#79c75e6856e5a59e0857d0d066a8ff5ff5e0d752" + integrity sha512-T29yV+EPo4Fis9hAArxAXS/u6utKnlBq3DEu85LTSIA8i6e6Xg7e9u7Rveo8DmrlVrf7EGCNThaeF9WERHnwLg== dependencies: buffer "4.9.2" events "1.1.1" ieee754 "1.1.13" - jmespath "0.15.0" + jmespath "0.16.0" querystring "0.2.0" sax "1.2.1" url "0.10.3" uuid "3.3.2" xml2js "0.4.19" -axios@^0.19.2: - version "0.19.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== +axios@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" + integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== dependencies: - follow-redirects "1.5.10" + follow-redirects "^1.14.4" babel-walk@3.0.0-canary-5: version "3.0.0-canary-5" @@ -1574,7 +1594,7 @@ bn.js@^4.0.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -boolbase@^1.0.0, boolbase@~1.0.0: +boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= @@ -1594,16 +1614,17 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" -broadcast-channel@4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.5.0.tgz#d4717c493e219908fcb7f2f9078fe0baf95b77c1" - integrity sha512-jp+VPlQ1HyR0CM3uIYUrdpXupBvhTMFRkjR6mEmt5W4HaGDPFEzrO2Jqvi2PZ6zCC4zwLeco7CC5EUJPrVH8Tw== +broadcast-channel@4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.9.0.tgz#8af337d4ea19aeb6b819ec2eb3dda942b28c724c" + integrity sha512-xWzFb3wrOZGJF2kOSs2D3KvHXdLDMVb+WypEIoNvwblcHgUBydVy65pDJ9RS4WN9Kyvs0UVQuCCzfKme0G6Qjw== dependencies: "@babel/runtime" "^7.16.0" detect-node "^2.1.0" microseconds "0.2.0" nano-time "1.0.0" oblivious-set "1.0.0" + p-queue "6.6.2" rimraf "3.0.2" unload "2.3.1" @@ -1617,7 +1638,7 @@ browser-stdout@1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserslist@^4.0.0, browserslist@^4.14.5: +browserslist@^4.14.5: version "4.16.3" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717" integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw== @@ -1628,48 +1649,6 @@ browserslist@^4.0.0, browserslist@^4.14.5: escalade "^3.1.1" node-releases "^1.1.70" -browserslist@^4.16.0: - version "4.16.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.4.tgz#7ebf913487f40caf4637b892b268069951c35d58" - integrity sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ== - dependencies: - caniuse-lite "^1.0.30001208" - colorette "^1.2.2" - electron-to-chromium "^1.3.712" - escalade "^3.1.1" - node-releases "^1.1.71" - -browserslist@^4.16.6: - version "4.16.6" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" - integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== - dependencies: - caniuse-lite "^1.0.30001219" - colorette "^1.2.2" - electron-to-chromium "^1.3.723" - escalade "^3.1.1" - node-releases "^1.1.71" - -bs-logger@0.x: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -buffer-alloc-unsafe@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" - integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== - -buffer-alloc@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" - integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== - dependencies: - buffer-alloc-unsafe "^1.1.0" - buffer-fill "^1.0.0" - buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -1680,17 +1659,7 @@ buffer-equal-constant-time@1.0.1: resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= -buffer-fill@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" - integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= - -buffer-from@1.x: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-from@^1.0.0, buffer-from@^1.1.1: +buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== @@ -1714,7 +1683,7 @@ buffer@4.9.2: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.4.3, buffer@^5.5.0: +buffer@^5.5.0: version "5.6.0" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== @@ -1742,10 +1711,10 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "~3.7.0" -bull@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bull/-/bull-4.1.0.tgz#ff8f628694e7dbbdf89b6a6860a1f33e990527fd" - integrity sha512-rQcLuAmzZIv1dHJO/yKrWu497xcTxMpYeTEBfpStrJFZ1UZpBSGHSx+defbtFVeGEeY8Pn0aMGRvRtldUBVUyQ== +bull@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/bull/-/bull-4.2.1.tgz#c5a7e1496c7903274ce90192e4e5cb18f6c866c0" + integrity sha512-YkCQZMOub++siHw3SbYYXZ5xGEn6Tt3BPoCVq/irPNCxUqUYzta8yDlXyyAsfMKMVj0M7PcnynUabfMf9PFpOA== dependencies: cron-parser "^2.13.0" debuglog "^1.0.0" @@ -1753,9 +1722,7 @@ bull@4.1.0: ioredis "^4.27.0" lodash "^4.17.21" p-timeout "^3.2.0" - promise.prototype.finally "^3.1.2" semver "^7.3.2" - util.promisify "^1.0.1" uuid "^8.3.0" busboy@^0.2.11: @@ -1771,11 +1738,12 @@ bytes@3.1.0, bytes@^3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== -cacache@^15.0.5: - version "15.1.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.1.0.tgz#164c2f857ee606e4cc793c63018fefd0ea5eba7b" - integrity sha512-mfx0C+mCfWjD1PnwQ9yaOrwG1ou9FkKnx0SvzUHWdFt7r7GaRtzT+9M8HAvLu62zIHtnpQ/1m93nWNDCckJGXQ== +cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== dependencies: + "@npmcli/fs" "^1.0.0" "@npmcli/move-file" "^1.0.1" chownr "^2.0.0" fs-minipass "^2.0.0" @@ -1853,36 +1821,11 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0: - version "1.0.30001048" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001048.tgz#4bb4f1bc2eb304e5e1154da80b93dee3f1cf447e" - integrity sha512-g1iSHKVxornw0K8LG9LLdf+Fxnv7T1Z+mMsf0/YYLclQX4Cd522Ap0Lrw6NFqHgezit78dtyWxzlV2Xfc7vgRg== - caniuse-lite@^1.0.30001181: version "1.0.30001191" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001191.tgz#bacb432b6701f690c8c5f7c680166b9a9f0843d9" integrity sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw== -caniuse-lite@^1.0.30001208: - version "1.0.30001208" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz#a999014a35cebd4f98c405930a057a0d75352eb9" - integrity sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA== - -caniuse-lite@^1.0.30001219: - version "1.0.30001230" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71" - integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ== - canonicalize@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/canonicalize/-/canonicalize-1.0.1.tgz#657b4f3fa38a6ecb97a9e5b7b26d7a19cc6e0da9" @@ -1955,23 +1898,6 @@ character-parser@^2.2.0: dependencies: is-regex "^1.0.3" -chart.js@3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.6.0.tgz#a87fce8431d4e7c5523d721f487f53aada1e42fe" - integrity sha512-iOzzDKePL+bj+ccIsVAgWQehCXv8xOKGbaU2fO/myivH736zcx535PGJzQGanvcSGVOqX6yuLZsN3ygcQ35UgQ== - -chartjs-adapter-date-fns@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz#5e53b2f660b993698f936f509c86dddf9ed44c6b" - integrity sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw== - -chartjs-plugin-zoom@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chartjs-plugin-zoom/-/chartjs-plugin-zoom-1.1.1.tgz#8a28923a17fcb5eb57a0dc94c5113bf402677647" - integrity sha512-1q54WOzK7FtAjkbemQeqvmFUV0btNYIQny2HbQ6Awq9wUtCz7Zmj6vIgp3C1DYMQwN0nqgpC3vnApqiwI7cSdQ== - dependencies: - hammerjs "^2.0.8" - cheerio@0.22.0: version "0.22.0" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" @@ -2043,14 +1969,14 @@ cli-highlight@2.1.11, cli-highlight@^2.1.11: parse5-htmlparser2-tree-adapter "^6.0.0" yargs "^16.0.0" -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" cliui@^7.0.2: version "7.0.4" @@ -2068,7 +1994,7 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" -cluster-key-slot@^1.1.0: +cluster-key-slot@1.1.0, cluster-key-slot@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== @@ -2135,6 +2061,11 @@ color-string@^1.6.0: color-name "^1.0.0" simple-swizzle "^0.2.2" +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + color@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/color/-/color-4.0.1.tgz#21df44cd10245a91b1ccf5ba031609b0e10e7d67" @@ -2143,12 +2074,7 @@ color@^4.0.1: color-convert "^2.0.1" color-string "^1.6.0" -colord@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.1.tgz#c961ea0efeb57c9f0f4834458f26cb9cc4a3f90e" - integrity sha512-4LBMSt09vR0uLnPVkOUBnmxgoaeN4ewRbx801wY/bXcltXfpR/G46OdWn96XpYmCWuYvO46aBZP4NgX8HpNAcw== - -colorette@^1.2.0, colorette@^1.2.1, colorette@^1.2.2: +colorette@^1.2.0, colorette@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== @@ -2165,11 +2091,6 @@ commander@^2.19.0, commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - commander@^8.2.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" @@ -2217,7 +2138,7 @@ config-chain@^1.1.12: ini "^1.3.4" proto-list "~1.2.1" -console-control-strings@^1.0.0, console-control-strings@~1.1.0: +console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= @@ -2237,7 +2158,14 @@ constantinople@^4.0.1: "@babel/parser" "^7.6.0" "@babel/types" "^7.6.1" -content-disposition@0.5.3, content-disposition@~0.5.2: +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-disposition@~0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== @@ -2312,43 +2240,6 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -css-color-names@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-1.0.1.tgz#6ff7ee81a823ad46e020fa2fd6ab40a887e2ba67" - integrity sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA== - -css-declaration-sorter@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.0.3.tgz#9dfd8ea0df4cc7846827876fafb52314890c21a9" - integrity sha512-52P95mvW1SMzuRZegvpluT6yEv0FqQusydKQPZsNN5Q7hh8EwQvN8E2nwuJ16BBvNN6LcoIZXu/Bk58DAhrrxw== - dependencies: - timsort "^0.3.0" - -css-loader@6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.5.1.tgz#0c43d4fbe0d97f699c91e9818cb585759091d1b1" - integrity sha512-gEy2w9AnJNnD9Kuo4XAP9VflW/ujKoS9c/syO+uWMlm5igc7LysKzPXaDoR2vroROkSwsTS2tGr1yGGEbZOYZQ== - dependencies: - icss-utils "^5.1.0" - postcss "^8.2.15" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.1.0" - semver "^7.3.5" - -css-select@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067" - integrity sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA== - dependencies: - boolbase "^1.0.0" - css-what "^5.0.0" - domhandler "^4.2.0" - domutils "^2.6.0" - nth-check "^2.0.0" - css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" @@ -2359,86 +2250,11 @@ css-select@~1.2.0: domutils "1.5.1" nth-check "~1.0.1" -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - css-what@2.1: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== -css-what@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" - integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssnano-preset-default@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.6.tgz#1bdb83be6a6b1fee6dc5e9ec2e61286bcadcc7a6" - integrity sha512-X2nDeNGBXc0486oHjT2vSj+TdeyVsxRvJUxaOH50hOM6vSDLkKd0+59YXpSZRInJ4sNtBOykS4KsPfhdrU/35w== - dependencies: - css-declaration-sorter "^6.0.3" - cssnano-utils "^2.0.1" - postcss-calc "^8.0.0" - postcss-colormin "^5.2.1" - postcss-convert-values "^5.0.2" - postcss-discard-comments "^5.0.1" - postcss-discard-duplicates "^5.0.1" - postcss-discard-empty "^5.0.1" - postcss-discard-overridden "^5.0.1" - postcss-merge-longhand "^5.0.3" - postcss-merge-rules "^5.0.2" - postcss-minify-font-values "^5.0.1" - postcss-minify-gradients "^5.0.3" - postcss-minify-params "^5.0.1" - postcss-minify-selectors "^5.1.0" - postcss-normalize-charset "^5.0.1" - postcss-normalize-display-values "^5.0.1" - postcss-normalize-positions "^5.0.1" - postcss-normalize-repeat-style "^5.0.1" - postcss-normalize-string "^5.0.1" - postcss-normalize-timing-functions "^5.0.1" - postcss-normalize-unicode "^5.0.1" - postcss-normalize-url "^5.0.2" - postcss-normalize-whitespace "^5.0.1" - postcss-ordered-values "^5.0.2" - postcss-reduce-initial "^5.0.1" - postcss-reduce-transforms "^5.0.1" - postcss-svgo "^5.0.3" - postcss-unique-selectors "^5.0.1" - -cssnano-utils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-2.0.1.tgz#8660aa2b37ed869d2e2f22918196a9a8b6498ce2" - integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ== - -cssnano@5.0.10: - version "5.0.10" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.10.tgz#92207eb7c9c6dc08d318050726f9fad0adf7220b" - integrity sha512-YfNhVJJ04imffOpbPbXP2zjIoByf0m8E2c/s/HnvSvjXgzXMfgopVjAEGvxYOjkOpWuRQDg/OZFjO7WW94Ri8w== - dependencies: - cssnano-preset-default "^5.1.6" - is-resolvable "^1.1.0" - lilconfig "^2.0.3" - yaml "^1.10.2" - -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" @@ -2476,6 +2292,11 @@ data-uri-to-buffer@^3.0.1: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== +data-uri-to-buffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" + integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA== + data-urls@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" @@ -2485,11 +2306,6 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@2.25.0: - version "2.25.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.25.0.tgz#8c5c8f1d958be3809a9a03f4b742eba894fc5680" - integrity sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w== - dateformat@4.5.1: version "4.5.1" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.5.1.tgz#c20e7a9ca77d147906b6dc2261a8be0a5bd2173c" @@ -2516,13 +2332,6 @@ debug@4.3.3: dependencies: ms "2.1.2" -debug@=3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -2564,13 +2373,6 @@ decimal.js@^10.2.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== -decompress-response@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" - integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== - dependencies: - mimic-response "^2.0.0" - decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -2578,17 +2380,15 @@ decompress-response@^6.0.0: dependencies: mimic-response "^3.1.0" -deep-email-validator@0.1.18: - version "0.1.18" - resolved "https://registry.yarnpkg.com/deep-email-validator/-/deep-email-validator-0.1.18.tgz#a072a93f28e11863cc6b9ca3ae964e0e45b3ece8" - integrity sha512-eo2WEUidQvppg6Qdek8iwOqmXvaxRJ2D2VJKbIOwUgLZNFveDDdJMBsFc+yq0S+lILEUcmzrJRrCWbyoe7QUzQ== +deep-email-validator@0.1.21: + version "0.1.21" + resolved "https://registry.yarnpkg.com/deep-email-validator/-/deep-email-validator-0.1.21.tgz#5d0120fe1aeae83ab7cb39378a40a381b681219f" + integrity sha512-DBAmMzbr+MAubXQ+TS9tZuPwLcdKscb8YzKZiwoLqF3NmaeEgXvSSHhZ0EXOFeKFE2FNWC4mNXCyiQ/JdFXUwg== dependencies: "@types/disposable-email-domains" "^1.0.1" - axios "^0.19.2" - disposable-email-domains "^1.0.53" - lodash "^4.17.15" + axios "^0.24.0" + disposable-email-domains "^1.0.59" mailcheck "^1.1.1" - ts-jest "^25.2.1" deep-equal@~1.0.1: version "1.0.1" @@ -2615,7 +2415,7 @@ defer-to-connect@^2.0.0: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -2702,10 +2502,10 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -disposable-email-domains@^1.0.53: - version "1.0.58" - resolved "https://registry.yarnpkg.com/disposable-email-domains/-/disposable-email-domains-1.0.58.tgz#ac9c879c02c4f0898bfb6c0c80b959c0b0b7bc51" - integrity sha512-frnNCPqTjk6t/sosPoco6EIFHbP9SazHQkeltJNfZeUyNgewaVf+kFjEfVkVDVd436Vln43YElJPb8JozhBs7Q== +disposable-email-domains@^1.0.59: + version "1.0.59" + resolved "https://registry.yarnpkg.com/disposable-email-domains/-/disposable-email-domains-1.0.59.tgz#8b3670667dcef9d0d21b224de283d56d468913c2" + integrity sha512-45NbOP1Oboaddf0pD5mGnT+1msEifY6VUcR9Msq4zBHk2EeGv9PxiwuoynIfdGID1BSFR3U3egPfMbERkqXxUQ== doctrine@^2.1.0: version "2.1.0" @@ -2810,7 +2610,7 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -domutils@^2.5.2, domutils@^2.6.0: +domutils@^2.5.2: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== @@ -2866,21 +2666,6 @@ electron-to-chromium@^1.3.649: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.672.tgz#3a6e335016dab4bc584d5292adc4f98f54541f6a" integrity sha512-gFQe7HBb0lbOMqK2GAS5/1F+B0IMdYiAgB9OT/w1F4M7lgJK2aNOMNOM622aEax+nS1cTMytkiT0uMOkbtFmHw== -electron-to-chromium@^1.3.712: - version "1.3.717" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.717.tgz#78d4c857070755fb58ab64bcc173db1d51cbc25f" - integrity sha512-OfzVPIqD1MkJ7fX+yTl2nKyOE4FReeVfMCzzxQS+Kp43hZYwHwThlGP+EGIZRXJsxCM7dqo8Y65NOX/HP12iXQ== - -electron-to-chromium@^1.3.723: - version "1.3.742" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.742.tgz#7223215acbbd3a5284962ebcb6df85d88b95f200" - integrity sha512-ihL14knI9FikJmH2XUIDdZFWJxvr14rPSdOhJ7PpS27xbz8qmaRwCwyg/bmFwjWKmWK9QyamiCZVCvXm5CH//Q== - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -2891,6 +2676,11 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== +encode-utf8@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" + integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== + encodeurl@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -2926,13 +2716,6 @@ enhanced-resolve@^5.7.0: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== - dependencies: - ansi-colors "^4.1.1" - entities@^1.1.1, entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -2953,23 +2736,6 @@ err-code@^2.0.2: resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== -es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: - version "1.17.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" - integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" - es-abstract@^1.19.0, es-abstract@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" @@ -3081,43 +2847,32 @@ eslint-import-resolver-node@^0.3.6: debug "^3.2.7" resolve "^1.20.0" -eslint-module-utils@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c" - integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ== +eslint-module-utils@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz#1d0aa455dcf41052339b63cada8ab5fd57577129" + integrity sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg== dependencies: debug "^3.2.7" find-up "^2.1.0" - pkg-dir "^2.0.0" -eslint-plugin-import@2.25.3: - version "2.25.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz#a554b5f66e08fb4f6dc99221866e57cfff824766" - integrity sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg== +eslint-plugin-import@2.25.4: + version "2.25.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" + integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== dependencies: array-includes "^3.1.4" array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.1" + eslint-module-utils "^2.7.2" has "^1.0.3" is-core-module "^2.8.0" is-glob "^4.0.3" minimatch "^3.0.4" object.values "^1.1.5" resolve "^1.20.0" - tsconfig-paths "^3.11.0" - -eslint-plugin-vue@8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.0.3.tgz#791cc4543940319e612ea61a1d779e8c87cf749a" - integrity sha512-Rlhhy5ltzde0sRwSkqHuNePTXLMMaJ5+qsQubM4RYloYsQ8cXlnJT5MDaCzSirkGADipOHtmQXIbbPFAzUrADg== - dependencies: - eslint-utils "^3.0.0" - natural-compare "^1.4.0" - semver "^7.3.5" - vue-eslint-parser "^8.0.1" + tsconfig-paths "^3.12.0" eslint-scope@^5.1.1: version "5.1.1" @@ -3127,10 +2882,10 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-6.0.0.tgz#9cf45b13c5ac8f3d4c50f46a5121f61b3e318978" - integrity sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA== +eslint-scope@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" + integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -3152,24 +2907,28 @@ eslint-visitor-keys@^3.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186" integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q== -eslint@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.2.0.tgz#44d3fb506d0f866a506d97a0fc0e90ee6d06a815" - integrity sha512-erw7XmM+CLxTOickrimJ1SiF55jiNlVSp2qqm0NuBWPtHYQCegD5ZMaW0c3i5ytPqL+SSLaCxdvQXFPLJn+ABw== +eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" + integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== + +eslint@8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.7.0.tgz#22e036842ee5b7cf87b03fe237731675b4d3633c" + integrity sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w== dependencies: - "@eslint/eslintrc" "^1.0.4" - "@humanwhocodes/config-array" "^0.6.0" + "@eslint/eslintrc" "^1.0.5" + "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" - enquirer "^2.3.5" escape-string-regexp "^4.0.0" - eslint-scope "^6.0.0" + eslint-scope "^7.1.0" eslint-utils "^3.0.0" - eslint-visitor-keys "^3.0.0" - espree "^9.0.0" + eslint-visitor-keys "^3.2.0" + espree "^9.3.0" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -3177,7 +2936,7 @@ eslint@8.2.0: functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" globals "^13.6.0" - ignore "^4.0.6" + ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" @@ -3188,9 +2947,7 @@ eslint@8.2.0: minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" - progress "^2.0.0" regexpp "^3.2.0" - semver "^7.2.1" strip-ansi "^6.0.1" strip-json-comments "^3.1.0" text-table "^0.2.0" @@ -3201,16 +2958,16 @@ esm@^3.2.22: resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== -espree@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.0.0.tgz#e90a2965698228502e771c7a58489b1a9d107090" - integrity sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ== +espree@^9.2.0, espree@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" + integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== dependencies: - acorn "^8.5.0" + acorn "^8.7.0" acorn-jsx "^5.3.1" - eslint-visitor-keys "^3.0.0" + eslint-visitor-keys "^3.1.0" -esprima@^4.0.0, esprima@^4.0.1: +esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -3254,7 +3011,7 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter3@4.0.7, eventemitter3@^4.0.7: +eventemitter3@4.0.7, eventemitter3@^4.0.4, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -3347,7 +3104,7 @@ fast-glob@^3.1.1: micromatch "^4.0.2" picomatch "^2.2.1" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -3381,6 +3138,14 @@ fetch-blob@^2.1.1: resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.4.tgz#e8c6567f80ad7fc22fd302e7dcb72bafde9c1717" + integrity sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -3427,12 +3192,13 @@ find-up@^2.1.0: dependencies: locate-path "^2.0.0" -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== +find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: - locate-path "^3.0.0" + locate-path "^5.0.0" + path-exists "^4.0.0" findup-sync@^4.0.0: version "4.0.0" @@ -3470,12 +3236,10 @@ fluent-ffmpeg@2.1.2: async ">=0.2.9" which "^1.1.1" -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" +follow-redirects@^1.14.4: + version "1.14.7" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" + integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== form-data@^3.0.0: version "3.0.0" @@ -3486,6 +3250,13 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + fresh@~0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -3542,6 +3313,21 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +gauge@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.0.tgz#afba07aa0374a93c6219603b1fb83eaa2264d8f8" + integrity sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw== + dependencies: + ansi-regex "^5.0.1" + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -3556,6 +3342,11 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" +generic-pool@3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.8.2.tgz#aab4f280adb522fdfbdc5e5b64d718d3683f04e9" + integrity sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg== + get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -3757,11 +3548,6 @@ growl@1.10.5: resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== -hammerjs@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" - integrity sha1-BO93hiz/K7edMPdpIJWTAiK/YPE= - has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" @@ -3777,7 +3563,7 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.0, has-symbols@^1.0.1: +has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== @@ -3794,7 +3580,7 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-unicode@^2.0.0: +has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= @@ -3917,13 +3703,13 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-signature@1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.5.tgz#9f19496ffbf3227298d7b5f156e0e1a948678683" - integrity sha512-NwoTQYSJoFt34jSBbwzDHDofoA61NGXzu6wXh95o1Ry62EnmKjXb/nR/RknLeZ3G/uGwrlKNY2z7uPt+Cdl7Tw== +http-signature@1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" + integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== dependencies: assert-plus "^1.0.0" - jsprim "^1.2.2" + jsprim "^2.0.2" sshpk "^1.14.1" http2-wrapper@^1.0.0-beta.5.0: @@ -3988,11 +3774,6 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - ieee754@1.1.13, ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" @@ -4018,6 +3799,11 @@ ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" @@ -4036,11 +3822,6 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= - infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" @@ -4074,10 +3855,10 @@ ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== -install-artifact-from-github@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.2.0.tgz#adcbd123c16a4337ec44ea76d0ebf253cc16b074" - integrity sha512-3OxCPcY55XlVM3kkfIpeCgmoSKnMsz2A3Dbhsq0RXpIknKQmrX1YiznCeW9cD2ItFmDxziA3w6Eg8d80AoL3oA== +install-artifact-from-github@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.0.tgz#cab6ff821976b8a35b0c079da19a727c90381a40" + integrity sha512-iT8v1GwOAX0pPXifF/5ihnMhHOCo3OeK7z3TQa4CtSNCIg8k0UxqBEk9jRwz8OP68hHXvJ2gxRa89KYHtBkqGA== internal-slot@^1.0.3: version "1.0.3" @@ -4135,11 +3916,6 @@ ipaddr.js@^2.0.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== -is-absolute-url@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" - integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== - is-arrayish@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" @@ -4172,7 +3948,7 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.1.5: +is-callable@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== @@ -4318,7 +4094,7 @@ is-promise@^2.0.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.0.3, is-regex@^1.0.5: +is-regex@^1.0.3: version "1.0.5" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== @@ -4333,11 +4109,6 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-resolvable@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - is-shared-array-buffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" @@ -4355,10 +4126,10 @@ is-string@^1.0.5, is-string@^1.0.7: dependencies: has-tostringtag "^1.0.0" -is-svg@4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-4.3.1.tgz#8c63ec8c67c8c7f0a8de0a71c8c7d58eccf4406b" - integrity sha512-h2CGs+yPUyvkgTJQS9cJzo9lYK06WgRiXUqBBHtglSzVKAuH4/oWsqk7LGfbSa1hGk9QcZ0SyQtVggvBA8LZXA== +is-svg@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-4.3.2.tgz#a119e9932e1af53f6be1969d1790d6cc5fd947d3" + integrity sha512-mM90duy00JGMyjqIVHu9gNTjywdZV+8qNasX8cm/EEYZ53PHDgajvbBwNVvty5dwSAxLUD3p3bdo+7sR/UMrpw== dependencies: fast-xml-parser "^3.19.0" @@ -4408,11 +4179,6 @@ isarray@^1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isarray@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -4427,10 +4193,10 @@ jest-worker@^26.6.2: merge-stream "^2.0.0" supports-color "^7.0.0" -jmespath@0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" - integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= +jmespath@0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== jpeg-js@^0.4.1: version "0.4.1" @@ -4472,14 +4238,6 @@ js-yaml@4.1.0, js-yaml@^4.0.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -js-yaml@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - jsbn@1.1.0, jsbn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" @@ -4548,10 +4306,10 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" @@ -4572,7 +4330,7 @@ json5-loader@4.0.1: loader-utils "^2.0.0" schema-utils "^3.0.0" -json5@2.2.0, json5@2.x: +json5@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== @@ -4619,14 +4377,14 @@ jsonld@5.2.0: lru-cache "^6.0.0" rdf-canonize "^3.0.0" -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= +jsprim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" + integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" - json-schema "0.2.3" + json-schema "0.4.0" verror "1.10.0" jsrsasign@8.0.20: @@ -4847,11 +4605,6 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -lilconfig@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd" - integrity sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg== - listenercount@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" @@ -4879,13 +4632,12 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" + p-locate "^4.1.0" locate-path@^6.0.0: version "6.0.0" @@ -4954,11 +4706,6 @@ lodash.map@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= -lodash.memoize@4.x, lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - lodash.merge@^4.4.0, lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -4994,12 +4741,7 @@ lodash.union@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: +lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -5036,18 +4778,18 @@ mailcheck@^1.1.1: resolved "https://registry.yarnpkg.com/mailcheck/-/mailcheck-1.1.1.tgz#d87cf6ba0b64ba512199dbf93f1489f479591e34" integrity sha1-2Hz2ugtkulEhmdv5PxSJ9HlZHjQ= -make-error@1.x, make-error@^1.1.1: +make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -make-fetch-happen@^8.0.14: - version "8.0.14" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz#aaba73ae0ab5586ad8eaa68bd83332669393e222" - integrity sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ== +make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== dependencies: agentkeepalive "^4.1.3" - cacache "^15.0.5" + cacache "^15.2.0" http-cache-semantics "^4.1.0" http-proxy-agent "^4.0.1" https-proxy-agent "^5.0.0" @@ -5058,15 +4800,11 @@ make-fetch-happen@^8.0.14: minipass-fetch "^1.3.2" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" + negotiator "^0.6.2" promise-retry "^2.0.1" - socks-proxy-agent "^5.0.0" + socks-proxy-agent "^6.0.0" ssri "^8.0.0" -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -5092,21 +4830,13 @@ methods@^1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -mfm-js@0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.20.0.tgz#3afdcd7959461fd825aa8af9b9e8a57cdbddc290" - integrity sha512-1+3tV3nWUKQNh/ztX3wXu5iLBtdsg6q3wUhl+XyOhc2H3sQdG+sih/w2c0nR9TIawjN+Z1/pvgGzxMJHfmKQmA== +mfm-js@0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.21.0.tgz#954cc6e7071700b0b1872c78a90bada10be7f772" + integrity sha512-nyQXaipa7rmAw9ER9uYigMvGcdCwhSv93abZBwccnSnPOc1W3S/WW0+sN28g3YSmlHDCA0i2q9aAFc9EgOi5KA== dependencies: twemoji-parser "13.1.x" -micromatch@4.x: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== - dependencies: - braces "^3.0.1" - picomatch "^2.2.3" - micromatch@^4.0.0, micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" @@ -5154,11 +4884,6 @@ mimic-response@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -mimic-response@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" - integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== - mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -5242,10 +4967,10 @@ minizlib@^2.0.0, minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" -misskey-js@0.0.12: - version "0.0.12" - resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.12.tgz#c8fc2fc823c69b0b7d1cb153a5a29afc33f0ff4d" - integrity sha512-Aq74/XskxaFN5CeCLeKPp5UP/xTFHvVnOV677G/zoSIShJRTeLsN5YnzwFpOVI2KN21JQ/ExesKDLoWlvQHtNA== +misskey-js@0.0.13: + version "0.0.13" + resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.13.tgz#03a4e469186e28752d599dc4093519eb64647970" + integrity sha512-kBdJdfe281gtykzzsrN3IAxWUQIimzPiJGyKWf863ggWJlWYVPmP9hTFlX2z8oPOaypgVBPEPHyw/jNUdc2DbQ== dependencies: autobind-decorator "^2.4.0" eventemitter3 "^4.0.7" @@ -5261,7 +4986,7 @@ mkdirp-classic@^0.5.3: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.4: +"mkdirp@>=0.5 0", mkdirp@^0.5.4: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -5336,10 +5061,10 @@ ms@3.0.0-canary.1: resolved "https://registry.yarnpkg.com/ms/-/ms-3.0.0-canary.1.tgz#c7b34fbce381492fd0b345d1cf56e14d67b77b80" integrity sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g== -multer@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.3.tgz#4db352d6992e028ac0eacf7be45c6efd0264297b" - integrity sha512-np0YLKncuZoTzufbkM6wEKp68EhWJXcU6fq6QqrSwkckd2LlMgd1UqhUJLj6NS/5sZ8dE8LYDWslsltJznnXlg== +multer@1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.4.tgz#e2bc6cac0df57a8832b858d7418ccaa8ebaf7d8c" + integrity sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw== dependencies: append-field "^1.0.0" busboy "^0.2.11" @@ -5364,10 +5089,10 @@ mz@^2.4.0, mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.14.2: - version "2.14.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" - integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== +nan@^2.15.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" + integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== nano-time@1.0.0: version "1.0.0" @@ -5381,11 +5106,6 @@ nanoid@3.1.20: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== -nanoid@^3.1.23: - version "3.1.23" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" - integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== - nanoid@^3.1.30: version "3.1.30" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" @@ -5410,7 +5130,7 @@ needle@^2.5.2: iconv-lite "^0.4.4" sax "^1.2.4" -negotiator@0.6.2: +negotiator@0.6.2, negotiator@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== @@ -5440,18 +5160,32 @@ next-tick@~1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= -node-abi@^2.21.0: - version "2.21.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.21.0.tgz#c2dc9ebad6f4f53d6ea9b531e7b8faad81041d48" - integrity sha512-smhrivuPqEM3H5LmnY3KU6HfYv0u4QklgAxfFyRNujKUzbUcYZ+Jc2EhukB9SRcD2VpqhxM7n/MIcp1Ua1/JMg== +node-abi@^3.3.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.5.0.tgz#26e8b7b251c3260a5ac5ba5aef3b4345a0229248" + integrity sha512-LtHvNIBgOy5mO8mPEUtkCW/YCRWYEKshIvqhe1GHHyXEHEB5mgICyYnAcl4qan3uFeRROErKGzatFHPf6kDxWw== dependencies: - semver "^5.4.1" + semver "^7.3.5" node-addon-api@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.2.0.tgz#117cbb5a959dff0992e1c586ae0393573e4d2a87" integrity sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q== +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@*: + version "3.2.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.0.tgz#59390db4e489184fa35d4b74caf5510e8dfbaf3b" + integrity sha512-8xeimMwMItMw8hRrOl3C9/xzU49HV/yE6ORew/l+dxWimO5A4Ra8ld2rerlJvc/O7et5Z1zrWsPX43v1QBjCxw== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + node-fetch@2.6.1, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" @@ -5470,31 +5204,31 @@ node-gyp-build@~3.7.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d" integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w== -node-gyp@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.0.0.tgz#225af2b06b8419ae81f924bf25ae4c167f6378a5" - integrity sha512-Jod6NxyWtcwrpAQe0O/aXOpC5QfncotgtG73dg65z6VW/C6g/G4jiajXQUBIJ8pk/VfM6mBYE9BN/HvudTunUQ== +node-gyp@^8.4.1: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== dependencies: env-paths "^2.2.0" glob "^7.1.4" graceful-fs "^4.2.6" - make-fetch-happen "^8.0.14" + make-fetch-happen "^9.1.0" nopt "^5.0.0" - npmlog "^4.1.2" + npmlog "^6.0.0" rimraf "^3.0.2" semver "^7.3.5" - tar "^6.1.0" + tar "^6.1.2" which "^2.0.2" -node-releases@^1.1.70, node-releases@^1.1.71: +node-releases@^1.1.70: version "1.1.71" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== -nodemailer@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.0.tgz#86614722c4e0c33d1b5b02aecb90d6d629932b0d" - integrity sha512-AtiTVUFHLiiDnMQ43zi0YgkzHOEWUkhDgPlBXrsDzJiJvB29Alo4OKxHQ0ugF3gRqRQIneCLtZU3yiUo7pItZw== +nodemailer@6.7.2: + version "6.7.2" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.2.tgz#44b2ad5f7ed71b7067f7a21c4fedabaec62b85e0" + integrity sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q== nofilter@^2.0.3: version "2.0.3" @@ -5533,11 +5267,6 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - npm-run-path@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.0.1.tgz#748dd68ed7de377bb1f7132c7dafe657be5ab400" @@ -5545,7 +5274,7 @@ npm-run-path@^5.0.1: dependencies: path-key "^4.0.0" -npmlog@^4.0.1, npmlog@^4.1.2: +npmlog@^4.0.1: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -5555,12 +5284,15 @@ npmlog@^4.0.1, npmlog@^4.1.2: gauge "~2.7.3" set-blocking "~2.0.0" -nth-check@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125" - integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q== +npmlog@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.0.tgz#ba9ef39413c3d936ea91553db7be49c34ad0520c" + integrity sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q== dependencies: - boolbase "^1.0.0" + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.0" + set-blocking "^2.0.0" nth-check@~1.0.1: version "1.0.2" @@ -5594,26 +5326,11 @@ object-inspect@^1.11.0, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== -object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -5624,14 +5341,6 @@ object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" -object.getownpropertydescriptors@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - object.values@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" @@ -5741,7 +5450,7 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0: +p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -5769,12 +5478,12 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: - p-limit "^2.0.0" + p-limit "^2.2.0" p-locate@^5.0.0: version "5.0.0" @@ -5795,6 +5504,14 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" +p-queue@6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + p-timeout@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" @@ -5967,28 +5684,26 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== -picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== - pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pkg-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" - integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= - dependencies: - find-up "^2.1.0" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== -pngjs@^3.3.0, pngjs@^3.3.1: +pngjs@^3.3.1: version "3.4.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + portscanner@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.2.0.tgz#6059189b3efa0965c9d96a56b958eb9508411cf1" @@ -5997,274 +5712,6 @@ portscanner@2.2.0: async "^2.6.0" is-number-like "^1.0.3" -postcss-calc@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a" - integrity sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g== - dependencies: - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" - -postcss-colormin@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.1.tgz#6e444a806fd3c578827dbad022762df19334414d" - integrity sha512-VVwMrEYLcHYePUYV99Ymuoi7WhKrMGy/V9/kTS0DkCoJYmmjdOMneyhzYUxcNgteKDVbrewOkSM7Wje/MFwxzA== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - colord "^2.9.1" - postcss-value-parser "^4.1.0" - -postcss-convert-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.0.2.tgz#879b849dc3677c7d6bc94b6a2c1a3f0808798059" - integrity sha512-KQ04E2yadmfa1LqXm7UIDwW1ftxU/QWZmz6NKnHnUvJ3LEYbbcX6i329f/ig+WnEByHegulocXrECaZGLpL8Zg== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-discard-comments@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz#9eae4b747cf760d31f2447c27f0619d5718901fe" - integrity sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg== - -postcss-discard-duplicates@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz#68f7cc6458fe6bab2e46c9f55ae52869f680e66d" - integrity sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA== - -postcss-discard-empty@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz#ee136c39e27d5d2ed4da0ee5ed02bc8a9f8bf6d8" - integrity sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw== - -postcss-discard-overridden@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6" - integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q== - -postcss-merge-longhand@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.3.tgz#42194a5ffbaa5513edbf606ef79c44958564658b" - integrity sha512-kmB+1TjMTj/bPw6MCDUiqSA5e/x4fvLffiAdthra3a0m2/IjTrWsTmD3FdSskzUjEwkj5ZHBDEbv5dOcqD7CMQ== - dependencies: - css-color-names "^1.0.1" - postcss-value-parser "^4.1.0" - stylehacks "^5.0.1" - -postcss-merge-rules@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.2.tgz#d6e4d65018badbdb7dcc789c4f39b941305d410a" - integrity sha512-5K+Md7S3GwBewfB4rjDeol6V/RZ8S+v4B66Zk2gChRqLTCC8yjnHQ601omj9TKftS19OPGqZ/XzoqpzNQQLwbg== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - cssnano-utils "^2.0.1" - postcss-selector-parser "^6.0.5" - vendors "^1.0.3" - -postcss-minify-font-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz#a90cefbfdaa075bd3dbaa1b33588bb4dc268addf" - integrity sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-minify-gradients@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz#f970a11cc71e08e9095e78ec3a6b34b91c19550e" - integrity sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q== - dependencies: - colord "^2.9.1" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-minify-params@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.1.tgz#371153ba164b9d8562842fdcd929c98abd9e5b6c" - integrity sha512-4RUC4k2A/Q9mGco1Z8ODc7h+A0z7L7X2ypO1B6V8057eVK6mZ6xwz6QN64nHuHLbqbclkX1wyzRnIrdZehTEHw== - dependencies: - alphanum-sort "^1.0.2" - browserslist "^4.16.0" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - uniqs "^2.0.0" - -postcss-minify-selectors@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz#4385c845d3979ff160291774523ffa54eafd5a54" - integrity sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og== - dependencies: - alphanum-sort "^1.0.2" - postcss-selector-parser "^6.0.5" - -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-normalize-charset@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz#121559d1bebc55ac8d24af37f67bd4da9efd91d0" - integrity sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg== - -postcss-normalize-display-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz#62650b965981a955dffee83363453db82f6ad1fd" - integrity sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-normalize-positions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz#868f6af1795fdfa86fbbe960dceb47e5f9492fe5" - integrity sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-normalize-repeat-style@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz#cbc0de1383b57f5bb61ddd6a84653b5e8665b2b5" - integrity sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-normalize-string@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz#d9eafaa4df78c7a3b973ae346ef0e47c554985b0" - integrity sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-normalize-timing-functions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz#8ee41103b9130429c6cbba736932b75c5e2cb08c" - integrity sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-normalize-unicode@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz#82d672d648a411814aa5bf3ae565379ccd9f5e37" - integrity sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA== - dependencies: - browserslist "^4.16.0" - postcss-value-parser "^4.1.0" - -postcss-normalize-url@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.2.tgz#ddcdfb7cede1270740cf3e4dfc6008bd96abc763" - integrity sha512-k4jLTPUxREQ5bpajFQZpx8bCF2UrlqOTzP9kEqcEnOfwsRshWs2+oAFIHfDQB8GO2PaUaSE0NlTAYtbluZTlHQ== - dependencies: - is-absolute-url "^3.0.3" - normalize-url "^6.0.1" - postcss-value-parser "^4.1.0" - -postcss-normalize-whitespace@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz#b0b40b5bcac83585ff07ead2daf2dcfbeeef8e9a" - integrity sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-ordered-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz#1f351426977be00e0f765b3164ad753dac8ed044" - integrity sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-reduce-initial@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.1.tgz#9d6369865b0f6f6f6b165a0ef5dc1a4856c7e946" - integrity sha512-zlCZPKLLTMAqA3ZWH57HlbCjkD55LX9dsRyxlls+wfuRfqCi5mSlZVan0heX5cHr154Dq9AfbH70LyhrSAezJw== - dependencies: - browserslist "^4.16.0" - caniuse-api "^3.0.0" - -postcss-reduce-transforms@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz#93c12f6a159474aa711d5269923e2383cedcf640" - integrity sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" - integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - util-deprecate "^1.0.2" - -postcss-selector-parser@^6.0.5: - version "6.0.6" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" - integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-svgo@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.0.3.tgz#d945185756e5dfaae07f9edb0d3cae7ff79f9b30" - integrity sha512-41XZUA1wNDAZrQ3XgWREL/M2zSw8LJPvb5ZWivljBsUQAGoEKMYm6okHsTjJxKYI4M75RQEH4KYlEM52VwdXVA== - dependencies: - postcss-value-parser "^4.1.0" - svgo "^2.7.0" - -postcss-unique-selectors@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.0.1.tgz#3be5c1d7363352eff838bd62b0b07a0abad43bfc" - integrity sha512-gwi1NhHV4FMmPn+qwBNuot1sG1t2OmacLQ/AX29lzyggnjd+MnVD5uqQmpXO3J17KGL2WAxQruj1qTd3H0gG/w== - dependencies: - alphanum-sort "^1.0.2" - postcss-selector-parser "^6.0.5" - uniqs "^2.0.0" - -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== - -postcss@^8.2.15: - version "8.3.0" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.0.tgz#b1a713f6172ca427e3f05ef1303de8b65683325f" - integrity sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ== - dependencies: - colorette "^1.2.2" - nanoid "^3.1.23" - source-map-js "^0.6.2" - postcss@^8.3.11: version "8.3.11" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.11.tgz#c3beca7ea811cd5e1c4a3ec6d2e7599ef1f8f858" @@ -6296,10 +5743,10 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prebuild-install@^6.1.4: - version "6.1.4" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.1.4.tgz#ae3c0142ad611d58570b89af4986088a4937e00f" - integrity sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ== +prebuild-install@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.0.tgz#3c5ce3902f1cb9d6de5ae94ca53575e4af0c1574" + integrity sha512-IvSenf33K7JcgddNz2D5w521EgO+4aMMjFt73Uk9FRzQ7P+QZPKrp7qPsDydsSwjGt3T5xRNnM1bj1zMTD5fTA== dependencies: detect-libc "^1.0.3" expand-template "^2.0.3" @@ -6307,11 +5754,11 @@ prebuild-install@^6.1.4: minimist "^1.2.3" mkdirp-classic "^0.5.3" napi-build-utils "^1.0.1" - node-abi "^2.21.0" + node-abi "^3.3.0" npmlog "^4.0.1" pump "^3.0.0" rc "^1.2.7" - simple-get "^3.0.3" + simple-get "^4.0.0" tar-fs "^2.0.0" tunnel-agent "^0.6.0" @@ -6339,11 +5786,6 @@ printj@~1.1.0: resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== -prismjs@1.25.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" - integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== - private-ip@2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/private-ip/-/private-ip-2.3.3.tgz#1e80ff8443e5ac78f555631aec3ea6ff027fa6aa" @@ -6354,10 +5796,10 @@ private-ip@2.3.3: is-ip "^3.1.0" netmask "^2.0.2" -probe-image-size@7.2.1: - version "7.2.1" - resolved "https://registry.yarnpkg.com/probe-image-size/-/probe-image-size-7.2.1.tgz#df0c924e67e247bc94f8fcb0fad7f0081061fc44" - integrity sha512-d+6L3NvQBCNt4peRDoEfA7r9bPm6/qy18FnLKwg4NWBC5JrJm0pMLRg1kF4XNsPe1bUdt3WIMonPJzQWN2HXjQ== +probe-image-size@7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/probe-image-size/-/probe-image-size-7.2.2.tgz#e5851b9be7864f21e3bac5e6e4fac9da9055b412" + integrity sha512-QUm+w1S9WTsT5GZB830u0BHExrUmF0J4fyRm5kbLUMEP3fl9UVYXc3xOBVqZNnH9tnvVEJO8vDk3PMtsLqjxug== dependencies: lodash.merge "^4.6.2" needle "^2.5.2" @@ -6368,11 +5810,6 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -6391,15 +5828,6 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -promise.prototype.finally@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz#b8af89160c9c673cefe3b4c4435b53cfd0287067" - integrity sha512-A2HuJWl2opDH0EafgdjwEw7HysI8ff/n4lW4QEVBCUXFk9QeGecBWv0Deph0UmLe3tTNYegz8MOjsVuE6SMoJA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.0" - function-bind "^1.1.1" - promise@^7.0.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -6543,10 +5971,10 @@ punycode@2.1.1, punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pureimage@0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/pureimage/-/pureimage-0.3.5.tgz#cd5e91f7b6409fcf4880297aaa3e7fc0afc24d5e" - integrity sha512-+CFUEpoX6GemlKlHihI7Ii4IqKqF5KZjd682sAxwzbc4t4zU4Gwhxd4W3UMZW94nJzf0n4nA9zJrwTR4jZB4TA== +pureimage@0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/pureimage/-/pureimage-0.3.8.tgz#b9c2a127f3182ab94fb4520e83f4fbcbdd9b38f1" + integrity sha512-+CuR0HM0VmBfKKQTM56myBonDZAhZkS6ymJ8W5oYYDXG7y7X34B/dEH3UesbJI497Vc2OkA+g8T1/Xj/FTyQ8A== dependencies: jpeg-js "^0.4.1" opentype.js "^0.4.3" @@ -6557,18 +5985,15 @@ q@1.4.1: resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" integrity sha1-VXBbzZPF82c1MMLCy8DCs63cKG4= -qrcode@1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83" - integrity sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q== +qrcode@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b" + integrity sha512-9MgRpgVc+/+47dFvQeD6U2s0Z92EsKzcHogtum4QB+UNd025WOJSHvn/hjk9xmzj7Stj95CyUAs31mrjxliEsQ== dependencies: - buffer "^5.4.3" - buffer-alloc "^1.2.0" - buffer-from "^1.1.1" dijkstrajs "^1.0.1" - isarray "^2.0.1" - pngjs "^3.3.0" - yargs "^13.2.4" + encode-utf8 "^1.0.3" + pngjs "^5.0.0" + yargs "^15.3.1" qs@^6.4.0, qs@^6.5.2: version "6.9.3" @@ -6636,14 +6061,14 @@ rdf-canonize@^3.0.0: dependencies: setimmediate "^1.0.5" -re2@1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/re2/-/re2-1.16.0.tgz#f311eb4865b1296123800ea8e013cec8dab25590" - integrity sha512-eizTZL2ZO0ZseLqfD4t3Qd0M3b3Nr0MBWpX81EbPMIud/1d/CSfUIx2GQK8fWiAeHoSekO5EOeFib2udTZLwYw== +re2@1.17.3: + version "1.17.3" + resolved "https://registry.yarnpkg.com/re2/-/re2-1.17.3.tgz#8cceb48f52c45b860b1f67cee8a44726f7d05e9a" + integrity sha512-Dp5iWVR8W3C7Nm9DziMY4BleMPRb/pe6kvfbzLv80dVYaXRc9jRnwwNqU0oE/taRm0qYR1+Qrtzk9rPjS9ecaQ== dependencies: - install-artifact-from-github "^1.2.0" - nan "^2.14.2" - node-gyp "^8.0.0" + install-artifact-from-github "^1.3.0" + nan "^2.15.0" + node-gyp "^8.4.1" readable-stream@1.1.x: version "1.1.14" @@ -6719,13 +6144,24 @@ redis-lock@0.1.4: resolved "https://registry.yarnpkg.com/redis-lock/-/redis-lock-0.1.4.tgz#e83590bee22b5f01cdb65bfbd88d988045356272" integrity sha512-7/+zu86XVQfJVx1nHTzux5reglDiyUCDwmW7TSlvVezfhH2YLc/Rc8NE0ejQG+8/0lwKzm29/u/4+ogKeLosiA== -redis-parser@^3.0.0: +redis-parser@3.0.0, redis-parser@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= dependencies: redis-errors "^1.0.0" +redis@*: + version "4.0.2" + resolved "https://registry.yarnpkg.com/redis/-/redis-4.0.2.tgz#096cf716842731a24f34c7c3a996c143e2b133bb" + integrity sha512-Ip1DJ/lwuvtJz9AZ6pl1Bv33fWzk5d3iQpGzsXpi04ErkT4fq0pfGOm4k/p9DHmPGieEIOWvJ9xmIeQMooLybg== + dependencies: + "@node-redis/bloom" "^1.0.0" + "@node-redis/client" "^1.0.2" + "@node-redis/json" "^1.0.2" + "@node-redis/search" "^1.0.2" + "@node-redis/time-series" "^1.0.1" + redis@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/redis/-/redis-3.1.2.tgz#766851117e80653d23e0ed536254677ab647638c" @@ -6869,7 +6305,7 @@ s-age@1.1.2: resolved "https://registry.yarnpkg.com/s-age/-/s-age-1.1.2.tgz#c0cf15233ccc93f41de92ea42c36d957977d1ea2" integrity sha512-aSN2TlF39WLoZA/6cgYSJZhKt63kJ4EaadejPWjWY9/h4rksIqvfWY3gfd+3uAegSM1IXsA9aWeEhJtkxkFQtA== -safe-buffer@*: +safe-buffer@*, safe-buffer@5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -6889,10 +6325,10 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sanitize-html@2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.5.3.tgz#91aa3dc760b072cdf92f9c6973747569b1ba1cd8" - integrity sha512-DGATXd1fs/Rm287/i5FBKVYSBBUL0iAaztOA1/RFhEs4yqo39/X52i/q/CwsfCUG5cilmXSBmnQmyWfnKhBlOg== +sanitize-html@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.6.1.tgz#5d37c08e189c61c0631560a889b10d9d155d000e" + integrity sha512-DzjSz3H5qDntD7s1TcWCSoRPmNR8UmA+y+xZQOvWgjATe2Br9ZW73+vD3Pj6Snrg0RuEuJdXgrKvnYuiuixRkA== dependencies: deepmerge "^4.2.2" escape-string-regexp "^4.0.0" @@ -6942,17 +6378,12 @@ seedrandom@3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -semver@6.x: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^5.4.1, semver@^5.6.0: +semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: +semver@^7.3.2, semver@^7.3.4: version "7.3.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== @@ -7001,17 +6432,17 @@ sha.js@^2.4.11: inherits "^2.0.1" safe-buffer "^5.0.1" -sharp@0.29.2: - version "0.29.2" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.2.tgz#e8c003cd9cb321585b32dbda6eed3baa7d6f2308" - integrity sha512-XWRdiYLIJ3tDUejRyG24KERnJzMfIoyiJBntd2S6/uj3NEeNgRFRLgiBlvPxMa8aml14dKKD98yHinSNKp1xzQ== +sharp@0.29.3: + version "0.29.3" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.3.tgz#0da183d626094c974516a48fab9b3e4ba92eb5c2" + integrity sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA== dependencies: color "^4.0.1" detect-libc "^1.0.3" node-addon-api "^4.2.0" - prebuild-install "^6.1.4" + prebuild-install "^7.0.0" semver "^7.3.5" - simple-get "^3.1.0" + simple-get "^4.0.0" tar-fs "^2.1.1" tunnel-agent "^0.6.0" @@ -7056,12 +6487,12 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= -simple-get@^3.0.3, simple-get@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" - integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== +simple-get@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675" + integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ== dependencies: - decompress-response "^4.2.0" + decompress-response "^6.0.0" once "^1.3.1" simple-concat "^1.0.0" @@ -7082,16 +6513,16 @@ smart-buffer@^4.1.0: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== -socks-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz#7c0f364e7b1cf4a7a437e71253bed72e9004be60" - integrity sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA== +socks-proxy-agent@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz#e664e8f1aaf4e1fb3df945f09e3d94f911137f87" + integrity sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew== dependencies: - agent-base "6" - debug "4" - socks "^2.3.3" + agent-base "^6.0.2" + debug "^4.3.1" + socks "^2.6.1" -socks@^2.3.3: +socks@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== @@ -7146,11 +6577,6 @@ sprintf-js@1.1.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - sshpk@^1.14.1: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -7173,11 +6599,6 @@ ssri@^8.0.0, ssri@^8.0.1: dependencies: minipass "^3.1.1" -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - standard-as-callback@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" @@ -7222,14 +6643,14 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" string-width@^4.1.0, string-width@^4.2.0: version "4.2.0" @@ -7240,14 +6661,6 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.trimend@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimend@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" @@ -7256,32 +6669,6 @@ string.prototype.trimend@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -string.prototype.trimleft@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" - integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimstart "^1.0.0" - -string.prototype.trimright@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" - integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimend "^1.0.0" - -string.prototype.trimstart@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimstart@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" @@ -7330,13 +6717,6 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - strip-ansi@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" @@ -7384,14 +6764,6 @@ style-loader@3.3.1: resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== -stylehacks@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.0.1.tgz#323ec554198520986806388c7fdaebc38d2c06fb" - integrity sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA== - dependencies: - browserslist "^4.16.0" - postcss-selector-parser "^6.0.4" - summaly@2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/summaly/-/summaly-2.5.0.tgz#ec5af6e84857efcb6c844d896e83569e64a923ea" @@ -7429,19 +6801,6 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -svgo@^2.7.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" - symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -7518,10 +6877,10 @@ tar@^6.0.2: mkdirp "^1.0.3" yallist "^4.0.0" -tar@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" - integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== +tar@^6.1.2: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -7580,11 +6939,6 @@ through@2: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" - integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= - tinycolor2@1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" @@ -7653,22 +7007,6 @@ trace-redirect@1.0.6: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= -ts-jest@^25.2.1: - version "25.5.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.5.1.tgz#2913afd08f28385d54f2f4e828be4d261f4337c7" - integrity sha512-kHEUlZMK8fn8vkxDjwbHlxXRB9dHYpyzqKIGDNxbzs+Rz+ssNDSDNusEK8Fk/sDd4xE6iKoQLfFkFVaskmTJyw== - dependencies: - bs-logger "0.x" - buffer-from "1.x" - fast-json-stable-stringify "2.x" - json5 "2.x" - lodash.memoize "4.x" - make-error "1.x" - micromatch "4.x" - mkdirp "0.x" - semver "6.x" - yargs-parser "18.x" - ts-loader@9.2.6: version "9.2.6" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.6.tgz#9937c4dd0a1e3dbbb5e433f8102a6601c6615d74" @@ -7709,10 +7047,10 @@ tsc-alias@1.4.1: mylas "^2.1.4" normalize-path "^3.0.0" -tsconfig-paths@3.11.0, tsconfig-paths@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" - integrity sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA== +tsconfig-paths@3.12.0, tsconfig-paths@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" + integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== dependencies: "@types/json5" "^0.0.29" json5 "^1.0.1" @@ -7812,10 +7150,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typeorm@0.2.39: - version "0.2.39" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.39.tgz#4d22fc68d114b2ca88a8d7b064f31af15e836ade" - integrity sha512-yQdvDpmmmn8wp1We25V76KIBPYR/lDbymNbGC++Uq8mSRhpHIPnlg26VAT4CF6Ypqx72zn8eqr+/72uSo7HdJQ== +typeorm@0.2.41: + version "0.2.41" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.41.tgz#88758101ac158dc0a0a903d70eaacea2974281cc" + integrity sha512-/d8CLJJxKPgsnrZWiMyPI0rz2MFZnBQrnQ5XP3Vu3mswv2WPexb58QM6BEtmRmlTMYN5KFWUz8SKluze+wS9xw== dependencies: "@sqltools/formatter" "^1.2.2" app-root-path "^3.0.0" @@ -7834,10 +7172,10 @@ typeorm@0.2.39: yargs "^17.0.1" zen-observable-ts "^1.0.0" -typescript@4.4.4: - version "4.4.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" - integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== +typescript@4.5.5: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== ulid@2.3.0: version "2.3.0" @@ -7854,16 +7192,6 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" - integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= - unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -7939,21 +7267,11 @@ utf-8-validate@^5.0.2: dependencies: node-gyp-build "~3.7.0" -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util.promisify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" - integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.0" - uuid@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" @@ -7979,11 +7297,6 @@ vary@^1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= -vendors@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" - integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -7998,19 +7311,6 @@ void-elements@^3.1.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk= -vue-eslint-parser@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-8.0.1.tgz#25e08b20a414551531f3e19f999902e1ecf45f13" - integrity sha512-lhWjDXJhe3UZw2uu3ztX51SJAPGPey1Tff2RK3TyZURwbuI4vximQLzz4nQfCv8CZq4xx7uIiogHMMoSJPr33A== - dependencies: - debug "^4.3.2" - eslint-scope "^6.0.0" - eslint-visitor-keys "^3.0.0" - espree "^9.0.0" - esquery "^1.4.0" - lodash "^4.17.21" - semver "^7.3.5" - w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" @@ -8045,6 +7345,11 @@ web-push@3.4.5: minimist "^1.2.5" urlsafe-base64 "^1.0.0" +web-streams-polyfill@^3.0.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965" + integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA== + webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" @@ -8171,6 +7476,13 @@ wide-align@1.1.3, wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + with@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac" @@ -8191,14 +7503,14 @@ workerpool@6.1.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" wrap-ansi@^7.0.0: version "7.0.0" @@ -8214,10 +7526,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@8.2.3: - version "8.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" - integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== +ws@8.4.2: + version "8.4.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" + integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA== ws@^7.4.6: version "7.5.3" @@ -8292,43 +7604,30 @@ yaeti@^0.0.6: resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc= +yallist@4.0.0, yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - yaml-ast-parser@0.0.43: version "0.0.43" resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz#e8a23e6fb4c38076ab92995c5dca33f3d3d7c9bb" integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A== -yaml@^1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yargs-parser@18.x: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - yargs-parser@20.2.4, yargs-parser@^20.2.2: version "20.2.4" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" @@ -8356,21 +7655,22 @@ yargs@16.2.0, yargs@^16.0.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^13.2.4: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== dependencies: - cliui "^5.0.0" - find-up "^3.0.0" + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" get-caller-file "^2.0.1" require-directory "^2.1.1" require-main-filename "^2.0.0" set-blocking "^2.0.0" - string-width "^3.0.0" + string-width "^4.2.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^13.1.2" + yargs-parser "^18.1.2" yargs@^17.0.1: version "17.1.1" diff --git a/packages/client/package.json b/packages/client/package.json index e105ffcae7..e03e9edc01 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -13,7 +13,7 @@ "@discordapp/twemoji": "13.1.0", "@syuilo/aiscript": "0.11.1", "@types/dateformat": "3.0.1", - "@types/escape-regexp": "0.0.0", + "@types/escape-regexp": "0.0.1", "@types/glob": "7.2.0", "@types/gulp": "4.0.9", "@types/gulp-rename": "2.0.1", @@ -31,14 +31,14 @@ "@types/throttle-debounce": "2.1.0", "@types/tinycolor2": "1.4.3", "@types/tmp": "0.2.3", - "@types/uuid": "8.3.3", + "@types/uuid": "8.3.4", "@types/web-push": "3.3.2", "@types/webpack": "5.28.0", "@types/webpack-stream": "3.2.12", "@types/websocket": "1.0.4", "@types/ws": "8.2.2", - "@typescript-eslint/parser": "5.8.1", - "@vue/compiler-sfc": "3.2.26", + "@typescript-eslint/parser": "5.10.0", + "@vue/compiler-sfc": "3.2.27", "abort-controller": "3.0.0", "autobind-decorator": "2.4.0", "autosize": "5.0.1", @@ -48,28 +48,28 @@ "chart.js": "3.7.0", "chartjs-adapter-date-fns": "2.0.0", "chartjs-plugin-zoom": "1.2.0", - "compare-versions": "4.1.2", + "compare-versions": "4.1.3", "content-disposition": "0.5.4", "crc-32": "1.2.0", "css-loader": "6.5.1", - "cssnano": "5.0.14", + "cssnano": "5.0.15", "date-fns": "2.28.0", "escape-regexp": "0.0.1", - "eslint": "8.6.0", - "eslint-plugin-vue": "8.2.0", + "eslint": "8.7.0", + "eslint-plugin-vue": "8.3.0", "eventemitter3": "4.0.7", "feed": "4.2.2", "glob": "7.2.0", - "idb-keyval": "6.0.3", + "idb-keyval": "6.1.0", "insert-text-at-cursor": "0.3.0", "ip-cidr": "3.0.4", "json5": "2.2.0", "json5-loader": "4.0.1", - "katex": "0.15.1", + "katex": "0.15.2", "langmap": "0.0.16", "matter-js": "0.18.0", - "mfm-js": "0.20.0", - "misskey-js": "0.0.12", + "mfm-js": "0.21.0", + "misskey-js": "0.0.13", "mocha": "8.4.0", "ms": "2.1.3", "nested-property": "4.0.0", @@ -78,7 +78,7 @@ "portscanner": "2.2.0", "postcss": "8.4.5", "postcss-loader": "6.2.1", - "prismjs": "1.25.0", + "prismjs": "1.26.0", "private-ip": "2.3.3", "promise-limit": "2.7.0", "pug": "3.0.2", @@ -90,7 +90,7 @@ "request-stats": "3.0.0", "rndstr": "1.0.0", "s-age": "1.1.2", - "sass": "1.45.2", + "sass": "1.49.0", "sass-loader": "12.4.0", "seedrandom": "3.0.5", "strict-event-emitter-types": "2.0.0", @@ -98,7 +98,7 @@ "style-loader": "3.3.1", "syuilo-password-strength": "0.0.1", "textarea-caret": "3.1.0", - "three": "0.117.1", + "three": "0.136.0", "throttle-debounce": "3.0.1", "tinycolor2": "1.4.2", "tmp": "0.2.1", @@ -107,11 +107,11 @@ "tsc-alias": "1.5.0", "tsconfig-paths": "3.12.0", "twemoji-parser": "13.1.0", - "typescript": "4.5.4", + "typescript": "4.5.5", "uuid": "8.3.2", "v-debounce": "0.1.2", "vanilla-tilt": "1.7.2", - "vue": "3.2.26", + "vue": "3.2.27", "vue-loader": "17.0.0", "vue-prism-editor": "2.0.0-alpha.2", "vue-router": "4.0.5", @@ -119,18 +119,18 @@ "vue-svg-loader": "0.17.0-beta.2", "vuedraggable": "4.0.1", "web-push": "3.4.5", - "webpack": "5.65.0", + "webpack": "5.66.0", "webpack-cli": "4.9.1", "websocket": "1.0.34", - "ws": "8.4.0" + "ws": "8.4.2" }, "devDependencies": { - "@redocly/openapi-core": "1.0.0-beta.54", - "@types/fluent-ffmpeg": "2.1.17", - "@typescript-eslint/eslint-plugin": "5.8.1", + "@redocly/openapi-core": "1.0.0-beta.79", + "@types/fluent-ffmpeg": "2.1.20", + "@typescript-eslint/eslint-plugin": "5.10.0", "cross-env": "7.0.3", - "cypress": "9.2.0", - "eslint-plugin-import": "2.25.3", + "cypress": "9.3.1", + "eslint-plugin-import": "2.25.4", "start-server-and-test": "1.14.0" } } diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index 0d08448ded..07d1dbf9df 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -177,29 +177,30 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" -"@redocly/ajv@^8.6.2": - version "8.6.2" - resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.2.tgz#8c4e485e72f7864f91fae40093bed548ec2619b2" - integrity sha512-tU8fQs0D76ZKhJ2cWtnfQthWqiZgGBx0gH0+5D8JvaBEBaqA8foPPBt3Nonwr3ygyv5xrw2IzKWgIY86BlGs+w== +"@redocly/ajv@^8.6.4": + version "8.6.4" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.4.tgz#94053e7a9d4146d1a4feacd3813892873f229a85" + integrity sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -"@redocly/openapi-core@1.0.0-beta.54": - version "1.0.0-beta.54" - resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.54.tgz#42575a849c4dd54b9d0c6413fb8ca547e087cd11" - integrity sha512-uYs0N1Trjkh7u8IMIuCU2VxCXhMyGWSZUkP/WNdTR1OgBUtvNdF9C32zoQV+hyCIH4gVu42ROHkjisy333ZX+w== +"@redocly/openapi-core@1.0.0-beta.79": + version "1.0.0-beta.79" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.79.tgz#7512b3507ab99dc78226f9069669c5302abb0969" + integrity sha512-do79vGt3iiHsaVG9LKY8dH+d1E7TLHr+3T+CQ1lqagtWVjYOxqGaoxAT8tRD7R1W0z8BmS4e2poNON6c1sxP5g== dependencies: - "@redocly/ajv" "^8.6.2" + "@redocly/ajv" "^8.6.4" "@types/node" "^14.11.8" colorette "^1.2.0" js-levenshtein "^1.1.6" - js-yaml "^3.14.1" + js-yaml "^4.1.0" lodash.isequal "^4.5.0" minimatch "^3.0.4" node-fetch "^2.6.1" + pluralize "^8.0.0" yaml-ast-parser "0.0.43" "@sideway/address@^4.1.0": @@ -270,10 +271,10 @@ resolved "https://registry.yarnpkg.com/@types/dateformat/-/dateformat-3.0.1.tgz#98d747a2e5e9a56070c6bf14e27bff56204e34cc" integrity sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g== -"@types/escape-regexp@0.0.0": - version "0.0.0" - resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.0.tgz#bff0225f9ef30d0dbdbe0e2a24283ee5342990c3" - integrity sha512-HTansGo4tJ7K7W9I9LBdQqnHtPB/Y7tlS+EMrkboaAQLsRPhRpHaqAHe01K1HVXM5e1u1IplRd8EBh+pJrp7Dg== +"@types/escape-regexp@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.1.tgz#f1a977ccdf2ef059e9862bd3af5e92cbbe723e0e" + integrity sha512-ogj/ZTIdeFkiuxDwawYuZSIgC6suFGgBeZPr6Xs5lHEcvIXTjXGtH+/n8f1XhZhespaUwJ5LIGRICPji972FLw== "@types/eslint-scope@^3.7.0": version "3.7.0" @@ -311,10 +312,10 @@ resolved "https://registry.yarnpkg.com/@types/expect/-/expect-1.20.4.tgz#8288e51737bf7e3ab5d7c77bfa695883745264e5" integrity sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg== -"@types/fluent-ffmpeg@2.1.17": - version "2.1.17" - resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.17.tgz#6958dda400fe1b33c21f3683db76905cb210d053" - integrity sha512-/bdvjKw/mtBHlJ2370d04nt4CsWqU5MrwS/NtO96V01jxitJ4+iq8OFNcqc5CegeV3TQOK3uueK02kvRK+zjUg== +"@types/fluent-ffmpeg@2.1.20": + version "2.1.20" + resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.20.tgz#3b5f42fc8263761d58284fa46ee6759a64ce54ac" + integrity sha512-B+OvhCdJ3LgEq2PhvWNOiB/EfwnXLElfMCgc4Z1K5zXgSfo9I6uGKwR/lqmNPFQuebNnes7re3gqkV77SyypLg== dependencies: "@types/node" "*" @@ -471,10 +472,10 @@ resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.28.tgz#9ce8fa048c1e8c85cb71d7fe4d704e000226036f" integrity sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA== -"@types/sinonjs__fake-timers@^6.0.2": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.3.tgz#79df6f358ae8f79e628fe35a63608a0ea8e7cf08" - integrity sha512-E1dU4fzC9wN2QK2Cr1MLCfyHM8BoNnRFvuf45LYMPNDA+WqbNzC45S4UzPxvp1fFJ1rvSGU0bPvdd35VLmXG8g== +"@types/sinonjs__fake-timers@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" + integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== "@types/sizzle@^2.3.2": version "2.3.3" @@ -525,10 +526,10 @@ dependencies: "@types/undertaker-registry" "*" -"@types/uuid@8.3.3": - version "8.3.3" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.3.tgz#c6a60686d953dbd1b1d45e66f4ecdbd5d471b4d0" - integrity sha512-0LbEEx1zxrYB3pgpd1M5lEhLcXjKJnYghvhTRgaBeUivLHMDM1TzF3IJ6hXU2+8uA4Xz+5BA63mtZo5DjVT8iA== +"@types/uuid@8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/vinyl-fs@*": version "2.4.11" @@ -613,13 +614,14 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.8.1.tgz#97dfaa39f38e99f86801fdf34f9f1bed66704258" - integrity sha512-wTZ5oEKrKj/8/366qTM366zqhIKAp6NCMweoRONtfuC07OAU9nVI2GZZdqQ1qD30WAAtcPdkH+npDwtRFdp4Rw== +"@typescript-eslint/eslint-plugin@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.0.tgz#e90afea96dff8620892ad216b0e4ccdf8ee32d3a" + integrity sha512-XXVKnMsq2fuu9K2KsIxPUGqb6xAImz8MEChClbXmE3VbveFtBUU5bzM6IPVWqzyADIgdkS2Ws/6Xo7W2TeZWjQ== dependencies: - "@typescript-eslint/experimental-utils" "5.8.1" - "@typescript-eslint/scope-manager" "5.8.1" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/type-utils" "5.10.0" + "@typescript-eslint/utils" "5.10.0" debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" @@ -627,60 +629,69 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.8.1.tgz#01861eb2f0749f07d02db342b794145a66ed346f" - integrity sha512-fbodVnjIDU4JpeXWRDsG5IfIjYBxEvs8EBO8W1+YVdtrc2B9ppfof5sZhVEDOtgTfFHnYQJDI8+qdqLYO4ceww== +"@typescript-eslint/parser@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.10.0.tgz#8f59e036f5f1cffc178cacbd5ccdd02aeb96c91c" + integrity sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw== dependencies: - "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.8.1" - "@typescript-eslint/types" "5.8.1" - "@typescript-eslint/typescript-estree" "5.8.1" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - -"@typescript-eslint/parser@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.8.1.tgz#380f5f1e596b540059998aa3fc80d78f0f9b0d0a" - integrity sha512-K1giKHAjHuyB421SoXMXFHHVI4NdNY603uKw92++D3qyxSeYvC10CBJ/GE5Thpo4WTUvu1mmJI2/FFkz38F2Gw== - dependencies: - "@typescript-eslint/scope-manager" "5.8.1" - "@typescript-eslint/types" "5.8.1" - "@typescript-eslint/typescript-estree" "5.8.1" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" debug "^4.3.2" -"@typescript-eslint/scope-manager@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.8.1.tgz#7fc0604f7ade8833e4d42cebaa1e2debf8b932e4" - integrity sha512-DGxJkNyYruFH3NIZc3PwrzwOQAg7vvgsHsHCILOLvUpupgkwDZdNq/cXU3BjF4LNrCsVg0qxEyWasys5AiJ85Q== +"@typescript-eslint/scope-manager@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz#bb5d872e8b9e36203908595507fbc4d3105329cb" + integrity sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg== dependencies: - "@typescript-eslint/types" "5.8.1" - "@typescript-eslint/visitor-keys" "5.8.1" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" -"@typescript-eslint/types@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.8.1.tgz#04c6b49ebc8c99238238a6b8b43f2fc613983b5a" - integrity sha512-L/FlWCCgnjKOLefdok90/pqInkomLnAcF9UAzNr+DSqMC3IffzumHTQTrINXhP1gVp9zlHiYYjvozVZDPleLcA== - -"@typescript-eslint/typescript-estree@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.8.1.tgz#a592855be688e7b729a1e9411d7d74ec992ed6ef" - integrity sha512-26lQ8l8tTbG7ri7xEcCFT9ijU5Fk+sx/KRRyyzCv7MQ+rZZlqiDPtMKWLC8P7o+dtCnby4c+OlxuX1tp8WfafQ== +"@typescript-eslint/type-utils@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz#8524b9479c19c478347a7df216827e749e4a51e5" + integrity sha512-TzlyTmufJO5V886N+hTJBGIfnjQDQ32rJYxPaeiyWKdjsv2Ld5l8cbS7pxim4DeNs62fKzRSt8Q14Evs4JnZyQ== dependencies: - "@typescript-eslint/types" "5.8.1" - "@typescript-eslint/visitor-keys" "5.8.1" + "@typescript-eslint/utils" "5.10.0" + debug "^4.3.2" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.0.tgz#beb3cb345076f5b088afe996d57bcd1dfddaa75c" + integrity sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ== + +"@typescript-eslint/typescript-estree@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz#4be24a3dea0f930bb1397c46187d0efdd955a224" + integrity sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA== + dependencies: + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.8.1.tgz#58a2c566265d5511224bc316149890451c1bbab0" - integrity sha512-SWgiWIwocK6NralrJarPZlWdr0hZnj5GXHIgfdm8hNkyKvpeQuFyLP6YjSIe9kf3YBIfU6OHSZLYkQ+smZwtNg== +"@typescript-eslint/utils@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.10.0.tgz#c3d152a85da77c400e37281355561c72fb1b5a65" + integrity sha512-IGYwlt1CVcFoE2ueW4/ioEwybR60RAdGeiJX/iDAw0t5w0wK3S7QncDwpmsM70nKgGTuVchEWB8lwZwHqPAWRg== dependencies: - "@typescript-eslint/types" "5.8.1" + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/visitor-keys@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz#770215497ad67cd15a572b52089991d5dfe06281" + integrity sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ== + dependencies: + "@typescript-eslint/types" "5.10.0" eslint-visitor-keys "^3.0.0" "@ungap/promise-all-settled@1.1.2": @@ -688,95 +699,95 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== -"@vue/compiler-core@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.26.tgz#9ab92ae624da51f7b6064f4679c2d4564f437cc8" - integrity sha512-N5XNBobZbaASdzY9Lga2D9Lul5vdCIOXvUMd6ThcN8zgqQhPKfCV+wfAJNNJKQkSHudnYRO2gEB+lp0iN3g2Tw== +"@vue/compiler-core@3.2.27": + version "3.2.27" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.27.tgz#01bd5e5071f58f43e4184ba7fa810226799a5383" + integrity sha512-JyxAglSM/pb9paG5ZNuKrf5IUpzLzQA3khjWGF9oESELCLQlt6O3YyPMR2A69wIpYWrf5mScZ8YY8TJKOI/1kQ== dependencies: "@babel/parser" "^7.16.4" - "@vue/shared" "3.2.26" + "@vue/shared" "3.2.27" estree-walker "^2.0.2" source-map "^0.6.1" -"@vue/compiler-dom@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.26.tgz#c7a7b55d50a7b7981dd44fc28211df1450482667" - integrity sha512-smBfaOW6mQDxcT3p9TKT6mE22vjxjJL50GFVJiI0chXYGU/xzC05QRGrW3HHVuJrmLTLx5zBhsZ2dIATERbarg== +"@vue/compiler-dom@3.2.27": + version "3.2.27" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.27.tgz#a12163e3f3f1d5ff1969253eba4b4ea3e67bbd0f" + integrity sha512-NyQ7nEbopUBPUMHM4c3FPCbFbnQwptoPjW5Y5qfJ7hfiCNhOuhQsDNqi5JYKBxfpxiFNwjcN9F8t1AsnLrDloQ== dependencies: - "@vue/compiler-core" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-core" "3.2.27" + "@vue/shared" "3.2.27" -"@vue/compiler-sfc@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.26.tgz#3ce76677e4aa58311655a3bea9eb1cb804d2273f" - integrity sha512-ePpnfktV90UcLdsDQUh2JdiTuhV0Skv2iYXxfNMOK/F3Q+2BO0AulcVcfoksOpTJGmhhfosWfMyEaEf0UaWpIw== +"@vue/compiler-sfc@3.2.27": + version "3.2.27" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.27.tgz#43bf23fe24feacba334636fa7a332f9d8a532c8c" + integrity sha512-WyecUhLN5UAQAr2QlmG2nA56OEnhZJaBnSw0G1tazb9rwDuK0V9tnbIXbQgmQlx+x4sJxgg61yWGcIXfilTl3A== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.26" - "@vue/compiler-dom" "3.2.26" - "@vue/compiler-ssr" "3.2.26" - "@vue/reactivity-transform" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-core" "3.2.27" + "@vue/compiler-dom" "3.2.27" + "@vue/compiler-ssr" "3.2.27" + "@vue/reactivity-transform" "3.2.27" + "@vue/shared" "3.2.27" estree-walker "^2.0.2" magic-string "^0.25.7" postcss "^8.1.10" source-map "^0.6.1" -"@vue/compiler-ssr@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.26.tgz#fd049523341fbf4ab5e88e25eef566d862894ba7" - integrity sha512-2mywLX0ODc4Zn8qBoA2PDCsLEZfpUGZcyoFRLSOjyGGK6wDy2/5kyDOWtf0S0UvtoyVq95OTSGIALjZ4k2q/ag== +"@vue/compiler-ssr@3.2.27": + version "3.2.27" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.27.tgz#49aa3afd1602275aba3c3e331764984111a460bf" + integrity sha512-+l09t319iV7HVSrXfBw9OLwMZIPOFTXmHjZ61Bc5ZcwKqOYAR4uTurKpoXAfcSc5qs/q6WdE9jY3nrP0LUEMQQ== dependencies: - "@vue/compiler-dom" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-dom" "3.2.27" + "@vue/shared" "3.2.27" -"@vue/reactivity-transform@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.26.tgz#6d8f20a4aa2d19728f25de99962addbe7c4d03e9" - integrity sha512-XKMyuCmzNA7nvFlYhdKwD78rcnmPb7q46uoR00zkX6yZrUmcCQ5OikiwUEVbvNhL5hBJuvbSO95jB5zkUon+eQ== +"@vue/reactivity-transform@3.2.27": + version "3.2.27" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.27.tgz#610b6df174cdceba6de1376f3218736c3b0e753d" + integrity sha512-67//61ObGxGnVrPhjygocb24eYUh+TFMhkm7szm8v5XdKXjkNl7qgIOflwGvUnwuIRJmr9nZ7+PvY0fL+H2upA== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-core" "3.2.27" + "@vue/shared" "3.2.27" estree-walker "^2.0.2" magic-string "^0.25.7" -"@vue/reactivity@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.26.tgz#d529191e581521c3c12e29ef986d4c8a933a0f83" - integrity sha512-h38bxCZLW6oFJVDlCcAiUKFnXI8xP8d+eO0pcDxx+7dQfSPje2AO6M9S9QO6MrxQB7fGP0DH0dYQ8ksf6hrXKQ== +"@vue/reactivity@3.2.27": + version "3.2.27" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.27.tgz#db7c4eefded938a8974e768b7b8f9ff4fad34a62" + integrity sha512-QPfIQEJidRGIu/mPexhcB4csp1LEg2Nr+/QE72MnXs/OYDtFErhC9FxIyymkxp/xvAgL5wsnSOuDD6zWF42vRQ== dependencies: - "@vue/shared" "3.2.26" + "@vue/shared" "3.2.27" -"@vue/runtime-core@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.26.tgz#5c59cc440ed7a39b6dbd4c02e2d21c8d1988f0de" - integrity sha512-BcYi7qZ9Nn+CJDJrHQ6Zsmxei2hDW0L6AB4vPvUQGBm2fZyC0GXd/4nVbyA2ubmuhctD5RbYY8L+5GUJszv9mQ== +"@vue/runtime-core@3.2.27": + version "3.2.27" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.27.tgz#1ea8acd30071f44bcf3915b088ff5d993a9d3154" + integrity sha512-NJrjuViHJyrT4bwIocbE4XDaDlA1Pj61pQlneZZdFEvgdMLlhzCCiJ4WZnWcohYQeisUAZjEFKK8GjQieDPFbw== dependencies: - "@vue/reactivity" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/reactivity" "3.2.27" + "@vue/shared" "3.2.27" -"@vue/runtime-dom@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.26.tgz#84d3ae2584488747717c2e072d5d9112c0d2e6c2" - integrity sha512-dY56UIiZI+gjc4e8JQBwAifljyexfVCkIAu/WX8snh8vSOt/gMSEGwPRcl2UpYpBYeyExV8WCbgvwWRNt9cHhQ== +"@vue/runtime-dom@3.2.27": + version "3.2.27" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.27.tgz#d13f9f5f27815041f5e9f493d77121955e3f7166" + integrity sha512-tlnKkvBSkV7MPUp/wRFsYcv67U1rUeZTPfpPzq5Kpmw5NNGkY6J075fFBH2k0MNxDucXS+qfStNrxAyGTUMkSA== dependencies: - "@vue/runtime-core" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/runtime-core" "3.2.27" + "@vue/shared" "3.2.27" csstype "^2.6.8" -"@vue/server-renderer@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.26.tgz#f16a4b9fbcc917417b4cea70c99afce2701341cf" - integrity sha512-Jp5SggDUvvUYSBIvYEhy76t4nr1vapY/FIFloWmQzn7UxqaHrrBpbxrqPcTrSgGrcaglj0VBp22BKJNre4aA1w== +"@vue/server-renderer@3.2.27": + version "3.2.27" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.27.tgz#a0caee0f7571fa741e4efaa72951a1cd70d61551" + integrity sha512-dZnzkFCDe6A/GIe/F1LcG6lWpprHVh62DjTv8wubtkHwfJWOmOeHp+KvPDRrswL/L3ghsm+E31xY+pvkgM3pbQ== dependencies: - "@vue/compiler-ssr" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-ssr" "3.2.27" + "@vue/shared" "3.2.27" -"@vue/shared@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.26.tgz#7acd1621783571b9a82eca1f041b4a0a983481d9" - integrity sha512-vPV6Cq+NIWbH5pZu+V+2QHE9y1qfuTq49uNWw4f7FDEeZaDU2H2cx5jcUZOAKW7qTrUS4k6qZPbMy1x4N96nbA== +"@vue/shared@3.2.27": + version "3.2.27" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.27.tgz#d5c6e574ee1afd55043470bf70b15772da4e19a2" + integrity sha512-rpAn9k6O08Lvo7ekBIAnkOukX/4EsEQLPrRJBKhIEasMsOI5eX0f6mq1sDUSY7cgAqWw2d7QtP74CWxdXoyKxA== "@webassemblyjs/ast@1.11.0": version "1.11.0" @@ -1336,6 +1347,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -1363,7 +1379,7 @@ blob-util@^2.0.2: resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== -bluebird@3.7.2: +bluebird@3.7.2, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -1465,6 +1481,14 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + bufferutil@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7" @@ -1618,15 +1642,14 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-table3@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" - integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== +cli-table3@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" + integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== dependencies: - object-assign "^4.1.0" string-width "^4.2.0" optionalDependencies: - colors "^1.1.2" + colors "1.4.0" cli-truncate@^2.1.0: version "2.1.0" @@ -1711,7 +1734,7 @@ colorette@^2.0.14: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.15.tgz#8e634aa0429b110d24be82eac4d42f5ea65ab2d5" integrity sha512-lIFQhufWaVvwi4wOlX9Gx5b0Nmw3XAZ8HzHNH9dfxhe+JaKNTmX6QLk4o7UHyI+tUY8ClvyfaHUm5bf61O3psA== -colors@^1.1.2: +colors@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -1753,10 +1776,10 @@ common-tags@^1.8.0: resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== -compare-versions@4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-4.1.2.tgz#a7b1678c897000d03a70a0e01efee43e7b04dda7" - integrity sha512-LAfbAbAgjnIwPsr2fvJLfrSyqAhK5nj/ffIg7a5aigry9RXJfNzVnOu0Egw8Z+G8LMDu1Qig2q48bpBzjyjZoQ== +compare-versions@4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-4.1.3.tgz#8f7b8966aef7dc4282b45dfa6be98434fc18a1a4" + integrity sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg== concat-map@0.0.1: version "0.0.1" @@ -1909,52 +1932,52 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-default@^5.1.9: - version "5.1.9" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.9.tgz#79628ac48eccbdad570f70b4018cc38d43d1b7df" - integrity sha512-RhkEucqlQ+OxEi14K1p8gdXcMQy1mSpo7P1oC44oRls7BYIj8p+cht4IFBFV3W4iOjTP8EUB33XV1fX9KhDzyA== +cssnano-preset-default@^5.1.10: + version "5.1.10" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.10.tgz#9350765fdf3c49bf78fac7673354fa58fa95daa4" + integrity sha512-BcpSzUVygHMOnp9uG5rfPzTOCb0GAHQkqtUQx8j1oMNF9A1Q8hziOOhiM4bdICpmrBIU85BE64RD5XGYsVQZNA== dependencies: css-declaration-sorter "^6.0.3" - cssnano-utils "^2.0.1" - postcss-calc "^8.0.0" - postcss-colormin "^5.2.2" + cssnano-utils "^3.0.0" + postcss-calc "^8.2.0" + postcss-colormin "^5.2.3" postcss-convert-values "^5.0.2" postcss-discard-comments "^5.0.1" postcss-discard-duplicates "^5.0.1" postcss-discard-empty "^5.0.1" - postcss-discard-overridden "^5.0.1" + postcss-discard-overridden "^5.0.2" postcss-merge-longhand "^5.0.4" - postcss-merge-rules "^5.0.3" - postcss-minify-font-values "^5.0.1" - postcss-minify-gradients "^5.0.3" - postcss-minify-params "^5.0.2" - postcss-minify-selectors "^5.1.0" + postcss-merge-rules "^5.0.4" + postcss-minify-font-values "^5.0.2" + postcss-minify-gradients "^5.0.4" + postcss-minify-params "^5.0.3" + postcss-minify-selectors "^5.1.1" postcss-normalize-charset "^5.0.1" - postcss-normalize-display-values "^5.0.1" - postcss-normalize-positions "^5.0.1" - postcss-normalize-repeat-style "^5.0.1" - postcss-normalize-string "^5.0.1" - postcss-normalize-timing-functions "^5.0.1" - postcss-normalize-unicode "^5.0.1" + postcss-normalize-display-values "^5.0.2" + postcss-normalize-positions "^5.0.2" + postcss-normalize-repeat-style "^5.0.2" + postcss-normalize-string "^5.0.2" + postcss-normalize-timing-functions "^5.0.2" + postcss-normalize-unicode "^5.0.2" postcss-normalize-url "^5.0.4" - postcss-normalize-whitespace "^5.0.1" - postcss-ordered-values "^5.0.2" + postcss-normalize-whitespace "^5.0.2" + postcss-ordered-values "^5.0.3" postcss-reduce-initial "^5.0.2" - postcss-reduce-transforms "^5.0.1" + postcss-reduce-transforms "^5.0.2" postcss-svgo "^5.0.3" postcss-unique-selectors "^5.0.2" -cssnano-utils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-2.0.1.tgz#8660aa2b37ed869d2e2f22918196a9a8b6498ce2" - integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ== +cssnano-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.0.0.tgz#c0b9fcd6e4f05c5155b07e9ab11bf94b97163057" + integrity sha512-Pzs7/BZ6OgT+tXXuF12DKR8SmSbzUeVYCtMBbS8lI0uAm3mrYmkyqCXXPsQESI6kmLfEVBppbdVY/el3hg3nAA== -cssnano@5.0.14: - version "5.0.14" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.14.tgz#99bc550f663b48c38e9b8e0ae795697c9de84b47" - integrity sha512-qzhRkFvBhv08tbyKCIfWbxBXmkIpLl1uNblt8SpTHkgLfON5OCPX/CCnkdNmEosvo8bANQYmTTMEgcVBlisHaw== +cssnano@5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.15.tgz#8779eaf60e3665e6a12687c814d375cc9f78db76" + integrity sha512-ppZsS7oPpi2sfiyV5+i+NbB/3GtQ+ab2Vs1azrZaXWujUSN4o+WdTxlCZIMcT9yLW3VO/5yX3vpyDaQ1nIn8CQ== dependencies: - cssnano-preset-default "^5.1.9" + cssnano-preset-default "^5.1.10" lilconfig "^2.0.3" yaml "^1.10.2" @@ -1977,24 +2000,25 @@ csstype@^2.6.8: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f" integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A== -cypress@9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.2.0.tgz#727c20b4662167890db81d5f6ba615231835b17d" - integrity sha512-Jn26Tprhfzh/a66Sdj9SoaYlnNX6Mjfmj5PHu2a7l3YHXhrgmavM368wjCmgrxC6KHTOv9SpMQGhAJn+upDViA== +cypress@9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.3.1.tgz#8116f52d49d6daf90a91e88f3eafd940234d2958" + integrity sha512-BODdPesxX6bkVUnH8BVsV8I/jn57zQtO1FEOUTiuG2us3kslW7g0tcuwiny7CKCmJUZz8S/D587ppC+s58a+5Q== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" "@types/node" "^14.14.31" - "@types/sinonjs__fake-timers" "^6.0.2" + "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" arch "^2.2.0" blob-util "^2.0.2" - bluebird "3.7.2" + bluebird "^3.7.2" + buffer "^5.6.0" cachedir "^2.3.0" chalk "^4.1.0" check-more-types "^2.24.0" cli-cursor "^3.1.0" - cli-table3 "~0.6.0" + cli-table3 "~0.6.1" commander "^5.1.0" common-tags "^1.8.0" dayjs "^1.10.4" @@ -2299,7 +2323,7 @@ enhanced-resolve@^5.8.3: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.5, enquirer@^2.3.6: +enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== @@ -2439,38 +2463,37 @@ eslint-import-resolver-node@^0.3.6: debug "^3.2.7" resolve "^1.20.0" -eslint-module-utils@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c" - integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ== +eslint-module-utils@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz#1d0aa455dcf41052339b63cada8ab5fd57577129" + integrity sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg== dependencies: debug "^3.2.7" find-up "^2.1.0" - pkg-dir "^2.0.0" -eslint-plugin-import@2.25.3: - version "2.25.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz#a554b5f66e08fb4f6dc99221866e57cfff824766" - integrity sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg== +eslint-plugin-import@2.25.4: + version "2.25.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" + integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== dependencies: array-includes "^3.1.4" array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.1" + eslint-module-utils "^2.7.2" has "^1.0.3" is-core-module "^2.8.0" is-glob "^4.0.3" minimatch "^3.0.4" object.values "^1.1.5" resolve "^1.20.0" - tsconfig-paths "^3.11.0" + tsconfig-paths "^3.12.0" -eslint-plugin-vue@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.2.0.tgz#b404bc10e3f43b2b7aad4ebb3b38090a58040202" - integrity sha512-cLIdTuOAMXyHeQ4drYKcZfoyzdwdBpH279X8/N0DgmotEI9yFKb5O/cAgoie/CkQZCH/MOmh0xw/KEfS90zY2A== +eslint-plugin-vue@8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.3.0.tgz#2ae4f915ed3541a58c4a4c1145c1e60b86aa7e85" + integrity sha512-IIuLHw4vQxGlHcoP2dG6t/2OVdQf2qoyAzEGAxreU1afZOHGA7y3TWq8I+r3ZA6Wjs6xpeUWGHlT31QGr9Rb5g== dependencies: eslint-utils "^3.0.0" natural-compare "^1.4.0" @@ -2523,10 +2546,15 @@ eslint-visitor-keys@^3.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2" integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA== -eslint@8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.6.0.tgz#4318c6a31c5584838c1a2e940c478190f58d558e" - integrity sha512-UvxdOJ7mXFlw7iuHZA4jmzPaUqIw54mZrv+XPYKNbKdLR0et4rf60lIZUU9kiNtnzzMzGWxMV+tQ7uG7JG8DPw== +eslint-visitor-keys@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" + integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== + +eslint@8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.7.0.tgz#22e036842ee5b7cf87b03fe237731675b4d3633c" + integrity sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w== dependencies: "@eslint/eslintrc" "^1.0.5" "@humanwhocodes/config-array" "^0.9.2" @@ -2535,11 +2563,10 @@ eslint@8.6.0: cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" - enquirer "^2.3.5" escape-string-regexp "^4.0.0" eslint-scope "^7.1.0" eslint-utils "^3.0.0" - eslint-visitor-keys "^3.1.0" + eslint-visitor-keys "^3.2.0" espree "^9.3.0" esquery "^1.4.0" esutils "^2.0.2" @@ -2548,7 +2575,7 @@ eslint@8.6.0: functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" globals "^13.6.0" - ignore "^4.0.6" + ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" @@ -2559,9 +2586,7 @@ eslint@8.6.0: minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" - progress "^2.0.0" regexpp "^3.2.0" - semver "^7.2.1" strip-ansi "^6.0.1" strip-json-comments "^3.1.0" text-table "^0.2.0" @@ -3123,6 +3148,11 @@ graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +graceful-fs@^4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -3240,13 +3270,18 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -idb-keyval@6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.0.3.tgz#e47246a15e55d0fff9fa204fd9ca06f90ff30c52" - integrity sha512-yh8V7CnE6EQMu9YDwQXhRxwZh4nv+8xm/HV4ZqK4IiYFJBWYGjJuykADJbSP+F/GDXUBwCSSNn/14IpGL81TuA== +idb-keyval@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.1.0.tgz#e659cff41188e6097d7fadd69926f6adbbe70041" + integrity sha512-u/qHZ75rlD3gH+Zah8dAJVJcGW/RfCnfNrFkElC5RpRCnpsCXXhqjVk+6MoVKJ3WhmNbRYdI6IIVP88e+5sxGw== dependencies: safari-14-idb-fix "^3.0.0" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -3262,6 +3297,11 @@ ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + immutable@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23" @@ -3668,7 +3708,7 @@ js-yaml@4.0.0: dependencies: argparse "^2.0.1" -js-yaml@^3.13.1, js-yaml@^3.14.1: +js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -3818,10 +3858,10 @@ jws@^4.0.0: jwa "^2.0.0" safe-buffer "^5.0.1" -katex@0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.1.tgz#cf4ce2fa1257c3279cc7a7fe0c8d1fab40800893" - integrity sha512-KIk+gizli0gl1XaJlCYS8/donGMbzXYTka6BbH3AgvDJTOwyDY4hJ+YmzJ1F0y/3XzX5B9ED8AqB2Hmn2AZ0uA== +katex@0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.2.tgz#c05ece41ab497597b17abca2cecde3e4c0127f9d" + integrity sha512-FfZ/f6f8bQdLmJ3McXDNTkKenQkoXkItpW0I9bsG2wgb+8JAY5bwpXFtI8ZVrg5hc1wo1X/UIhdkVMpok46tEQ== dependencies: commander "^8.0.0" @@ -4045,10 +4085,10 @@ merge@^2.1.0: resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== -mfm-js@0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.20.0.tgz#3afdcd7959461fd825aa8af9b9e8a57cdbddc290" - integrity sha512-1+3tV3nWUKQNh/ztX3wXu5iLBtdsg6q3wUhl+XyOhc2H3sQdG+sih/w2c0nR9TIawjN+Z1/pvgGzxMJHfmKQmA== +mfm-js@0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.21.0.tgz#954cc6e7071700b0b1872c78a90bada10be7f772" + integrity sha512-nyQXaipa7rmAw9ER9uYigMvGcdCwhSv93abZBwccnSnPOc1W3S/WW0+sN28g3YSmlHDCA0i2q9aAFc9EgOi5KA== dependencies: twemoji-parser "13.1.x" @@ -4099,10 +4139,10 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -misskey-js@0.0.12: - version "0.0.12" - resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.12.tgz#c8fc2fc823c69b0b7d1cb153a5a29afc33f0ff4d" - integrity sha512-Aq74/XskxaFN5CeCLeKPp5UP/xTFHvVnOV677G/zoSIShJRTeLsN5YnzwFpOVI2KN21JQ/ExesKDLoWlvQHtNA== +misskey-js@0.0.13: + version "0.0.13" + resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.13.tgz#03a4e469186e28752d599dc4093519eb64647970" + integrity sha512-kBdJdfe281gtykzzsrN3IAxWUQIimzPiJGyKWf863ggWJlWYVPmP9hTFlX2z8oPOaypgVBPEPHyw/jNUdc2DbQ== dependencies: autobind-decorator "^2.4.0" eventemitter3 "^4.0.7" @@ -4269,7 +4309,7 @@ oauth@0.9.15: resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= -object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -4551,13 +4591,6 @@ pify@^2.2.0: resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= -pkg-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" - integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= - dependencies: - find-up "^2.1.0" - pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -4565,6 +4598,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + pngjs@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" @@ -4578,18 +4616,18 @@ portscanner@2.2.0: async "^2.6.0" is-number-like "^1.0.3" -postcss-calc@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a" - integrity sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g== +postcss-calc@^8.2.0: + version "8.2.2" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.2.tgz#9706e7399e8ec8b61a47830dcf1f21391af23373" + integrity sha512-B5R0UeB4zLJvxNt1FVCaDZULdzsKLPc6FhjFJ+xwFiq7VG4i9cuaJLxVjNtExNK8ocm3n2o4unXXLiVX1SCqxA== dependencies: postcss-selector-parser "^6.0.2" postcss-value-parser "^4.0.2" -postcss-colormin@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.2.tgz#019cd6912bef9e7e0924462c5e4ffae241e2f437" - integrity sha512-tSEe3NpqWARUTidDlF0LntPkdlhXqfDFuA1yslqpvvGAfpZ7oBaw+/QXd935NKm2U9p4PED0HDZlzmMk7fVC6g== +postcss-colormin@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.3.tgz#da7fb80e81ad80d2867ea9e38672a892add5df15" + integrity sha512-dra4xoAjub2wha6RUXAgadHEn2lGxbj8drhFcIGLOMn914Eu7DkPUurugDXgstwttCYkJtZ/+PkWRWdp3UHRIA== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" @@ -4618,10 +4656,10 @@ postcss-discard-empty@^5.0.1: resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz#ee136c39e27d5d2ed4da0ee5ed02bc8a9f8bf6d8" integrity sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw== -postcss-discard-overridden@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6" - integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q== +postcss-discard-overridden@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.2.tgz#e6f51d83e66feffcf05ed94c4ad20b814d0aab5f" + integrity sha512-+56BLP6NSSUuWUXjRgAQuho1p5xs/hU5Sw7+xt9S3JSg+7R6+WMGnJW7Hre/6tTuZ2xiXMB42ObkiZJ2hy/Pew== postcss-loader@6.2.1: version "6.2.1" @@ -4640,46 +4678,46 @@ postcss-merge-longhand@^5.0.4: postcss-value-parser "^4.1.0" stylehacks "^5.0.1" -postcss-merge-rules@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.3.tgz#b5cae31f53129812a77e3eb1eeee448f8cf1a1db" - integrity sha512-cEKTMEbWazVa5NXd8deLdCnXl+6cYG7m2am+1HzqH0EnTdy8fRysatkaXb2dEnR+fdaDxTvuZ5zoBdv6efF6hg== +postcss-merge-rules@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.4.tgz#a50640fd832380f322bd2861a9b33fbde4219f9b" + integrity sha512-yOj7bW3NxlQxaERBB0lEY1sH5y+RzevjbdH4DBJurjKERNpknRByFNdNe+V72i5pIZL12woM9uGdS5xbSB+kDQ== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" - cssnano-utils "^2.0.1" + cssnano-utils "^3.0.0" postcss-selector-parser "^6.0.5" -postcss-minify-font-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz#a90cefbfdaa075bd3dbaa1b33588bb4dc268addf" - integrity sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA== +postcss-minify-font-values@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.2.tgz#4603e956d85cd0719156e2b3eb68e3cd2f917092" + integrity sha512-R6MJZryq28Cw0AmnyhXrM7naqJZZLoa1paBltIzh2wM7yb4D45TLur+eubTQ4jCmZU9SGeZdWsc5KcSoqTMeTg== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-minify-gradients@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz#f970a11cc71e08e9095e78ec3a6b34b91c19550e" - integrity sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q== +postcss-minify-gradients@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.4.tgz#f13146950513f5a201015306914e3c76d10b591d" + integrity sha512-RVwZA7NC4R4J76u8X0Q0j+J7ItKUWAeBUJ8oEEZWmtv3Xoh19uNJaJwzNpsydQjk6PkuhRrK+YwwMf+c+68EYg== dependencies: colord "^2.9.1" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-minify-params@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.2.tgz#1b644da903473fbbb18fbe07b8e239883684b85c" - integrity sha512-qJAPuBzxO1yhLad7h2Dzk/F7n1vPyfHfCCh5grjGfjhi1ttCnq4ZXGIW77GSrEbh9Hus9Lc/e/+tB4vh3/GpDg== +postcss-minify-params@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.3.tgz#9f933d37098ef1dcf007e159a47bb2c1cf06989d" + integrity sha512-NY92FUikE+wralaiVexFd5gwb7oJTIDhgTNeIw89i1Ymsgt4RWiPXfz3bg7hDy4NL6gepcThJwOYNtZO/eNi7Q== dependencies: alphanum-sort "^1.0.2" browserslist "^4.16.6" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-minify-selectors@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz#4385c845d3979ff160291774523ffa54eafd5a54" - integrity sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og== +postcss-minify-selectors@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.1.tgz#20ae03b411f7fb397451e3d7d85b989f944b871c" + integrity sha512-TOzqOPXt91O2luJInaVPiivh90a2SIK5Nf1Ea7yEIM/5w+XA5BGrZGUSW8aEx9pJ/oNj7ZJBhjvigSiBV+bC1Q== dependencies: alphanum-sort "^1.0.2" postcss-selector-parser "^6.0.5" @@ -4717,51 +4755,48 @@ postcss-normalize-charset@^5.0.1: resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz#121559d1bebc55ac8d24af37f67bd4da9efd91d0" integrity sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg== -postcss-normalize-display-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz#62650b965981a955dffee83363453db82f6ad1fd" - integrity sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ== +postcss-normalize-display-values@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.2.tgz#8b5273c6c7d0a445e6ef226b8a5bb3204a55fb99" + integrity sha512-RxXoJPUR0shSjkMMzgEZDjGPrgXUVYyWA/YwQRicb48H15OClPuaDR7tYokLAlGZ2tCSENEN5WxjgxSD5m4cUw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-positions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz#868f6af1795fdfa86fbbe960dceb47e5f9492fe5" - integrity sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg== +postcss-normalize-positions@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.2.tgz#799fa494b352a5da183be8f050024af6d92fa29c" + integrity sha512-tqghWFVDp2btqFg1gYob1etPNxXLNh3uVeWgZE2AQGh6b2F8AK2Gj36v5Vhyh+APwIzNjmt6jwZ9pTBP+/OM8g== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-repeat-style@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz#cbc0de1383b57f5bb61ddd6a84653b5e8665b2b5" - integrity sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w== +postcss-normalize-repeat-style@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.2.tgz#fd9bddba3e6fd5f5d95c18dfb42a09ecd563adea" + integrity sha512-/rIZn8X9bBzC7KvY4iKUhXUGW3MmbXwfPF23jC9wT9xTi7kAvgj8sEgwxjixBmoL6MVa4WOgxNz2hAR6wTK8tw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-string@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz#d9eafaa4df78c7a3b973ae346ef0e47c554985b0" - integrity sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA== +postcss-normalize-string@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.2.tgz#1b2bbf91526f61266f28abf7f773e4136b2c4bd2" + integrity sha512-zaI1yzwL+a/FkIzUWMQoH25YwCYxi917J4pYm1nRXtdgiCdnlTkx5eRzqWEC64HtRa06WCJ9TIutpb6GmW4gFw== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-timing-functions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz#8ee41103b9130429c6cbba736932b75c5e2cb08c" - integrity sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q== +postcss-normalize-timing-functions@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.2.tgz#db4f4f49721f47667afd1fdc5edb032f8d9cdb2e" + integrity sha512-Ao0PP6MoYsRU1LxeVUW740ioknvdIUmfr6uAA3xWlQJ9s69/Tupy8qwhuKG3xWfl+KvLMAP9p2WXF9cwuk/7Bg== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-unicode@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz#82d672d648a411814aa5bf3ae565379ccd9f5e37" - integrity sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA== +postcss-normalize-unicode@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.2.tgz#c4db89a0116066716b9e9fcb6444ce63178f5ced" + integrity sha512-3y/V+vjZ19HNcTizeqwrbZSUsE69ZMRHfiiyLAJb7C7hJtYmM4Gsbajy7gKagu97E8q5rlS9k8FhojA8cpGhWw== dependencies: - browserslist "^4.16.0" - postcss-value-parser "^4.1.0" + browserslist "^4.16.6" + postcss-value-parser "^4.2.0" postcss-normalize-url@^5.0.4: version "5.0.4" @@ -4771,20 +4806,20 @@ postcss-normalize-url@^5.0.4: normalize-url "^6.0.1" postcss-value-parser "^4.2.0" -postcss-normalize-whitespace@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz#b0b40b5bcac83585ff07ead2daf2dcfbeeef8e9a" - integrity sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-ordered-values@^5.0.2: +postcss-normalize-whitespace@^5.0.2: version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz#1f351426977be00e0f765b3164ad753dac8ed044" - integrity sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ== + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.2.tgz#92c5eaffe5255b5c43fca0baf19227e607c534db" + integrity sha512-CXBx+9fVlzSgbk0IXA/dcZn9lXixnQRndnsPC5ht3HxlQ1bVh77KQDL1GffJx1LTzzfae8ftMulsjYmO2yegxA== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" + +postcss-ordered-values@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.3.tgz#d80a8565f2e21efe8a06abacd60629a783bbcf54" + integrity sha512-T9pDS+P9bWeFvqivXd5ACzQmrCmHjv3ZP+djn8E1UZY7iK79pFSm7i3WbKw2VSmFmdbMm8sQ12OPcNpzBo3Z2w== + dependencies: + cssnano-utils "^3.0.0" + postcss-value-parser "^4.2.0" postcss-reduce-initial@^5.0.2: version "5.0.2" @@ -4794,13 +4829,12 @@ postcss-reduce-initial@^5.0.2: browserslist "^4.16.6" caniuse-api "^3.0.0" -postcss-reduce-transforms@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz#93c12f6a159474aa711d5269923e2383cedcf640" - integrity sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA== +postcss-reduce-transforms@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.2.tgz#9242758629f9ad4d90312eadbc921259d15bee4d" + integrity sha512-25HeDeFsgiPSUx69jJXZn8I06tMxLQJJNF5h7i9gsUg8iP4KOOJ8EX8fj3seeoLt3SLU2YDD6UPnDYVGUO7DEA== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: version "6.0.4" @@ -4888,10 +4922,10 @@ printj@~1.1.0: resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== -prismjs@1.25.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" - integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== +prismjs@1.26.0: + version "1.26.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.26.0.tgz#16881b594828bb6b45296083a8cbab46b0accd47" + integrity sha512-HUoH9C5Z3jKkl3UunCyiD5jwk0+Hz0fIgQ2nbwU2Oo/ceuTAQAg+pPVnfdt2TJWRVLcxKh9iuoYDUSc8clb5UQ== private-ip@2.3.3: version "2.3.3" @@ -4903,11 +4937,6 @@ private-ip@2.3.3: is-ip "^3.1.0" netmask "^2.0.2" -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - promise-limit@2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/promise-limit/-/promise-limit-2.7.0.tgz#eb5737c33342a030eaeaecea9b3d3a93cb592b26" @@ -5292,10 +5321,10 @@ sass-loader@12.4.0: klona "^2.0.4" neo-async "^2.6.2" -sass@1.45.2: - version "1.45.2" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.45.2.tgz#130b428c1692201cfa181139835d6fc378a33323" - integrity sha512-cKfs+F9AMPAFlbbTXNsbGvg3y58nV0mXA3E94jqaySKcC8Kq3/8983zVKQ0TLMUrHw7hF9Tnd3Bz9z5Xgtrl9g== +sass@1.49.0: + version "1.49.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.0.tgz#65ec1b1d9a6bc1bae8d2c9d4b392c13f5d32c078" + integrity sha512-TVwVdNDj6p6b4QymJtNtRS2YtLJ/CqZriGg0eIAbAKMlN8Xy6kbv33FsEZSF7FufFFM705SQviHjjThfaQ4VNw== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -5334,7 +5363,7 @@ seedrandom@3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: +semver@^7.3.2, semver@^7.3.4: version "7.3.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== @@ -5766,10 +5795,10 @@ textarea-caret@3.1.0: resolved "https://registry.yarnpkg.com/textarea-caret/-/textarea-caret-3.1.0.tgz#5d5a35bb035fd06b2ff0e25d5359e97f2655087f" integrity sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q== -three@0.117.1: - version "0.117.1" - resolved "https://registry.yarnpkg.com/three/-/three-0.117.1.tgz#a49bcb1a6ddea2f250003e42585dc3e78e92b9d3" - integrity sha512-t4zeJhlNzUIj9+ub0l6nICVimSuRTZJOqvk3Rmlu+YGdTOJ49Wna8p7aumpkXJakJfITiybfpYE1XN1o1Z34UQ== +three@0.136.0: + version "0.136.0" + resolved "https://registry.yarnpkg.com/three/-/three-0.136.0.tgz#b1504db021b46398ef468aa7849f3dcabb814f50" + integrity sha512-+fEMX7nYLz2ZesVP/dyifli5Jf8gR3XPAnFJveQ80aMhibFduzrADnjMbARXh8+W9qLK7rshJCjAIL/6cDxC+A== throttle-debounce@3.0.1: version "3.0.1" @@ -5868,7 +5897,7 @@ tsc-alias@1.5.0: mylas "^2.1.6" normalize-path "^3.0.0" -tsconfig-paths@3.12.0: +tsconfig-paths@3.12.0, tsconfig-paths@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== @@ -5878,16 +5907,6 @@ tsconfig-paths@3.12.0: minimist "^1.2.0" strip-bom "^3.0.0" -tsconfig-paths@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" - integrity sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.1" - minimist "^1.2.0" - strip-bom "^3.0.0" - tslib@^1.8.1, tslib@^1.9.0: version "1.11.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" @@ -5956,10 +5975,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" - integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== +typescript@4.5.5: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== unbox-primitive@^1.0.1: version "1.0.1" @@ -6134,16 +6153,16 @@ vue-svg-loader@0.17.0-beta.2: semver "^7.3.2" svgo "^1.3.2" -vue@3.2.26: - version "3.2.26" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.26.tgz#5db575583ecae495c7caa5c12fd590dffcbb763e" - integrity sha512-KD4lULmskL5cCsEkfhERVRIOEDrfEL9CwAsLYpzptOGjaGFNWo3BQ9g8MAb7RaIO71rmVOziZ/uEN/rHwcUIhg== +vue@3.2.27: + version "3.2.27" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.27.tgz#dc898b3cf7393a620ed5e4a91e5fa8c1ed1ba67a" + integrity sha512-p1cH8Q6eaPwvANCjFQj497a914cxXKKwOG3Lg9USddTOrn4/zFMKjn9dnovkx+L8VtFaNgbVqW8mLJS/eTA6xw== dependencies: - "@vue/compiler-dom" "3.2.26" - "@vue/compiler-sfc" "3.2.26" - "@vue/runtime-dom" "3.2.26" - "@vue/server-renderer" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-dom" "3.2.27" + "@vue/compiler-sfc" "3.2.27" + "@vue/runtime-dom" "3.2.27" + "@vue/server-renderer" "3.2.27" + "@vue/shared" "3.2.27" vuedraggable@4.0.1: version "4.0.1" @@ -6230,10 +6249,10 @@ webpack-sources@^3.2.2: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.2.tgz#d88e3741833efec57c4c789b6010db9977545260" integrity sha512-cp5qdmHnu5T8wRg2G3vZZHoJPN14aqQ89SyQ11NpGH5zEMDCclt49rzo+MaRazk7/UeILhAI+/sEtcM+7Fr0nw== -webpack@5.65.0: - version "5.65.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.65.0.tgz#ed2891d9145ba1f0d318e4ea4f89c3fa18e6f9be" - integrity sha512-Q5or2o6EKs7+oKmJo7LaqZaMOlDWQse9Tm5l1WAfU/ujLGN5Pb0SqGeVkN/4bpPmEqEP5RnVhiqsOtWtUVwGRw== +webpack@5.66.0: + version "5.66.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.66.0.tgz#789bf36287f407fc92b3e2d6f978ddff1bfc2dbb" + integrity sha512-NJNtGT7IKpGzdW7Iwpn/09OXz9inIkeIQ/ibY6B+MdV1x6+uReqz/5z1L89ezWnpPDWpXF0TY5PCYKQdWVn8Vg== dependencies: "@types/eslint-scope" "^3.7.0" "@types/estree" "^0.0.50" @@ -6249,7 +6268,7 @@ webpack@5.65.0: eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" json-parse-better-errors "^1.0.2" loader-runner "^4.2.0" mime-types "^2.1.27" @@ -6386,10 +6405,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.0.tgz#f05e982a0a88c604080e8581576e2a063802bed6" - integrity sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ== +ws@8.4.2: + version "8.4.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" + integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA== xml-js@^1.6.11: version "1.6.11" diff --git a/yarn.lock b/yarn.lock index 7f74114102..82d2272f6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@cypress/request@^2.88.7": - version "2.88.7" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.7.tgz#386d960ab845a96953723348088525d5a75aaac4" - integrity sha512-FTULIP2rnDJvZDT9t6B4nSfYR40ue19tVmv3wUcY05R9/FPCoMl1nAPJkzWzBCo7ltVn5ThQTbxiMoGBN7k0ig== +"@cypress/request@^2.88.10": + version "2.88.10" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" + integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -14,8 +14,7 @@ extend "~3.0.2" forever-agent "~0.6.1" form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" + http-signature "~1.3.6" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" @@ -68,29 +67,30 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@redocly/ajv@^8.6.2": - version "8.6.2" - resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.2.tgz#8c4e485e72f7864f91fae40093bed548ec2619b2" - integrity sha512-tU8fQs0D76ZKhJ2cWtnfQthWqiZgGBx0gH0+5D8JvaBEBaqA8foPPBt3Nonwr3ygyv5xrw2IzKWgIY86BlGs+w== +"@redocly/ajv@^8.6.4": + version "8.6.4" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.4.tgz#94053e7a9d4146d1a4feacd3813892873f229a85" + integrity sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -"@redocly/openapi-core@1.0.0-beta.54": - version "1.0.0-beta.54" - resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.54.tgz#42575a849c4dd54b9d0c6413fb8ca547e087cd11" - integrity sha512-uYs0N1Trjkh7u8IMIuCU2VxCXhMyGWSZUkP/WNdTR1OgBUtvNdF9C32zoQV+hyCIH4gVu42ROHkjisy333ZX+w== +"@redocly/openapi-core@1.0.0-beta.79": + version "1.0.0-beta.79" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.79.tgz#7512b3507ab99dc78226f9069669c5302abb0969" + integrity sha512-do79vGt3iiHsaVG9LKY8dH+d1E7TLHr+3T+CQ1lqagtWVjYOxqGaoxAT8tRD7R1W0z8BmS4e2poNON6c1sxP5g== dependencies: - "@redocly/ajv" "^8.6.2" + "@redocly/ajv" "^8.6.4" "@types/node" "^14.11.8" colorette "^1.2.0" js-levenshtein "^1.1.6" - js-yaml "^3.14.1" + js-yaml "^4.1.0" lodash.isequal "^4.5.0" minimatch "^3.0.4" node-fetch "^2.6.1" + pluralize "^8.0.0" yaml-ast-parser "0.0.43" "@sideway/address@^4.1.0": @@ -125,10 +125,10 @@ resolved "https://registry.yarnpkg.com/@types/expect/-/expect-1.20.4.tgz#8288e51737bf7e3ab5d7c77bfa695883745264e5" integrity sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg== -"@types/fluent-ffmpeg@2.1.17": - version "2.1.17" - resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.17.tgz#6958dda400fe1b33c21f3683db76905cb210d053" - integrity sha512-/bdvjKw/mtBHlJ2370d04nt4CsWqU5MrwS/NtO96V01jxitJ4+iq8OFNcqc5CegeV3TQOK3uueK02kvRK+zjUg== +"@types/fluent-ffmpeg@2.1.20": + version "2.1.20" + resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.20.tgz#3b5f42fc8263761d58284fa46ee6759a64ce54ac" + integrity sha512-B+OvhCdJ3LgEq2PhvWNOiB/EfwnXLElfMCgc4Z1K5zXgSfo9I6uGKwR/lqmNPFQuebNnes7re3gqkV77SyypLg== dependencies: "@types/node" "*" @@ -181,10 +181,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.9.tgz#b97c057e6138adb7b720df2bd0264b03c9f504fd" integrity sha512-CMjgRNsks27IDwI785YMY0KLt3co/c0cQ5foxHYv/shC2w8oOnVwz5Ubq1QG5KzrcW+AXk6gzdnxIkDnTvzu3g== -"@types/sinonjs__fake-timers@^6.0.2": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.3.tgz#79df6f358ae8f79e628fe35a63608a0ea8e7cf08" - integrity sha512-E1dU4fzC9wN2QK2Cr1MLCfyHM8BoNnRFvuf45LYMPNDA+WqbNzC45S4UzPxvp1fFJ1rvSGU0bPvdd35VLmXG8g== +"@types/sinonjs__fake-timers@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" + integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== "@types/sizzle@^2.3.2": version "2.3.3" @@ -227,48 +227,48 @@ dependencies: "@types/node" "*" -"@typescript-eslint/parser@5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.4.0.tgz#3aa83ce349d66e39b84151f6d5464928044ca9e3" - integrity sha512-JoB41EmxiYpaEsRwpZEYAJ9XQURPFer8hpkIW9GiaspVLX8oqbqNM8P4EP8HOZg96yaALiLEVWllA2E8vwsIKw== +"@typescript-eslint/parser@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.10.0.tgz#8f59e036f5f1cffc178cacbd5ccdd02aeb96c91c" + integrity sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw== dependencies: - "@typescript-eslint/scope-manager" "5.4.0" - "@typescript-eslint/types" "5.4.0" - "@typescript-eslint/typescript-estree" "5.4.0" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" debug "^4.3.2" -"@typescript-eslint/scope-manager@5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.4.0.tgz#aaab08415f4a9cf32b870c7750ae8ba4607126a1" - integrity sha512-pRxFjYwoi8R+n+sibjgF9iUiAELU9ihPBtHzocyW8v8D8G8KeQvXTsW7+CBYIyTYsmhtNk50QPGLE3vrvhM5KA== +"@typescript-eslint/scope-manager@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz#bb5d872e8b9e36203908595507fbc4d3105329cb" + integrity sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg== dependencies: - "@typescript-eslint/types" "5.4.0" - "@typescript-eslint/visitor-keys" "5.4.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" -"@typescript-eslint/types@5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.4.0.tgz#b1c130f4b381b77bec19696c6e3366f9781ce8f2" - integrity sha512-GjXNpmn+n1LvnttarX+sPD6+S7giO+9LxDIGlRl4wK3a7qMWALOHYuVSZpPTfEIklYjaWuMtfKdeByx0AcaThA== +"@typescript-eslint/types@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.0.tgz#beb3cb345076f5b088afe996d57bcd1dfddaa75c" + integrity sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ== -"@typescript-eslint/typescript-estree@5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.4.0.tgz#fe524fb308973c68ebeb7428f3b64499a6ba5fc0" - integrity sha512-nhlNoBdhKuwiLMx6GrybPT3SFILm5Gij2YBdPEPFlYNFAXUJWX6QRgvi/lwVoadaQEFsizohs6aFRMqsXI2ewA== +"@typescript-eslint/typescript-estree@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz#4be24a3dea0f930bb1397c46187d0efdd955a224" + integrity sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA== dependencies: - "@typescript-eslint/types" "5.4.0" - "@typescript-eslint/visitor-keys" "5.4.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.4.0.tgz#09bc28efd3621f292fe88c86eef3bf4893364c8c" - integrity sha512-PVbax7MeE7tdLfW5SA0fs8NGVVr+buMPrcj+CWYWPXsZCH8qZ1THufDzbXm1xrZ2b2PA1iENJ0sRq5fuUtvsJg== +"@typescript-eslint/visitor-keys@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz#770215497ad67cd15a572b52089991d5dfe06281" + integrity sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ== dependencies: - "@typescript-eslint/types" "5.4.0" + "@typescript-eslint/types" "5.10.0" eslint-visitor-keys "^3.0.0" aggregate-error@^3.0.0: @@ -279,16 +279,6 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.5.5: - version "6.12.5" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" - integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -584,6 +574,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -631,7 +626,7 @@ blob-util@^2.0.2: resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== -bluebird@3.7.2: +bluebird@3.7.2, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -690,6 +685,14 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -827,15 +830,14 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-table3@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" - integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== +cli-table3@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" + integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== dependencies: - object-assign "^4.1.0" string-width "^4.2.0" optionalDependencies: - colors "^1.1.2" + colors "1.4.0" cli-truncate@^2.1.0: version "2.1.0" @@ -971,7 +973,7 @@ colormin@^1.0.5: css-color-names "0.0.4" has "^1.0.1" -colors@^1.1.2: +colors@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -1115,24 +1117,25 @@ csso@~2.3.1: clap "^1.0.9" source-map "^0.5.3" -cypress@9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.1.0.tgz#5d23c1b363b7d4853009c74a422a083a8ad2601c" - integrity sha512-fyXcCN51vixkPrz/vO/Qy6WL3hKYJzCQFeWofOpGOFewVVXrGfmfSOGFntXpzWBXsIwPn3wzW0HOFw51jZajNQ== +cypress@9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.3.1.tgz#8116f52d49d6daf90a91e88f3eafd940234d2958" + integrity sha512-BODdPesxX6bkVUnH8BVsV8I/jn57zQtO1FEOUTiuG2us3kslW7g0tcuwiny7CKCmJUZz8S/D587ppC+s58a+5Q== dependencies: - "@cypress/request" "^2.88.7" + "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" "@types/node" "^14.14.31" - "@types/sinonjs__fake-timers" "^6.0.2" + "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" arch "^2.2.0" blob-util "^2.0.2" - bluebird "3.7.2" + bluebird "^3.7.2" + buffer "^5.6.0" cachedir "^2.3.0" chalk "^4.1.0" check-more-types "^2.24.0" cli-cursor "^3.1.0" - cli-table3 "~0.6.0" + cli-table3 "~0.6.1" commander "^5.1.0" common-tags "^1.8.0" dayjs "^1.10.4" @@ -1389,11 +1392,6 @@ esprima@^2.6.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - event-stream@=3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" @@ -1557,11 +1555,6 @@ fast-glob@^3.1.1: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - fastq@^1.6.0: version "1.13.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" @@ -1978,19 +1971,6 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== - dependencies: - ajv "^6.5.5" - har-schema "^2.0.0" - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -2068,14 +2048,14 @@ html-comment-regex@^1.1.0: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= +http-signature@~1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" + integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== dependencies: assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" + jsprim "^2.0.2" + sshpk "^1.14.1" human-signals@^1.1.1: version "1.1.1" @@ -2087,6 +2067,11 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.1.4: version "5.1.9" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" @@ -2436,21 +2421,13 @@ js-levenshtein@^1.1.6: resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== -js-yaml@4.1.0: +js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" -js-yaml@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - js-yaml@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" @@ -2464,20 +2441,15 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - json-schema-traverse@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" @@ -2498,14 +2470,14 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= +jsprim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" + integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" - json-schema "0.2.3" + json-schema "0.4.0" verror "1.10.0" just-debounce@^1.0.0: @@ -3139,6 +3111,11 @@ plugin-error@^1.0.1: arr-union "^3.1.0" extend-shallow "^3.0.2" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -3947,10 +3924,10 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== +sshpk@^1.14.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" + integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -4283,10 +4260,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" - integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw== +typescript@4.5.5: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== unc-path-regex@^0.1.2: version "0.1.2" From 8d502eba596d63ff4ddb14f833eef76e7889665e Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 21 Jan 2022 17:15:14 +0900 Subject: [PATCH 70/81] refactor --- packages/backend/src/server/api/call.ts | 2 +- packages/backend/src/server/api/limiter.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index 36aadb532b..399ee65bde 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -1,5 +1,5 @@ import { performance } from 'perf_hooks'; -import limiter from './limiter'; +import { limiter } from './limiter'; import { User } from '@/models/entities/user'; import endpoints from './endpoints'; import { ApiError } from './error'; diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts index 5f617771e0..4721f6263a 100644 --- a/packages/backend/src/server/api/limiter.ts +++ b/packages/backend/src/server/api/limiter.ts @@ -7,8 +7,8 @@ import Logger from '@/services/logger'; const logger = new Logger('limiter'); -export default (endpoint: IEndpoint, user: User) => new Promise<void>((ok, reject) => { - const limitation = endpoint.meta.limit!; +export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user: User) => new Promise<void>((ok, reject) => { + const limitation = endpoint.meta.limit; const key = Object.prototype.hasOwnProperty.call(limitation, 'key') ? limitation.key @@ -30,7 +30,7 @@ export default (endpoint: IEndpoint, user: User) => new Promise<void>((ok, rejec } // Short-term limit - function min() { + function min(): void { const minIntervalLimiter = new Limiter({ id: `${user.id}:${key}:min`, duration: limitation.minInterval, @@ -58,7 +58,7 @@ export default (endpoint: IEndpoint, user: User) => new Promise<void>((ok, rejec } // Long term limit - function max() { + function max(): void { const limiter = new Limiter({ id: `${user.id}:${key}`, duration: limitation.duration, From 9a4267f01fb0a8b56a3176778e28115f9d8765cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= <root@acid-chicken.com> Date: Fri, 21 Jan 2022 17:43:14 +0900 Subject: [PATCH 71/81] =?UTF-8?q?fix(#8133):=20hCaptcha=20=E3=81=AE=20reCA?= =?UTF-8?q?PTCHA=20=E4=BA=92=E6=8F=9B=E6=8C=99=E5=8B=95=E3=82=92=E7=84=A1?= =?UTF-8?q?=E5=8A=B9=E5=8C=96=E3=81=99=E3=82=8B=20(#8135)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(#8133): hCaptcha の reCAPTCHA 互換挙動を無効化する * Update packages/client/src/components/captcha.vue * fix: hCaptcha host Co-authored-by: tamaina <tamaina@hotmail.co.jp> --- packages/client/src/components/captcha.vue | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/client/src/components/captcha.vue b/packages/client/src/components/captcha.vue index 7fe499dc86..770804cf44 100644 --- a/packages/client/src/components/captcha.vue +++ b/packages/client/src/components/captcha.vue @@ -55,12 +55,10 @@ const variable = computed(() => { const loaded = computed(() => !!window[variable.value]); const src = computed(() => { - const endpoint = ({ - hcaptcha: 'https://hcaptcha.com/1', - recaptcha: 'https://www.recaptcha.net/recaptcha', - } as Record<CaptchaProvider, string>)[props.provider]; - - return `${typeof endpoint === 'string' ? endpoint : 'about:invalid'}/api.js?render=explicit`; + switch (props.provider) { + case 'hcaptcha': return 'https://js.hcaptcha.com/1/api.js?render=explicit&recaptchacompat=off'; + case 'recaptcha': return 'https://www.recaptcha.net/recaptcha/api.js?render=explicit'; + } }); const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha); From 76db7ba7816fdcdba03314c062c9319f02118784 Mon Sep 17 00:00:00 2001 From: nullobsi <me@nullob.si> Date: Fri, 21 Jan 2022 00:43:51 -0800 Subject: [PATCH 72/81] update local copy of file when describing (#8131) --- packages/client/src/components/post-form-attaches.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/post-form-attaches.vue b/packages/client/src/components/post-form-attaches.vue index 0782ce22e5..4555fc1a2d 100644 --- a/packages/client/src/components/post-form-attaches.vue +++ b/packages/client/src/components/post-form-attaches.vue @@ -99,10 +99,12 @@ export default defineComponent({ }, { done: result => { if (!result || result.canceled) return; - let comment = result.result; + let comment = result.result.length == 0 ? null : result.result; os.api('drive/files/update', { fileId: file.id, - comment: comment.length == 0 ? null : comment + comment: comment, + }).then(() => { + file.comment = comment; }); } }, 'closed'); From e3a5584d0ab9b1b75b254070ac471c2d9830020f Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 21 Jan 2022 18:27:33 +0900 Subject: [PATCH 73/81] feat: increase files limit for note #8062 --- CHANGELOG.md | 1 + packages/backend/src/server/api/endpoints/notes/create.ts | 6 +++--- packages/client/src/components/post-form-attaches.vue | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88e8055daa..7fb7a37e22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - リバーシ機能が削除されました - 後日別リポジトリとして復活予定です - Chat UIが削除されました +- ノートに添付できるファイルの数が16に増えました ### Improvements - カスタム絵文字一括編集機能 diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 6adbc821e7..4efa76b248 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -48,7 +48,7 @@ export const meta = { validator: $.optional.nullable.str.pipe(text => text.trim() != '' && length(text.trim()) <= maxNoteTextLength - && Array.from(text.trim()).length <= DB_MAX_NOTE_TEXT_LENGTH // DB limit + && Array.from(text.trim()).length <= DB_MAX_NOTE_TEXT_LENGTH, // DB limit ), default: null, }, @@ -78,11 +78,11 @@ export const meta = { }, fileIds: { - validator: $.optional.arr($.type(ID)).unique().range(1, 4), + validator: $.optional.arr($.type(ID)).unique().range(1, 16), }, mediaIds: { - validator: $.optional.arr($.type(ID)).unique().range(1, 4), + validator: $.optional.arr($.type(ID)).unique().range(1, 16), deprecated: true, }, diff --git a/packages/client/src/components/post-form-attaches.vue b/packages/client/src/components/post-form-attaches.vue index 0782ce22e5..591022c5f9 100644 --- a/packages/client/src/components/post-form-attaches.vue +++ b/packages/client/src/components/post-form-attaches.vue @@ -10,7 +10,7 @@ </div> </template> </XDraggable> - <p class="remain">{{ 4 - files.length }}/4</p> + <p class="remain">{{ 16 - files.length }}/16</p> </div> </template> @@ -41,7 +41,6 @@ export default defineComponent({ data() { return { menu: null as Promise<null> | null, - }; }, From eec7e6500ed7f56a3b545adbb45420b3e1c7b0a9 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 21 Jan 2022 18:47:02 +0900 Subject: [PATCH 74/81] enhance: convert svg to png of custom emojis --- .../migration/1642611822809-emoji-url.js | 15 +++++++++ packages/backend/src/misc/populate-emojis.ts | 5 +-- packages/backend/src/models/entities/emoji.ts | 8 ++++- .../backend/src/models/repositories/emoji.ts | 3 +- .../processors/db/export-custom-emojis.ts | 2 +- .../processors/db/import-custom-emojis.ts | 5 +-- .../src/remote/activitypub/models/note.ts | 8 +++-- .../src/remote/activitypub/renderer/emoji.ts | 2 +- .../server/api/endpoints/admin/emoji/add.ts | 5 +-- .../server/api/endpoints/admin/emoji/copy.ts | 8 ++--- .../src/services/note/reaction/create.ts | 14 +++----- packages/backend/src/tools/add-emoji.ts | 33 ------------------- 12 files changed, 49 insertions(+), 59 deletions(-) create mode 100644 packages/backend/migration/1642611822809-emoji-url.js delete mode 100644 packages/backend/src/tools/add-emoji.ts diff --git a/packages/backend/migration/1642611822809-emoji-url.js b/packages/backend/migration/1642611822809-emoji-url.js new file mode 100644 index 0000000000..f229c403f4 --- /dev/null +++ b/packages/backend/migration/1642611822809-emoji-url.js @@ -0,0 +1,15 @@ +const { MigrationInterface, QueryRunner } = require("typeorm"); + +module.exports = class emojiUrl1642611822809 { + name = 'emojiUrl1642611822809' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "emoji" RENAME COLUMN "url" TO "originalUrl"`); + await queryRunner.query(`ALTER TABLE "emoji" ADD "publicUrl" character varying(512) NOT NULL DEFAULT ''`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "publicUrl"`); + await queryRunner.query(`ALTER TABLE "emoji" RENAME COLUMN "originalUrl" TO "url"`); + } +} diff --git a/packages/backend/src/misc/populate-emojis.ts b/packages/backend/src/misc/populate-emojis.ts index b021ec46eb..26c05e5fa6 100644 --- a/packages/backend/src/misc/populate-emojis.ts +++ b/packages/backend/src/misc/populate-emojis.ts @@ -62,7 +62,8 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu if (emoji == null) return null; const isLocal = emoji.host == null; - const url = isLocal ? emoji.url : `${config.url}/proxy/image.png?${query({ url: emoji.url })}`; + const emojiUrl = emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のため + const url = isLocal ? emojiUrl : `${config.url}/proxy/image.png?${query({ url: emojiUrl })}`; return { name: emojiName, @@ -116,7 +117,7 @@ export async function prefetchEmojis(emojis: { name: string; host: string | null } const _emojis = emojisQuery.length > 0 ? await Emojis.find({ where: emojisQuery, - select: ['name', 'host', 'url'], + select: ['name', 'host', 'originalUrl', 'publicUrl'], }) : []; for (const emoji of _emojis) { cache.set(`${emoji.name} ${emoji.host}`, emoji); diff --git a/packages/backend/src/models/entities/emoji.ts b/packages/backend/src/models/entities/emoji.ts index 1146908a88..2e9c11d21c 100644 --- a/packages/backend/src/models/entities/emoji.ts +++ b/packages/backend/src/models/entities/emoji.ts @@ -32,13 +32,19 @@ export class Emoji { @Column('varchar', { length: 512, }) - public url: string; + public originalUrl: string; + + @Column('varchar', { + length: 512, + }) + public publicUrl: string; @Column('varchar', { length: 512, nullable: true, }) public uri: string | null; + // publicUrlの方のtypeが入る @Column('varchar', { length: 64, nullable: true, }) diff --git a/packages/backend/src/models/repositories/emoji.ts b/packages/backend/src/models/repositories/emoji.ts index 9d63ced811..b9dc6ed0ac 100644 --- a/packages/backend/src/models/repositories/emoji.ts +++ b/packages/backend/src/models/repositories/emoji.ts @@ -15,7 +15,8 @@ export class EmojiRepository extends Repository<Emoji> { name: emoji.name, category: emoji.category, host: emoji.host, - url: emoji.url, + // || emoji.originalUrl してるのは後方互換性のため + url: emoji.publicUrl || emoji.originalUrl, }; } diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index 92344430d6..0c06b12c9a 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -72,7 +72,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi let downloaded = false; try { - await downloadUrl(emoji.url, emojiPath); + await downloadUrl(emoji.originalUrl, emojiPath); downloaded = true; } catch (e) { // TODO: 何度か再試行 logger.error(e); diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts index ba4e3f0c08..d2b0eb269a 100644 --- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts @@ -67,8 +67,9 @@ export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, don category: emojiInfo.category, host: null, aliases: emojiInfo.aliases, - url: driveFile.url, - type: driveFile.type, + originalUrl: driveFile.url, + publicUrl: driveFile.webpublicUrl ?? driveFile.url, + type: driveFile.webpublicType ?? driveFile.type, }).then(x => Emojis.findOneOrFail(x.identifiers[0])); } diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index a0fdf7f239..6847925a51 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -320,14 +320,15 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr if ((tag.updated != null && exists.updatedAt == null) || (tag.id != null && exists.uri == null) || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) - || (tag.icon!.url !== exists.url) + || (tag.icon!.url !== exists.originalUrl) ) { await Emojis.update({ host, name, }, { uri: tag.id, - url: tag.icon!.url, + originalUrl: tag.icon!.url, + publicUrl: tag.icon!.url, updatedAt: new Date(), }); @@ -347,7 +348,8 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr host, name, uri: tag.id, - url: tag.icon!.url, + originalUrl: tag.icon!.url, + publicUrl: tag.icon!.url, updatedAt: new Date(), aliases: [], } as Partial<Emoji>).then(x => Emojis.findOneOrFail(x.identifiers[0])); diff --git a/packages/backend/src/remote/activitypub/renderer/emoji.ts b/packages/backend/src/remote/activitypub/renderer/emoji.ts index 9d08c8ba81..e7ae7d959a 100644 --- a/packages/backend/src/remote/activitypub/renderer/emoji.ts +++ b/packages/backend/src/remote/activitypub/renderer/emoji.ts @@ -9,6 +9,6 @@ export default (emoji: Emoji) => ({ icon: { type: 'Image', mediaType: emoji.type || 'image/png', - url: emoji.url, + url: emoji.publicUrl || emoji.originalUrl, // || emoji.originalUrl してるのは後方互換性のため }, }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index 72adda9dcb..1dfeae262f 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -45,8 +45,9 @@ export default define(meta, async (ps, me) => { category: null, host: null, aliases: [], - url: file.url, - type: file.type, + originalUrl: file.url, + publicUrl: file.webpublicUrl ?? file.url, + type: file.webpublicType ?? file.type, }).then(x => Emojis.findOneOrFail(x.identifiers[0])); await getConnection().queryResultCache!.remove(['meta_emojis']); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index de239c4f0d..4f53739793 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -54,7 +54,7 @@ export default define(meta, async (ps, me) => { try { // Create file - driveFile = await uploadFromUrl(emoji.url, null, null, null, false, true); + driveFile = await uploadFromUrl(emoji.originalUrl, null, null, null, false, true); } catch (e) { throw new ApiError(); } @@ -65,9 +65,9 @@ export default define(meta, async (ps, me) => { name: emoji.name, host: null, aliases: [], - url: driveFile.url, - type: driveFile.type, - fileId: driveFile.id, + originalUrl: driveFile.url, + publicUrl: driveFile.webpublicUrl ?? driveFile.url, + type: driveFile.webpublicType ?? driveFile.type, }).then(x => Emojis.findOneOrFail(x.identifiers[0])); await getConnection().queryResultCache!.remove(['meta_emojis']); diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index e0baa6a096..47f46419dd 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -81,19 +81,15 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, name: decodedReaction.name, host: decodedReaction.host, }, - select: ['name', 'host', 'url'], + select: ['name', 'host', 'originalUrl', 'publicUrl'], }); - if (emoji) { - emoji = { - name: emoji.host ? `${emoji.name}@${emoji.host}` : `${emoji.name}@.`, - url: emoji.url, - } as any; - } - publishNoteStream(note.id, 'reacted', { reaction: decodedReaction.reaction, - emoji: emoji, + emoji: emoji != null ? { + name: emoji.host ? `${emoji.name}@${emoji.host}` : `${emoji.name}@.`, + url: emoji.publicUrl || emoji.originalUrl, // || emoji.originalUrl してるのは後方互換性のため + } : null, userId: user.id, }); diff --git a/packages/backend/src/tools/add-emoji.ts b/packages/backend/src/tools/add-emoji.ts deleted file mode 100644 index a3f4b54c7e..0000000000 --- a/packages/backend/src/tools/add-emoji.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { initDb } from '@/db/postgre'; -import { genId } from '@/misc/gen-id'; - -async function main(name: string, url: string, alias?: string): Promise<any> { - await initDb(); - const { Emojis } = await import('@/models/index'); - - const aliases = alias != null ? [ alias ] : []; - - await Emojis.save({ - id: genId(), - host: null, - name, - url, - aliases, - updatedAt: new Date(), - }); -} - -const args = process.argv.slice(2); -const name = args[0]; -const url = args[1]; - -if (!name) throw new Error('require name'); -if (!url) throw new Error('require url'); - -main(name, url).then(() => { - console.log('success'); - process.exit(0); -}).catch(e => { - console.warn(e); - process.exit(1); -}); From 51d21fbe665a8ae75f80fd4dcd69bc6b6f87a54f Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 21 Jan 2022 18:49:04 +0900 Subject: [PATCH 75/81] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fb7a37e22..5bdf76b79c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - 後日別リポジトリとして復活予定です - Chat UIが削除されました - ノートに添付できるファイルの数が16に増えました +- カスタム絵文字にSVGを指定した場合、PNGに変換されて表示されるようになりました ### Improvements - カスタム絵文字一括編集機能 From b388b788922f736c923650d27a8b5cbdb7c939a3 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 21 Jan 2022 18:53:34 +0900 Subject: [PATCH 76/81] update dep --- packages/client/package.json | 4 +- packages/client/yarn.lock | 136 +++++++++++++++++------------------ 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index e03e9edc01..6c4cf764d0 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -38,7 +38,7 @@ "@types/websocket": "1.0.4", "@types/ws": "8.2.2", "@typescript-eslint/parser": "5.10.0", - "@vue/compiler-sfc": "3.2.27", + "@vue/compiler-sfc": "3.2.28", "abort-controller": "3.0.0", "autobind-decorator": "2.4.0", "autosize": "5.0.1", @@ -111,7 +111,7 @@ "uuid": "8.3.2", "v-debounce": "0.1.2", "vanilla-tilt": "1.7.2", - "vue": "3.2.27", + "vue": "3.2.28", "vue-loader": "17.0.0", "vue-prism-editor": "2.0.0-alpha.2", "vue-router": "4.0.5", diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index 07d1dbf9df..d983d01ba8 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -699,95 +699,95 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== -"@vue/compiler-core@3.2.27": - version "3.2.27" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.27.tgz#01bd5e5071f58f43e4184ba7fa810226799a5383" - integrity sha512-JyxAglSM/pb9paG5ZNuKrf5IUpzLzQA3khjWGF9oESELCLQlt6O3YyPMR2A69wIpYWrf5mScZ8YY8TJKOI/1kQ== +"@vue/compiler-core@3.2.28": + version "3.2.28" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.28.tgz#7f6aa4b167f0ae0413f3c36e507c898db06e8fe8" + integrity sha512-mQpfEjmHVxmWKaup0HL6tLMv2HqjjJu7XT4/q0IoUXYXC4xKG8lIVn5YChJqxBTLPuQjzas7u7i9L4PAWJZRtA== dependencies: "@babel/parser" "^7.16.4" - "@vue/shared" "3.2.27" + "@vue/shared" "3.2.28" estree-walker "^2.0.2" source-map "^0.6.1" -"@vue/compiler-dom@3.2.27": - version "3.2.27" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.27.tgz#a12163e3f3f1d5ff1969253eba4b4ea3e67bbd0f" - integrity sha512-NyQ7nEbopUBPUMHM4c3FPCbFbnQwptoPjW5Y5qfJ7hfiCNhOuhQsDNqi5JYKBxfpxiFNwjcN9F8t1AsnLrDloQ== +"@vue/compiler-dom@3.2.28": + version "3.2.28" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.28.tgz#cc32a987fee50673f25430df35ea943f252c23e6" + integrity sha512-KA4yXceLteKC7VykvPnViUixemQw3A+oii+deSbZJOQKQKVh1HLosI10qxa8ImPCyun41+wG3uGR+tW7eu1W6Q== dependencies: - "@vue/compiler-core" "3.2.27" - "@vue/shared" "3.2.27" + "@vue/compiler-core" "3.2.28" + "@vue/shared" "3.2.28" -"@vue/compiler-sfc@3.2.27": - version "3.2.27" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.27.tgz#43bf23fe24feacba334636fa7a332f9d8a532c8c" - integrity sha512-WyecUhLN5UAQAr2QlmG2nA56OEnhZJaBnSw0G1tazb9rwDuK0V9tnbIXbQgmQlx+x4sJxgg61yWGcIXfilTl3A== +"@vue/compiler-sfc@3.2.28": + version "3.2.28" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.28.tgz#0a576c09abc72d6a76b153133de6fd7599c182c3" + integrity sha512-zB0WznfEBb4CbGBHzhboHDKVO5nxbkbxxFo9iVlxObP7a9/qvA5kkZEuT7nXP52f3b3qEfmVTjIT23Lo1ndZdQ== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.27" - "@vue/compiler-dom" "3.2.27" - "@vue/compiler-ssr" "3.2.27" - "@vue/reactivity-transform" "3.2.27" - "@vue/shared" "3.2.27" + "@vue/compiler-core" "3.2.28" + "@vue/compiler-dom" "3.2.28" + "@vue/compiler-ssr" "3.2.28" + "@vue/reactivity-transform" "3.2.28" + "@vue/shared" "3.2.28" estree-walker "^2.0.2" magic-string "^0.25.7" postcss "^8.1.10" source-map "^0.6.1" -"@vue/compiler-ssr@3.2.27": - version "3.2.27" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.27.tgz#49aa3afd1602275aba3c3e331764984111a460bf" - integrity sha512-+l09t319iV7HVSrXfBw9OLwMZIPOFTXmHjZ61Bc5ZcwKqOYAR4uTurKpoXAfcSc5qs/q6WdE9jY3nrP0LUEMQQ== +"@vue/compiler-ssr@3.2.28": + version "3.2.28" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.28.tgz#411e8b3bdc3183b2acd35e6551734b34366d64e5" + integrity sha512-z8rck1PDTu20iLyip9lAvIhaO40DUJrw3Zv0mS4Apfh3PlfWpF5dhsO5g0dgt213wgYsQIYVIlU9cfrYapqRgg== dependencies: - "@vue/compiler-dom" "3.2.27" - "@vue/shared" "3.2.27" + "@vue/compiler-dom" "3.2.28" + "@vue/shared" "3.2.28" -"@vue/reactivity-transform@3.2.27": - version "3.2.27" - resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.27.tgz#610b6df174cdceba6de1376f3218736c3b0e753d" - integrity sha512-67//61ObGxGnVrPhjygocb24eYUh+TFMhkm7szm8v5XdKXjkNl7qgIOflwGvUnwuIRJmr9nZ7+PvY0fL+H2upA== +"@vue/reactivity-transform@3.2.28": + version "3.2.28" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.28.tgz#e0abf86694f4d182f974fbac934fc3e23e0a6d9b" + integrity sha512-zE8idNkOPnBDd2tKSIk84hOQZ+jXKvSy5FoIIVlcNEJHnCFnQ3maqeSJ9KoB2Rf6EXUhFTiTDNRlYlXmT2uHbQ== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.27" - "@vue/shared" "3.2.27" + "@vue/compiler-core" "3.2.28" + "@vue/shared" "3.2.28" estree-walker "^2.0.2" magic-string "^0.25.7" -"@vue/reactivity@3.2.27": - version "3.2.27" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.27.tgz#db7c4eefded938a8974e768b7b8f9ff4fad34a62" - integrity sha512-QPfIQEJidRGIu/mPexhcB4csp1LEg2Nr+/QE72MnXs/OYDtFErhC9FxIyymkxp/xvAgL5wsnSOuDD6zWF42vRQ== +"@vue/reactivity@3.2.28": + version "3.2.28" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.28.tgz#1c3c7f434372edd867f937151897fca7efc4be18" + integrity sha512-WamM5LGv7JIarW+EYAzYFqYonZXjTnOjNW0sBO93jRE9I1ReAwfH8NvQXkPA3JZ3fuF6SGDdG8Y9/+dKjd/1Gw== dependencies: - "@vue/shared" "3.2.27" + "@vue/shared" "3.2.28" -"@vue/runtime-core@3.2.27": - version "3.2.27" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.27.tgz#1ea8acd30071f44bcf3915b088ff5d993a9d3154" - integrity sha512-NJrjuViHJyrT4bwIocbE4XDaDlA1Pj61pQlneZZdFEvgdMLlhzCCiJ4WZnWcohYQeisUAZjEFKK8GjQieDPFbw== +"@vue/runtime-core@3.2.28": + version "3.2.28" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.28.tgz#69d8eede42957a1660b964004aa002982ae36a41" + integrity sha512-sVbBMFUt42JatTlXbdH6tVcLPw1eEOrrVQWI+j6/nJVzR852RURaT6DhdR0azdYscxq4xmmBctE0VQmlibBOFw== dependencies: - "@vue/reactivity" "3.2.27" - "@vue/shared" "3.2.27" + "@vue/reactivity" "3.2.28" + "@vue/shared" "3.2.28" -"@vue/runtime-dom@3.2.27": - version "3.2.27" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.27.tgz#d13f9f5f27815041f5e9f493d77121955e3f7166" - integrity sha512-tlnKkvBSkV7MPUp/wRFsYcv67U1rUeZTPfpPzq5Kpmw5NNGkY6J075fFBH2k0MNxDucXS+qfStNrxAyGTUMkSA== +"@vue/runtime-dom@3.2.28": + version "3.2.28" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.28.tgz#b5a0cf38daed5534edbc95790f4eeac97dff2003" + integrity sha512-Jg7cxZanEXXGu1QnZILFLnDrM+MIFN8VAullmMZiJEZziHvhygRMpi0ahNy/8OqGwtTze1JNhLdHRBO+q2hbmg== dependencies: - "@vue/runtime-core" "3.2.27" - "@vue/shared" "3.2.27" + "@vue/runtime-core" "3.2.28" + "@vue/shared" "3.2.28" csstype "^2.6.8" -"@vue/server-renderer@3.2.27": - version "3.2.27" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.27.tgz#a0caee0f7571fa741e4efaa72951a1cd70d61551" - integrity sha512-dZnzkFCDe6A/GIe/F1LcG6lWpprHVh62DjTv8wubtkHwfJWOmOeHp+KvPDRrswL/L3ghsm+E31xY+pvkgM3pbQ== +"@vue/server-renderer@3.2.28": + version "3.2.28" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.28.tgz#235944dc4d969fadd387f62acc2eb8b8d50008a2" + integrity sha512-S+MhurgkPabRvhdDl8R6efKBmniJqBbbWIYTXADaJIKFLFLQCW4gcYUTbxuebzk6j3z485vpekhrHHymTF52Pg== dependencies: - "@vue/compiler-ssr" "3.2.27" - "@vue/shared" "3.2.27" + "@vue/compiler-ssr" "3.2.28" + "@vue/shared" "3.2.28" -"@vue/shared@3.2.27": - version "3.2.27" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.27.tgz#d5c6e574ee1afd55043470bf70b15772da4e19a2" - integrity sha512-rpAn9k6O08Lvo7ekBIAnkOukX/4EsEQLPrRJBKhIEasMsOI5eX0f6mq1sDUSY7cgAqWw2d7QtP74CWxdXoyKxA== +"@vue/shared@3.2.28": + version "3.2.28" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.28.tgz#5b0b1840432031d0ea1adff633b356a503e87048" + integrity sha512-eMQ8s9j8FpbGHlgUAaj/coaG3Q8YtMsoWL/RIHTsE3Ex7PUTyr7V91vB5HqWB5Sn8m4RXTHGO22/skoTUYvp0A== "@webassemblyjs/ast@1.11.0": version "1.11.0" @@ -6153,16 +6153,16 @@ vue-svg-loader@0.17.0-beta.2: semver "^7.3.2" svgo "^1.3.2" -vue@3.2.27: - version "3.2.27" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.27.tgz#dc898b3cf7393a620ed5e4a91e5fa8c1ed1ba67a" - integrity sha512-p1cH8Q6eaPwvANCjFQj497a914cxXKKwOG3Lg9USddTOrn4/zFMKjn9dnovkx+L8VtFaNgbVqW8mLJS/eTA6xw== +vue@3.2.28: + version "3.2.28" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.28.tgz#840d193bf9713f57a365ef115c4b1286d43e0e5d" + integrity sha512-U+jBwVh3RQ9AgceLFdT7i2FFujoC+kYuGrKo5y8aLluWKZWPS40WgA2pyYHaiSX9ydCbEGr3rc/JzdqskzD95g== dependencies: - "@vue/compiler-dom" "3.2.27" - "@vue/compiler-sfc" "3.2.27" - "@vue/runtime-dom" "3.2.27" - "@vue/server-renderer" "3.2.27" - "@vue/shared" "3.2.27" + "@vue/compiler-dom" "3.2.28" + "@vue/compiler-sfc" "3.2.28" + "@vue/runtime-dom" "3.2.28" + "@vue/server-renderer" "3.2.28" + "@vue/shared" "3.2.28" vuedraggable@4.0.1: version "4.0.1" From 331afcb96a638d436f7fa925bc395ef04057a147 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 21 Jan 2022 20:17:31 +0900 Subject: [PATCH 77/81] feat(client): make possible to switch account instantly in post form --- CHANGELOG.md | 1 + packages/client/src/account.ts | 86 ++++++++++++------- packages/client/src/components/post-form.vue | 46 +++++++++- packages/client/src/components/ui/menu.vue | 2 +- .../src/ui/_common_/sidebar-for-mobile.vue | 6 +- packages/client/src/ui/_common_/sidebar.vue | 6 +- packages/client/src/ui/classic.header.vue | 6 +- packages/client/src/ui/classic.sidebar.vue | 6 +- 8 files changed, 119 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bdf76b79c..5228be120b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### Improvements - カスタム絵文字一括編集機能 - カスタム絵文字一括インポート +- 投稿フォームで一時的に投稿するアカウントを切り替えられるように ### Bugfixes diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index 76a0d2bd00..5a935e1dc7 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -129,7 +129,12 @@ export async function login(token: Account['token'], redirect?: string) { unisonReload(); } -export async function openAccountMenu(ev: MouseEvent) { +export async function openAccountMenu(opts: { + includeCurrentAccount?: boolean; + withExtraOperation: boolean; + active?: misskey.entities.UserDetailed['id']; + onChoose?: (account: misskey.entities.UserDetailed) => void; +}, ev: MouseEvent) { function showSigninDialog() { popup(import('@/components/signin-dialog.vue'), {}, { done: res => { @@ -148,7 +153,7 @@ export async function openAccountMenu(ev: MouseEvent) { }, 'closed'); } - async function switchAccount(account: any) { + async function switchAccount(account: misskey.entities.UserDetailed) { const storedAccounts = await getAccounts(); const token = storedAccounts.find(x => x.id === account.id).token; switchAccountWithToken(token); @@ -161,41 +166,58 @@ export async function openAccountMenu(ev: MouseEvent) { const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id)); const accountsPromise = api('users/show', { userIds: storedAccounts.map(x => x.id) }); + function createItem(account: misskey.entities.UserDetailed) { + return { + type: 'user', + user: account, + active: opts.active != null ? opts.active === account.id : false, + action: () => { + if (opts.onChoose) { + opts.onChoose(account); + } else { + switchAccount(account); + } + }, + }; + } + const accountItemPromises = storedAccounts.map(a => new Promise(res => { accountsPromise.then(accounts => { const account = accounts.find(x => x.id === a.id); if (account == null) return res(null); - res({ - type: 'user', - user: account, - action: () => { switchAccount(account); } - }); + res(createItem(account)); }); })); - popupMenu([...[{ - type: 'link', - text: i18n.locale.profile, - to: `/@${ $i.username }`, - avatar: $i, - }, null, ...accountItemPromises, { - icon: 'fas fa-plus', - text: i18n.locale.addAccount, - action: () => { - popupMenu([{ - text: i18n.locale.existingAccount, - action: () => { showSigninDialog(); }, - }, { - text: i18n.locale.createAccount, - action: () => { createAccount(); }, - }], ev.currentTarget || ev.target); - }, - }, { - type: 'link', - icon: 'fas fa-users', - text: i18n.locale.manageAccounts, - to: `/settings/accounts`, - }]], ev.currentTarget || ev.target, { - align: 'left' - }); + if (opts.withExtraOperation) { + popupMenu([...[{ + type: 'link', + text: i18n.locale.profile, + to: `/@${ $i.username }`, + avatar: $i, + }, null, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, { + icon: 'fas fa-plus', + text: i18n.locale.addAccount, + action: () => { + popupMenu([{ + text: i18n.locale.existingAccount, + action: () => { showSigninDialog(); }, + }, { + text: i18n.locale.createAccount, + action: () => { createAccount(); }, + }], ev.currentTarget || ev.target); + }, + }, { + type: 'link', + icon: 'fas fa-users', + text: i18n.locale.manageAccounts, + to: `/settings/accounts`, + }]], ev.currentTarget || ev.target, { + align: 'left' + }); + } else { + popupMenu([...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises], ev.currentTarget || ev.target, { + align: 'left' + }); + } } diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue index 60b062f054..3ab8af3092 100644 --- a/packages/client/src/components/post-form.vue +++ b/packages/client/src/components/post-form.vue @@ -8,6 +8,9 @@ > <header> <button v-if="!fixed" class="cancel _button" @click="cancel"><i class="fas fa-times"></i></button> + <button v-click-anime class="account _button" @click="openAccountMenu"> + <MkAvatar :user="postAccount ?? $i" class="avatar"/> + </button> <div> <span class="text-count" :class="{ over: textLength > maxTextLength }">{{ maxTextLength - textLength }}</span> <span v-if="localOnly" class="local-only"><i class="fas fa-biohazard"></i></span> @@ -83,7 +86,7 @@ import { throttle } from 'throttle-debounce'; import MkInfo from '@/components/ui/info.vue'; import { i18n } from '@/i18n'; import { instance } from '@/instance'; -import { $i } from '@/account'; +import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account'; const modal = inject('modal'); @@ -553,8 +556,15 @@ async function post() { } } + let token = undefined; + + if (postAccount) { + const storedAccounts = await getAccounts(); + token = storedAccounts.find(x => x.id === postAccount.id)?.token; + } + posting = true; - os.api('notes/create', data).then(() => { + os.api('notes/create', data, token).then(() => { clear(); nextTick(() => { deleteDraft(); @@ -585,7 +595,7 @@ function insertMention() { }); } -async function insertEmoji(ev) { +async function insertEmoji(ev: MouseEvent) { os.openEmojiPicker(ev.currentTarget || ev.target, {}, textareaEl); } @@ -602,6 +612,23 @@ function showActions(ev) { })), ev.currentTarget || ev.target); } +let postAccount = $ref<misskey.entities.UserDetailed | null>(null); + +function openAccountMenu(ev: MouseEvent) { + openAccountMenu_({ + withExtraOperation: false, + includeCurrentAccount: true, + active: postAccount != null ? postAccount.id : $i.id, + onChoose: (account) => { + if (account.id === $i.id) { + postAccount = null; + } else { + postAccount = account; + } + }, + }, ev); +} + onMounted(() => { if (props.autofocus) { focus(); @@ -678,6 +705,19 @@ onMounted(() => { line-height: 66px; } + > .account { + height: 100%; + aspect-ratio: 1/1; + display: inline-flex; + vertical-align: bottom; + + > .avatar { + width: 28px; + height: 28px; + margin: auto; + } + } + > div { position: absolute; top: 0; diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue index 6f3f277b11..41165c8d33 100644 --- a/packages/client/src/components/ui/menu.vue +++ b/packages/client/src/components/ui/menu.vue @@ -24,7 +24,7 @@ <span>{{ item.text }}</span> <span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span> </a> - <button v-else-if="item.type === 'user'" :tabindex="i" class="_button item" @click="clicked(item.action, $event)"> + <button v-else-if="item.type === 'user'" :tabindex="i" class="_button item" :class="{ active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)"> <MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/> <span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span> </button> diff --git a/packages/client/src/ui/_common_/sidebar-for-mobile.vue b/packages/client/src/ui/_common_/sidebar-for-mobile.vue index 5babdb98a8..afcc50725b 100644 --- a/packages/client/src/ui/_common_/sidebar-for-mobile.vue +++ b/packages/client/src/ui/_common_/sidebar-for-mobile.vue @@ -61,7 +61,11 @@ export default defineComponent({ otherMenuItemIndicated, post: os.post, search, - openAccountMenu, + openAccountMenu:(ev) => { + openAccountMenu({ + withExtraOperation: true, + }, ev); + }, more: () => { os.popup(import('@/components/launch-pad.vue'), {}, { }, 'closed'); diff --git a/packages/client/src/ui/_common_/sidebar.vue b/packages/client/src/ui/_common_/sidebar.vue index fa712ba45d..94baacbee9 100644 --- a/packages/client/src/ui/_common_/sidebar.vue +++ b/packages/client/src/ui/_common_/sidebar.vue @@ -76,7 +76,11 @@ export default defineComponent({ iconOnly, post: os.post, search, - openAccountMenu, + openAccountMenu:(ev) => { + openAccountMenu({ + withExtraOperation: true, + }, ev); + }, more: () => { os.popup(import('@/components/launch-pad.vue'), {}, { }, 'closed'); diff --git a/packages/client/src/ui/classic.header.vue b/packages/client/src/ui/classic.header.vue index 3563e8a888..699b992668 100644 --- a/packages/client/src/ui/classic.header.vue +++ b/packages/client/src/ui/classic.header.vue @@ -105,7 +105,11 @@ export default defineComponent({ }, 'closed'); }, - openAccountMenu, + openAccountMenu:(ev) => { + openAccountMenu({ + withExtraOperation: true, + }, ev); + }, } }); </script> diff --git a/packages/client/src/ui/classic.sidebar.vue b/packages/client/src/ui/classic.sidebar.vue index cc9d7a9b48..afbca06c8e 100644 --- a/packages/client/src/ui/classic.sidebar.vue +++ b/packages/client/src/ui/classic.sidebar.vue @@ -125,7 +125,11 @@ export default defineComponent({ }, 'closed'); }, - openAccountMenu, + openAccountMenu:(ev) => { + openAccountMenu({ + withExtraOperation: true, + }, ev); + }, } }); </script> From 10609eebfa795158a9db9451cc74203c838d6887 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 21 Jan 2022 20:19:56 +0900 Subject: [PATCH 78/81] =?UTF-8?q?=E6=8A=95=E7=A8=BF=E3=81=97=E3=81=9F?= =?UTF-8?q?=E3=82=89=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3=E3=83=88=E3=82=92?= =?UTF-8?q?=E5=85=83=E3=81=AB=E6=88=BB=E3=81=99=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/post-form.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue index 3ab8af3092..a016df7e0b 100644 --- a/packages/client/src/components/post-form.vue +++ b/packages/client/src/components/post-form.vue @@ -575,6 +575,7 @@ async function post() { localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); } posting = false; + postAccount = null; }); }).catch(err => { posting = false; From 901453ba7dd9827b1fd17ac2a12cf347fa20fbc4 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 21 Jan 2022 20:51:07 +0900 Subject: [PATCH 79/81] chore(client): add tooltip --- locales/ja-JP.yml | 1 + packages/client/src/components/post-form.vue | 30 ++++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 59ce5ee84f..b3279d78b8 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -746,6 +746,7 @@ notRecommended: "非推奨" botProtection: "Botプロテクション" instanceBlocking: "インスタンスブロック" selectAccount: "アカウントを選択" +switchAccount: "アカウントを切り替え" enabled: "有効" disabled: "無効" quickAction: "クイックアクション" diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue index a016df7e0b..0dcec26932 100644 --- a/packages/client/src/components/post-form.vue +++ b/packages/client/src/components/post-form.vue @@ -8,28 +8,28 @@ > <header> <button v-if="!fixed" class="cancel _button" @click="cancel"><i class="fas fa-times"></i></button> - <button v-click-anime class="account _button" @click="openAccountMenu"> + <button v-click-anime v-tooltip="i18n.locale.switchAccount" class="account _button" @click="openAccountMenu"> <MkAvatar :user="postAccount ?? $i" class="avatar"/> </button> <div> <span class="text-count" :class="{ over: textLength > maxTextLength }">{{ maxTextLength - textLength }}</span> <span v-if="localOnly" class="local-only"><i class="fas fa-biohazard"></i></span> - <button ref="visibilityButton" v-tooltip="$ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> + <button ref="visibilityButton" v-tooltip="i18n.locale.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> <span v-if="visibility === 'public'"><i class="fas fa-globe"></i></span> <span v-if="visibility === 'home'"><i class="fas fa-home"></i></span> <span v-if="visibility === 'followers'"><i class="fas fa-unlock"></i></span> <span v-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span> </button> - <button v-tooltip="$ts.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="fas fa-file-code"></i></button> + <button v-tooltip="i18n.locale.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="fas fa-file-code"></i></button> <button class="submit _buttonGradate" :disabled="!canPost" data-cy-open-post-form-submit @click="post">{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button> </div> </header> <div class="form" :class="{ fixed }"> <XNoteSimple v-if="reply" class="preview" :note="reply"/> <XNoteSimple v-if="renote" class="preview" :note="renote"/> - <div v-if="quoteId" class="with-quote"><i class="fas fa-quote-left"></i> {{ $ts.quoteAttached }}<button @click="quoteId = null"><i class="fas fa-times"></i></button></div> + <div v-if="quoteId" class="with-quote"><i class="fas fa-quote-left"></i> {{ i18n.locale.quoteAttached }}<button @click="quoteId = null"><i class="fas fa-times"></i></button></div> <div v-if="visibility === 'specified'" class="to-specified"> - <span style="margin-right: 8px;">{{ $ts.recipient }}</span> + <span style="margin-right: 8px;">{{ i18n.locale.recipient }}</span> <div class="visibleUsers"> <span v-for="u in visibleUsers" :key="u.id"> <MkAcct :user="u"/> @@ -38,21 +38,21 @@ <button class="_buttonPrimary" @click="addVisibleUser"><i class="fas fa-plus fa-fw"></i></button> </div> </div> - <MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ $ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ $ts.add }}</button></MkInfo> - <input v-show="useCw" ref="cwInputEl" v-model="cw" class="cw" :placeholder="$ts.annotation" @keydown="onKeydown"> + <MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ i18n.locale.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.locale.add }}</button></MkInfo> + <input v-show="useCw" ref="cwInputEl" v-model="cw" class="cw" :placeholder="i18n.locale.annotation" @keydown="onKeydown"> <textarea ref="textareaEl" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> - <input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" class="hashtags" :placeholder="$ts.hashtags" list="hashtags"> + <input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" class="hashtags" :placeholder="i18n.locale.hashtags" list="hashtags"> <XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> <XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/> <XNotePreview v-if="showPreview" class="preview" :text="text"/> <footer> - <button v-tooltip="$ts.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button> - <button v-tooltip="$ts.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="fas fa-poll-h"></i></button> - <button v-tooltip="$ts.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="fas fa-eye-slash"></i></button> - <button v-tooltip="$ts.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button> - <button v-tooltip="$ts.hashtags" class="_button" :class="{ active: withHashtags }" @click="withHashtags = !withHashtags"><i class="fas fa-hashtag"></i></button> - <button v-tooltip="$ts.emoji" class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button> - <button v-if="postFormActions.length > 0" v-tooltip="$ts.plugin" class="_button" @click="showActions"><i class="fas fa-plug"></i></button> + <button v-tooltip="i18n.locale.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button> + <button v-tooltip="i18n.locale.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="fas fa-poll-h"></i></button> + <button v-tooltip="i18n.locale.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="fas fa-eye-slash"></i></button> + <button v-tooltip="i18n.locale.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button> + <button v-tooltip="i18n.locale.hashtags" class="_button" :class="{ active: withHashtags }" @click="withHashtags = !withHashtags"><i class="fas fa-hashtag"></i></button> + <button v-tooltip="i18n.locale.emoji" class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button> + <button v-if="postFormActions.length > 0" v-tooltip="i18n.locale.plugin" class="_button" @click="showActions"><i class="fas fa-plug"></i></button> </footer> <datalist id="hashtags"> <option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/> From 2c471966002bd62a85ca463aac35f4276fed5d50 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 21 Jan 2022 21:16:12 +0900 Subject: [PATCH 80/81] wip: refactor(client): migrate components to composition api --- packages/client/src/pages/about-misskey.vue | 112 +++++++++----------- 1 file changed, 51 insertions(+), 61 deletions(-) diff --git a/packages/client/src/pages/about-misskey.vue b/packages/client/src/pages/about-misskey.vue index 855a21e493..efb44adffd 100644 --- a/packages/client/src/pages/about-misskey.vue +++ b/packages/client/src/pages/about-misskey.vue @@ -3,36 +3,36 @@ <MkSpacer :content-max="600" :margin-min="20"> <div class="_formRoot znqjceqz"> <div id="debug"></div> - <div ref="about" v-panel class="_formBlock about" :class="{ playing: easterEggEngine != null }"> + <div ref="containerEl" v-panel class="_formBlock about" :class="{ playing: easterEggEngine != null }"> <img src="/client-assets/about-icon.png" alt="" class="icon" draggable="false" @load="iconLoaded" @click="gravity"/> <div class="misskey">Misskey</div> <div class="version">v{{ version }}</div> <span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"><MkEmoji class="emoji" :emoji="emoji.emoji" :custom-emojis="$instance.emojis" :is-reaction="false" :normal="true" :no-style="true"/></span> </div> <div class="_formBlock" style="text-align: center;"> - {{ $ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ $ts.learnMore }}</a> + {{ i18n.locale._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ i18n.locale.learnMore }}</a> </div> <FormSection> <div class="_formLinks"> <FormLink to="https://github.com/misskey-dev/misskey" external> <template #icon><i class="fas fa-code"></i></template> - {{ $ts._aboutMisskey.source }} + {{ i18n.locale._aboutMisskey.source }} <template #suffix>GitHub</template> </FormLink> <FormLink to="https://crowdin.com/project/misskey" external> <template #icon><i class="fas fa-language"></i></template> - {{ $ts._aboutMisskey.translation }} + {{ i18n.locale._aboutMisskey.translation }} <template #suffix>Crowdin</template> </FormLink> <FormLink to="https://www.patreon.com/syuilo" external> <template #icon><i class="fas fa-hand-holding-medical"></i></template> - {{ $ts._aboutMisskey.donate }} + {{ i18n.locale._aboutMisskey.donate }} <template #suffix>Patreon</template> </FormLink> </div> </FormSection> <FormSection> - <template #label>{{ $ts._aboutMisskey.contributors }}</template> + <template #label>{{ i18n.locale._aboutMisskey.contributors }}</template> <div class="_formLinks"> <FormLink to="https://github.com/syuilo" external>@syuilo</FormLink> <FormLink to="https://github.com/AyaMorisawa" external>@AyaMorisawa</FormLink> @@ -44,20 +44,20 @@ <FormLink to="https://github.com/u1-liquid" external>@u1-liquid</FormLink> <FormLink to="https://github.com/marihachi" external>@marihachi</FormLink> </div> - <template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ $ts._aboutMisskey.allContributors }}</MkLink></template> + <template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ i18n.locale._aboutMisskey.allContributors }}</MkLink></template> </FormSection> <FormSection> - <template #label><Mfm text="$[jelly ❤]"/> {{ $ts._aboutMisskey.patrons }}</template> + <template #label><Mfm text="$[jelly ❤]"/> {{ i18n.locale._aboutMisskey.patrons }}</template> <div v-for="patron in patrons" :key="patron">{{ patron }}</div> - <template #caption>{{ $ts._aboutMisskey.morePatrons }}</template> + <template #caption>{{ i18n.locale._aboutMisskey.morePatrons }}</template> </FormSection> </div> </MkSpacer> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { nextTick, onBeforeUnmount } from 'vue'; import { version } from '@/config'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; @@ -65,6 +65,8 @@ import MkKeyValue from '@/components/key-value.vue'; import MkLink from '@/components/link.vue'; import { physics } from '@/scripts/physics'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; const patrons = [ 'まっちゃとーにゅ', @@ -145,58 +147,46 @@ const patrons = [ '蝉暮せせせ', ]; -export default defineComponent({ - components: { - FormSection, - FormLink, - MkKeyValue, - MkLink, - }, +let easterEggReady = false; +let easterEggEmojis = $ref([]); +let easterEggEngine = $ref(null); +const containerEl = $ref<HTMLElement>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.aboutMisskey, - icon: null - }, - version, - patrons, - easterEggReady: false, - easterEggEmojis: [], - easterEggEngine: null, - } - }, - - beforeUnmount() { - if (this.easterEggEngine) { - this.easterEggEngine.stop(); - } - }, - - methods: { - iconLoaded() { - const emojis = this.$store.state.reactions; - const containerWidth = this.$refs.about.offsetWidth; - for (let i = 0; i < 32; i++) { - this.easterEggEmojis.push({ - id: i.toString(), - top: -(128 + (Math.random() * 256)), - left: (Math.random() * containerWidth), - emoji: emojis[Math.floor(Math.random() * emojis.length)], - }); - } - - this.$nextTick(() => { - this.easterEggReady = true; - }); - }, - - gravity() { - if (!this.easterEggReady) return; - this.easterEggReady = false; - this.easterEggEngine = physics(this.$refs.about); - } +function iconLoaded() { + const emojis = defaultStore.state.reactions; + const containerWidth = containerEl.offsetWidth; + for (let i = 0; i < 32; i++) { + easterEggEmojis.push({ + id: i.toString(), + top: -(128 + (Math.random() * 256)), + left: (Math.random() * containerWidth), + emoji: emojis[Math.floor(Math.random() * emojis.length)], + }); } + + nextTick(() => { + easterEggReady = true; + }); +} + +function gravity() { + if (!easterEggReady) return; + easterEggReady = false; + easterEggEngine = physics(containerEl); +} + +onBeforeUnmount(() => { + if (easterEggEngine) { + easterEggEngine.stop(); + } +}); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.aboutMisskey, + icon: null, + bg: 'var(--bg)', + }, }); </script> From 8cdc619f8f9ddb2a35e9192aba0a810eeaceb233 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 21 Jan 2022 21:26:39 +0900 Subject: [PATCH 81/81] chore(client): add #misskey button --- packages/client/src/pages/about-misskey.vue | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/client/src/pages/about-misskey.vue b/packages/client/src/pages/about-misskey.vue index efb44adffd..8119f33051 100644 --- a/packages/client/src/pages/about-misskey.vue +++ b/packages/client/src/pages/about-misskey.vue @@ -12,6 +12,9 @@ <div class="_formBlock" style="text-align: center;"> {{ i18n.locale._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ i18n.locale.learnMore }}</a> </div> + <div class="_formBlock" style="text-align: center;"> + <MkButton primary rounded inline @click="iLoveMisskey">I <Mfm text="$[jelly ❤]"/> #Misskey</MkButton> + </div> <FormSection> <div class="_formLinks"> <FormLink to="https://github.com/misskey-dev/misskey" external> @@ -61,12 +64,13 @@ import { nextTick, onBeforeUnmount } from 'vue'; import { version } from '@/config'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; -import MkKeyValue from '@/components/key-value.vue'; +import MkButton from '@/components/ui/button.vue'; import MkLink from '@/components/link.vue'; import { physics } from '@/scripts/physics'; import * as symbols from '@/symbols'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; +import * as os from '@/os'; const patrons = [ 'まっちゃとーにゅ', @@ -175,6 +179,12 @@ function gravity() { easterEggEngine = physics(containerEl); } +function iLoveMisskey() { + os.post({ + initialText: 'I $[jelly ❤] #Misskey', + }); +} + onBeforeUnmount(() => { if (easterEggEngine) { easterEggEngine.stop();