summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2025-12-30 14:32:40 +0900
committerGitHub <noreply@github.com>2025-12-30 14:32:40 +0900
commit4285303c8155dd91be7dcbb865d5e8f7cb0e1c71 (patch)
tree51a193c90401ada45f25921e4efdc07b46095ac5
parentenhance(frontend): ウィジェットの設定画面を改良 (#17033) (diff)
downloadmisskey-4285303c8155dd91be7dcbb865d5e8f7cb0e1c71.tar.gz
misskey-4285303c8155dd91be7dcbb865d5e8f7cb0e1c71.tar.bz2
misskey-4285303c8155dd91be7dcbb865d5e8f7cb0e1c71.zip
fix(frontend): follow-up of #17033 (#17047)
* wip * fix * ref -> reactive * tweak throttle threshold * tweak throttle threshold * rss設定にはmanualSaveを使用するように * Update MkWidgetSettingsDialog.vue --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
-rw-r--r--packages/frontend/src/components/MkForm.vue6
-rw-r--r--packages/frontend/src/components/MkWidgetSettingsDialog.vue11
-rw-r--r--packages/frontend/src/utility/form.ts10
-rw-r--r--packages/frontend/src/widgets/WidgetRss.vue6
-rw-r--r--packages/frontend/src/widgets/WidgetRssTicker.vue3
-rw-r--r--packages/frontend/src/widgets/widget.ts27
6 files changed, 36 insertions, 27 deletions
diff --git a/packages/frontend/src/components/MkForm.vue b/packages/frontend/src/components/MkForm.vue
index 750ffa77df..711aa611c3 100644
--- a/packages/frontend/src/components/MkForm.vue
+++ b/packages/frontend/src/components/MkForm.vue
@@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="Object.keys(form).filter(item => !form[item].hidden).length > 0" class="_gaps_m">
<template v-for="v, k in form">
<template v-if="typeof v.hidden == 'function' ? v.hidden(values) : v.hidden"></template>
- <MkInput v-else-if="v.type === 'number'" v-model="values[k]" type="number" :step="v.step || 1">
+ <MkInput v-else-if="v.type === 'number'" v-model="values[k]" type="number" :step="v.step || 1" :manualSave="v.manualSave">
<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
<template v-if="v.description" #caption>{{ v.description }}</template>
</MkInput>
- <MkInput v-else-if="v.type === 'string' && !v.multiline" v-model="values[k]" type="text" :mfmAutocomplete="v.treatAsMfm">
+ <MkInput v-else-if="v.type === 'string' && !v.multiline" v-model="values[k]" type="text" :mfmAutocomplete="v.treatAsMfm" :manualSave="v.manualSave">
<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
<template v-if="v.description" #caption>{{ v.description }}</template>
</MkInput>
- <MkTextarea v-else-if="v.type === 'string' && v.multiline" v-model="values[k]" :mfmAutocomplete="v.treatAsMfm" :mfmPreview="v.treatAsMfm">
+ <MkTextarea v-else-if="v.type === 'string' && v.multiline" v-model="values[k]" :mfmAutocomplete="v.treatAsMfm" :mfmPreview="v.treatAsMfm" :manualSave="v.manualSave">
<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
<template v-if="v.description" #caption>{{ v.description }}</template>
</MkTextarea>
diff --git a/packages/frontend/src/components/MkWidgetSettingsDialog.vue b/packages/frontend/src/components/MkWidgetSettingsDialog.vue
index cebbe93986..0dc26e054d 100644
--- a/packages/frontend/src/components/MkWidgetSettingsDialog.vue
+++ b/packages/frontend/src/components/MkWidgetSettingsDialog.vue
@@ -29,7 +29,6 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<component
:is="`widget-${widgetName}`"
- :key="currentId"
:widget="{ name: widgetName, id: '__PREVIEW__', data: settings }"
></component>
</div>
@@ -48,13 +47,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { reactive, useTemplateRef, ref, computed, watch, onBeforeUnmount, onMounted } from 'vue';
+import MkPreviewWithControls from './MkPreviewWithControls.vue';
+import type { Form } from '@/utility/form.js';
import { deepClone } from '@/utility/clone.js';
-import { genId } from '@/utility/id.js';
import { i18n } from '@/i18n.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
-import MkPreviewWithControls from './MkPreviewWithControls.vue';
import MkForm from '@/components/MkForm.vue';
-import type { Form } from '@/utility/form.js';
const props = defineProps<{
widgetName: string;
@@ -71,11 +69,6 @@ const emit = defineEmits<{
const dialog = useTemplateRef('dialog');
const settings = reactive<Record<string, any>>(deepClone(props.currentSettings));
-const currentId = ref(genId());
-
-watch(settings, () => {
- currentId.value = genId();
-});
function save() {
emit('saved', deepClone(settings));
diff --git a/packages/frontend/src/utility/form.ts b/packages/frontend/src/utility/form.ts
index cb4a227f67..fe89333285 100644
--- a/packages/frontend/src/utility/form.ts
+++ b/packages/frontend/src/utility/form.ts
@@ -25,6 +25,7 @@ export interface StringFormItem extends FormItemBase {
required?: boolean;
multiline?: boolean;
treatAsMfm?: boolean;
+ manualSave?: boolean;
}
export interface NumberFormItem extends FormItemBase {
@@ -33,6 +34,7 @@ export interface NumberFormItem extends FormItemBase {
description?: string;
required?: boolean;
step?: number;
+ manualSave?: boolean;
}
export interface BooleanFormItem extends FormItemBase {
@@ -145,3 +147,11 @@ type GetItemType<Item extends FormItem> =
export type GetFormResultType<F extends Form> = {
[P in keyof F]: GetItemType<F[P]>;
};
+
+export function getDefaultFormValues<F extends FormWithDefault>(form: F): GetFormResultType<F> {
+ const result = {} as GetFormResultType<F>;
+ for (const key of Object.keys(form) as (keyof F)[]) {
+ result[key] = form[key].default as GetItemType<F[typeof key]>;
+ }
+ return result;
+}
diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue
index e5499aa0da..5fa9e833b8 100644
--- a/packages/frontend/src/widgets/WidgetRss.vue
+++ b/packages/frontend/src/widgets/WidgetRss.vue
@@ -28,7 +28,6 @@ import { useWidgetPropsManager } from './widget.js';
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
import MkContainer from '@/components/MkContainer.vue';
-import { i18n } from '@/i18n.js';
const name = 'rss';
@@ -36,6 +35,7 @@ const widgetPropsDef = {
url: {
type: 'string',
default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
+ manualSave: true,
},
refreshIntervalSec: {
type: 'number',
@@ -68,7 +68,7 @@ const fetching = ref(true);
const fetchEndpoint = computed(() => {
const url = new URL('/api/fetch-rss', base);
url.searchParams.set('url', widgetProps.url);
- return url;
+ return url.toString();
});
const intervalClear = ref<(() => void) | undefined>();
@@ -83,7 +83,7 @@ const tick = () => {
});
};
-watch(() => fetchEndpoint, tick);
+watch(fetchEndpoint, tick);
watch(() => widgetProps.refreshIntervalSec, () => {
if (intervalClear.value) {
intervalClear.value();
diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue
index 95f82f7d7b..b0d9eec883 100644
--- a/packages/frontend/src/widgets/WidgetRssTicker.vue
+++ b/packages/frontend/src/widgets/WidgetRssTicker.vue
@@ -44,6 +44,7 @@ const widgetPropsDef = {
url: {
type: 'string',
default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
+ manualSave: true,
},
shuffle: {
type: 'boolean',
@@ -119,7 +120,7 @@ const tick = () => {
});
};
-watch(() => fetchEndpoint, tick);
+watch(fetchEndpoint, tick);
watch(() => widgetProps.refreshIntervalSec, () => {
if (intervalClear.value) {
intervalClear.value();
diff --git a/packages/frontend/src/widgets/widget.ts b/packages/frontend/src/widgets/widget.ts
index 6c5ff36b16..d4209f6251 100644
--- a/packages/frontend/src/widgets/widget.ts
+++ b/packages/frontend/src/widgets/widget.ts
@@ -4,8 +4,9 @@
*/
import { defineAsyncComponent, reactive, watch } from 'vue';
-import type { Reactive } from 'vue';
import { throttle } from 'throttle-debounce';
+import { getDefaultFormValues } from '@/utility/form.js';
+import type { Reactive } from 'vue';
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
import * as os from '@/os.js';
import { deepClone } from '@/utility/clone.js';
@@ -39,19 +40,23 @@ export const useWidgetPropsManager = <F extends FormWithDefault>(
save: () => void;
configure: () => void;
} => {
- const widgetProps = reactive<GetFormResultType<F>>((props.widget ? deepClone(props.widget.data) : {}) as GetFormResultType<F>);
-
- const mergeProps = () => {
- for (const prop of Object.keys(propsDef)) {
- if (typeof widgetProps[prop] === 'undefined') {
- widgetProps[prop] = propsDef[prop].default;
+ const widgetProps = reactive((() => {
+ const np = getDefaultFormValues(propsDef);
+ if (props.widget?.data != null) {
+ for (const key of Object.keys(props.widget.data) as (keyof F)[]) {
+ np[key] = props.widget.data[key] as GetFormResultType<F>[typeof key];
}
}
- };
+ return np;
+ })());
- watch(widgetProps, () => {
- mergeProps();
- }, { deep: true, immediate: true });
+ watch(() => props.widget?.data, (to) => {
+ if (to != null) {
+ for (const key of Object.keys(propsDef)) {
+ widgetProps[key] = to[key];
+ }
+ }
+ }, { deep: true });
const save = throttle(3000, () => {
emit('updateProps', widgetProps as GetFormResultType<F>);