fix(frontend): popupのemit型が正しく利用できるように修正 (#16826)
* fix(frontend): popupのemit型が正しく利用できるように修正 * fix: revert unnecessary code (for testing purpose) * fix lint * fix type errors * fix types * add comment * fix * fix * fix: OverloadToUnionの仕組みを変更 * add comments, clean up * fix lint * fix types * clean up [ci skip] * fix * add comments [ci skip]
This commit is contained in:
parent
75b5dc1cd8
commit
2a14025c29
|
|
@ -38,11 +38,7 @@ export interface BroadcastTypes {
|
||||||
emojis: Packed<'EmojiDetailed'>[];
|
emojis: Packed<'EmojiDetailed'>[];
|
||||||
};
|
};
|
||||||
emojiDeleted: {
|
emojiDeleted: {
|
||||||
emojis: {
|
emojis: Packed<'EmojiDetailed'>[];
|
||||||
id?: string;
|
|
||||||
name: string;
|
|
||||||
[other: string]: any;
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
announcementCreated: {
|
announcementCreated: {
|
||||||
announcement: Packed<'Announcement'>;
|
announcement: Packed<'Announcement'>;
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ export class EmojiEntityService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packSimpleMany(
|
public packSimpleMany(
|
||||||
emojis: any[],
|
emojis: (MiEmoji['id'] | MiEmoji)[],
|
||||||
) {
|
) {
|
||||||
return Promise.all(emojis.map(x => this.packSimple(x)));
|
return Promise.all(emojis.map(x => this.packSimple(x)));
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +69,7 @@ export class EmojiEntityService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packDetailedMany(
|
public packDetailedMany(
|
||||||
emojis: any[],
|
emojis: (MiEmoji['id'] | MiEmoji)[],
|
||||||
): Promise<Packed<'EmojiDetailed'>[]> {
|
): Promise<Packed<'EmojiDetailed'>[]> {
|
||||||
return Promise.all(emojis.map(x => this.packDetailed(x)));
|
return Promise.all(emojis.map(x => this.packDetailed(x)));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,39 +24,7 @@ export const meta = {
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
optional: false, nullable: false,
|
ref: 'EmojiDetailed',
|
||||||
properties: {
|
|
||||||
id: {
|
|
||||||
type: 'string',
|
|
||||||
optional: false, nullable: false,
|
|
||||||
format: 'id',
|
|
||||||
},
|
|
||||||
aliases: {
|
|
||||||
type: 'array',
|
|
||||||
optional: false, nullable: false,
|
|
||||||
items: {
|
|
||||||
type: 'string',
|
|
||||||
optional: false, nullable: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
optional: false, nullable: false,
|
|
||||||
},
|
|
||||||
category: {
|
|
||||||
type: 'string',
|
|
||||||
optional: false, nullable: true,
|
|
||||||
},
|
|
||||||
host: {
|
|
||||||
type: 'string',
|
|
||||||
optional: false, nullable: true,
|
|
||||||
description: 'The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files.',
|
|
||||||
},
|
|
||||||
url: {
|
|
||||||
type: 'string',
|
|
||||||
optional: false, nullable: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkModal ref="modal" :zPriority="'middle'" :preferType="'dialog'" @closed="$emit('closed')" @click="onBgClick">
|
<MkModal ref="modal" :zPriority="'middle'" :preferType="'dialog'" @closed="emit('closed')" @click="onBgClick">
|
||||||
<div ref="rootEl" :class="$style.root">
|
<div ref="rootEl" :class="$style.root">
|
||||||
<div :class="$style.header">
|
<div :class="$style.header">
|
||||||
<span :class="$style.icon">
|
<span :class="$style.icon">
|
||||||
|
|
@ -44,6 +44,10 @@ const props = defineProps<{
|
||||||
announcement: Misskey.entities.Announcement;
|
announcement: Misskey.entities.Announcement;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'closed'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const rootEl = useTemplateRef('rootEl');
|
const rootEl = useTemplateRef('rootEl');
|
||||||
const bottomEl = useTemplateRef('bottomEl');
|
const bottomEl = useTemplateRef('bottomEl');
|
||||||
const modal = useTemplateRef('modal');
|
const modal = useTemplateRef('modal');
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkModalWindow>
|
</MkModalWindow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup generic="F extends File | Blob">
|
||||||
import { onMounted, useTemplateRef, ref, onUnmounted } from 'vue';
|
import { onMounted, useTemplateRef, ref, onUnmounted } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import Cropper from 'cropperjs';
|
import Cropper from 'cropperjs';
|
||||||
|
|
@ -38,13 +38,13 @@ import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
imageFile: File | Blob;
|
imageFile: F;
|
||||||
aspectRatio: number | null;
|
aspectRatio: number | null;
|
||||||
uploadFolder?: string | null;
|
uploadFolder?: string | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'ok', cropped: File | Blob): void;
|
(ev: 'ok', cropped: F): void;
|
||||||
(ev: 'cancel'): void;
|
(ev: 'cancel'): void;
|
||||||
(ev: 'closed'): void;
|
(ev: 'closed'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
@ -74,8 +74,14 @@ async function ok() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const f = await promise;
|
const f = await promise;
|
||||||
|
let finalFile: F;
|
||||||
|
if (props.imageFile instanceof File) {
|
||||||
|
finalFile = new File([f], props.imageFile.name, { type: f.type }) as F;
|
||||||
|
} else {
|
||||||
|
finalFile = f as F;
|
||||||
|
}
|
||||||
|
|
||||||
emit('ok', f);
|
emit('ok', finalFile);
|
||||||
if (dialogEl.value != null) dialogEl.value.close();
|
if (dialogEl.value != null) dialogEl.value.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkModal>
|
</MkModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export type Result = string | number | true | null;
|
||||||
|
export type MkDialogReturnType<T = Result> = { canceled: true, result: undefined } | { canceled: false, result: T };
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, useTemplateRef, computed } from 'vue';
|
import { ref, useTemplateRef, computed } from 'vue';
|
||||||
import MkModal from '@/components/MkModal.vue';
|
import MkModal from '@/components/MkModal.vue';
|
||||||
|
|
@ -65,8 +70,6 @@ type Select = {
|
||||||
default: OptionValue | null;
|
default: OptionValue | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Result = string | number | true | null;
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
type?: 'success' | 'error' | 'warning' | 'info' | 'question' | 'waiting';
|
type?: 'success' | 'error' | 'warning' | 'info' | 'question' | 'waiting';
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|
@ -93,7 +96,7 @@ const props = withDefaults(defineProps<{
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'done', v: { canceled: true } | { canceled: false, result: Result }): void;
|
(ev: 'done', v: MkDialogReturnType): void;
|
||||||
(ev: 'closed'): void;
|
(ev: 'closed'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
@ -131,7 +134,7 @@ function done(canceled: true): void;
|
||||||
function done(canceled: false, result: Result): void; // eslint-disable-line no-redeclare
|
function done(canceled: false, result: Result): void; // eslint-disable-line no-redeclare
|
||||||
|
|
||||||
function done(canceled: boolean, result?: Result): void { // eslint-disable-line no-redeclare
|
function done(canceled: boolean, result?: Result): void { // eslint-disable-line no-redeclare
|
||||||
emit('done', { canceled, result } as { canceled: true } | { canceled: false, result: Result });
|
emit('done', { canceled, result } as MkDialogReturnType);
|
||||||
modal.value?.close();
|
modal.value?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -231,17 +231,17 @@ function rename() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function move() {
|
function move() {
|
||||||
selectDriveFolder(null).then(folder => {
|
selectDriveFolder(null).then(({ canceled, folders }) => {
|
||||||
if (folder[0] && folder[0].id === props.folder.id) return;
|
if (canceled || (folders[0] && folders[0].id === props.folder.id)) return;
|
||||||
|
|
||||||
misskeyApi('drive/folders/update', {
|
misskeyApi('drive/folders/update', {
|
||||||
folderId: props.folder.id,
|
folderId: props.folder.id,
|
||||||
parentId: folder[0] ? folder[0].id : null,
|
parentId: folders[0] ? folders[0].id : null,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
globalEvents.emit('driveFoldersUpdated', [{
|
globalEvents.emit('driveFoldersUpdated', [{
|
||||||
...props.folder,
|
...props.folder,
|
||||||
parentId: folder[0] ? folder[0].id : null,
|
parentId: folders[0] ? folders[0].id : null,
|
||||||
parent: folder[0] ?? null,
|
parent: folders[0] ?? null,
|
||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -577,17 +577,19 @@ function cd(target?: Misskey.entities.DriveFolder | Misskey.entities.DriveFolder
|
||||||
async function moveFilesBulk() {
|
async function moveFilesBulk() {
|
||||||
if (selectedFiles.value.length === 0) return;
|
if (selectedFiles.value.length === 0) return;
|
||||||
|
|
||||||
const toFolder = await selectDriveFolder(folder.value ? folder.value.id : null);
|
const { canceled, folders } = await selectDriveFolder(folder.value ? folder.value.id : null);
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
await os.apiWithDialog('drive/files/move-bulk', {
|
await os.apiWithDialog('drive/files/move-bulk', {
|
||||||
fileIds: selectedFiles.value.map(f => f.id),
|
fileIds: selectedFiles.value.map(f => f.id),
|
||||||
folderId: toFolder[0] ? toFolder[0].id : null,
|
folderId: folders[0] ? folders[0].id : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
globalEvents.emit('driveFilesUpdated', selectedFiles.value.map(x => ({
|
globalEvents.emit('driveFilesUpdated', selectedFiles.value.map(x => ({
|
||||||
...x,
|
...x,
|
||||||
folderId: toFolder[0] ? toFolder[0].id : null,
|
folderId: folders[0] ? folders[0].id : null,
|
||||||
folder: toFolder[0] ?? null,
|
folder: folders[0] ?? null,
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@close="close"
|
@close="close"
|
||||||
@esc="close"
|
@esc="close"
|
||||||
@click="close"
|
@click="close"
|
||||||
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header>{{ file.name }}</template>
|
<template #header>{{ file.name }}</template>
|
||||||
<div :class="$style.container">
|
<div :class="$style.container">
|
||||||
|
|
@ -27,6 +28,10 @@ defineProps<{
|
||||||
file: Misskey.entities.DriveFile;
|
file: Misskey.entities.DriveFile;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'closed'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const modal = ref<typeof MkModalWindow | null>(null);
|
const modal = ref<typeof MkModalWindow | null>(null);
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ import { i18n } from '@/i18n.js';
|
||||||
type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>>;
|
type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>>;
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'done', v: { excludeTypes: string[] }): void,
|
(ev: 'done', v: { excludeTypes: typeof notificationTypes[number][] }): void,
|
||||||
(ev: 'closed'): void,
|
(ev: 'closed'): void,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkModal ref="modal" preferType="dialog" :zPriority="'middle'" @click="modal?.close()" @closed="$emit('closed')">
|
<MkModal ref="modal" preferType="dialog" :zPriority="'middle'" @click="modal?.close()" @closed="emit('closed')">
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<div :class="$style.title"><MkSparkle>{{ i18n.ts.misskeyUpdated }}</MkSparkle></div>
|
<div :class="$style.title"><MkSparkle>{{ i18n.ts.misskeyUpdated }}</MkSparkle></div>
|
||||||
<div :class="$style.version">✨{{ version }}🚀</div>
|
<div :class="$style.version">✨{{ version }}🚀</div>
|
||||||
|
|
@ -26,6 +26,10 @@ import { confetti } from '@/utility/confetti.js';
|
||||||
|
|
||||||
const modal = useTemplateRef('modal');
|
const modal = useTemplateRef('modal');
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'closed'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const isBeta = version.includes('-beta') || version.includes('-alpha') || version.includes('-rc');
|
const isBeta = version.includes('-beta') || version.includes('-alpha') || version.includes('-rc');
|
||||||
|
|
||||||
function whatIsNew() {
|
function whatIsNew() {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkWindow :initialWidth="640" :initialHeight="402" :canResize="true" :closeButton="true">
|
<MkWindow :initialWidth="640" :initialHeight="402" :canResize="true" :closeButton="true" @closed="emit('closed')">
|
||||||
<template #header>
|
<template #header>
|
||||||
<i class="icon ti ti-brand-youtube" style="margin-right: 0.5em;"></i>
|
<i class="icon ti ti-brand-youtube" style="margin-right: 0.5em;"></i>
|
||||||
<span>{{ title ?? 'YouTube' }}</span>
|
<span>{{ title ?? 'YouTube' }}</span>
|
||||||
|
|
@ -34,6 +34,10 @@ const props = defineProps<{
|
||||||
url: string;
|
url: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'closed'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const requestUrl = new URL(props.url);
|
const requestUrl = new URL(props.url);
|
||||||
if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url');
|
if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,15 @@
|
||||||
import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue';
|
import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue';
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { Component, Ref } from 'vue';
|
import type { Component, MaybeRef } from 'vue';
|
||||||
import type { ComponentEmit, ComponentProps as CP } from 'vue-component-type-helpers';
|
import type { ComponentEmit, ComponentProps as CP } from 'vue-component-type-helpers';
|
||||||
import type { Form, GetFormResultType } from '@/utility/form.js';
|
import type { Form, GetFormResultType } from '@/utility/form.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import type { PostFormProps } from '@/types/post-form.js';
|
import type { PostFormProps } from '@/types/post-form.js';
|
||||||
import type { UploaderFeatures } from '@/composables/use-uploader.js';
|
import type { UploaderFeatures } from '@/composables/use-uploader.js';
|
||||||
import type { MkSelectItem, OptionValue } from '@/components/MkSelect.vue';
|
import type { MkSelectItem, OptionValue } from '@/components/MkSelect.vue';
|
||||||
|
import type { MkDialogReturnType } from '@/components/MkDialog.vue';
|
||||||
|
import type { OverloadToUnion } from '@/types/overload-to-union.js';
|
||||||
import type MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
|
import type MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
|
||||||
import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
|
import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
|
@ -159,12 +161,34 @@ export function claimZIndex(priority: keyof typeof zIndexes = 'low'): number {
|
||||||
}
|
}
|
||||||
|
|
||||||
// props に ref を許可するようにする
|
// props に ref を許可するようにする
|
||||||
type ComponentProps<T extends Component> = { [K in keyof CP<T>]: CP<T>[K] | Ref<CP<T>[K]> };
|
type PropsWithRefs<P> = { [K in keyof P]: MaybeRef<P[K]> };
|
||||||
|
type ComponentProps<T extends Component> = PropsWithRefs<CP<T>>;
|
||||||
|
|
||||||
|
// 関数の引数が any[] (もっとも広義なもの) かどうかを判定し、any[] の場合は排除 (never) するヘルパー
|
||||||
|
type FilterSpecificFunc<T> = T extends (...args: any[]) => void
|
||||||
|
? (any[] extends Parameters<T> ? never : T)
|
||||||
|
: T;
|
||||||
|
|
||||||
|
// オブジェクトの各プロパティに対して再帰的、あるいは単純に適用する型関数
|
||||||
|
type CleanFunctions<T> = {
|
||||||
|
[K in keyof T]: T[K] extends (...args: any[]) => any
|
||||||
|
? FilterSpecificFunc<T[K]>
|
||||||
|
: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
// emitの関数群をオブジェクト型に変換する(InstanceType<Component>['$emit']はFunctionalComponent = ジェネリックコンポーネントでは使用できない)
|
||||||
|
type ComponentEmitsObject<C extends Component, IE = OverloadToUnion<ComponentEmit<C>>> = CleanFunctions<{
|
||||||
|
[K in IE extends (evName: infer U, ...args: any[]) => any ? U & PropertyKey : never]: IE extends (evName: K, ...args: infer A) => infer R
|
||||||
|
? (...args: A) => R
|
||||||
|
: (...args: any[]) => void;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
// NOTE: ジェネリック型つきのコンポーネントでは、emitsの型推論がうまく働かない(型変数を取り出すことはできないため)
|
||||||
|
// NOTE: emitsがOverloadToUnionで対応しているオーバーロードの数を超える場合は、OverloadToUnionの個数を増やせばOK
|
||||||
export function popup<T extends Component>(
|
export function popup<T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
props: ComponentProps<T>,
|
props: ComponentProps<T>,
|
||||||
events: Partial<ComponentEmit<T>> = {},
|
events: Partial<ComponentEmitsObject<T>> = {},
|
||||||
): { dispose: () => void } {
|
): { dispose: () => void } {
|
||||||
markRaw(component);
|
markRaw(component);
|
||||||
|
|
||||||
|
|
@ -192,10 +216,10 @@ export function popup<T extends Component>(
|
||||||
export async function popupAsyncWithDialog<T extends Component>(
|
export async function popupAsyncWithDialog<T extends Component>(
|
||||||
componentFetching: Promise<T>,
|
componentFetching: Promise<T>,
|
||||||
props: ComponentProps<T>,
|
props: ComponentProps<T>,
|
||||||
events: Partial<ComponentEmit<T>> = {},
|
events: Partial<ComponentEmitsObject<T>> = {},
|
||||||
): Promise<{ dispose: () => void }> {
|
): Promise<{ dispose: () => void }> {
|
||||||
let component: T;
|
let component: T;
|
||||||
let closeWaiting = () => {};
|
let closeWaiting = () => { };
|
||||||
|
|
||||||
const timer = window.setTimeout(() => {
|
const timer = window.setTimeout(() => {
|
||||||
closeWaiting = waiting();
|
closeWaiting = waiting();
|
||||||
|
|
@ -291,23 +315,19 @@ export function confirm(props: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: const T extends ... にしたい
|
type ActionsAction = {
|
||||||
// https://zenn.dev/general_link/articles/813e47b7a0eef7#const-type-parameters
|
|
||||||
export function actions<T extends {
|
|
||||||
value: string;
|
value: string;
|
||||||
text: string;
|
text: string;
|
||||||
primary?: boolean,
|
primary?: boolean;
|
||||||
danger?: boolean,
|
danger?: boolean;
|
||||||
}[]>(props: {
|
};
|
||||||
|
|
||||||
|
export function actions<const T extends ActionsAction[]>(props: {
|
||||||
type: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question';
|
type: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question';
|
||||||
title?: string;
|
title?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
actions: T;
|
actions: T;
|
||||||
}): Promise<{
|
}): Promise<MkDialogReturnType<T[number]['value']>> {
|
||||||
canceled: true; result: undefined;
|
|
||||||
} | {
|
|
||||||
canceled: false; result: T[number]['value'];
|
|
||||||
}> {
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const { dispose } = popup(MkDialog, {
|
const { dispose } = popup(MkDialog, {
|
||||||
...props,
|
...props,
|
||||||
|
|
@ -321,7 +341,7 @@ export function actions<T extends {
|
||||||
})),
|
})),
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
resolve(result ? result : { canceled: true });
|
resolve(result as MkDialogReturnType<T[number]['value']>);
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
});
|
});
|
||||||
|
|
@ -338,11 +358,7 @@ export function inputText(props: {
|
||||||
default: string;
|
default: string;
|
||||||
minLength?: number;
|
minLength?: number;
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
}): Promise<{
|
}): Promise<MkDialogReturnType<string>>;
|
||||||
canceled: true; result: undefined;
|
|
||||||
} | {
|
|
||||||
canceled: false; result: string;
|
|
||||||
}>;
|
|
||||||
// min lengthが指定されてたら result は null になり得ないことを保証する overload function
|
// min lengthが指定されてたら result は null になり得ないことを保証する overload function
|
||||||
export function inputText(props: {
|
export function inputText(props: {
|
||||||
type?: 'text' | 'email' | 'password' | 'url';
|
type?: 'text' | 'email' | 'password' | 'url';
|
||||||
|
|
@ -353,11 +369,7 @@ export function inputText(props: {
|
||||||
default?: string;
|
default?: string;
|
||||||
minLength: number;
|
minLength: number;
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
}): Promise<{
|
}): Promise<MkDialogReturnType<string>>;
|
||||||
canceled: true; result: undefined;
|
|
||||||
} | {
|
|
||||||
canceled: false; result: string;
|
|
||||||
}>;
|
|
||||||
export function inputText(props: {
|
export function inputText(props: {
|
||||||
type?: 'text' | 'email' | 'password' | 'url';
|
type?: 'text' | 'email' | 'password' | 'url';
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|
@ -367,11 +379,7 @@ export function inputText(props: {
|
||||||
default?: string | null;
|
default?: string | null;
|
||||||
minLength?: number;
|
minLength?: number;
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
}): Promise<{
|
}): Promise<MkDialogReturnType<string | null>>;
|
||||||
canceled: true; result: undefined;
|
|
||||||
} | {
|
|
||||||
canceled: false; result: string | null;
|
|
||||||
}>;
|
|
||||||
export function inputText(props: {
|
export function inputText(props: {
|
||||||
type?: 'text' | 'email' | 'password' | 'url';
|
type?: 'text' | 'email' | 'password' | 'url';
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|
@ -381,11 +389,7 @@ export function inputText(props: {
|
||||||
default?: string | null;
|
default?: string | null;
|
||||||
minLength?: number;
|
minLength?: number;
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
}): Promise<{
|
}): Promise<MkDialogReturnType<string | null>> {
|
||||||
canceled: true; result: undefined;
|
|
||||||
} | {
|
|
||||||
canceled: false; result: string | null;
|
|
||||||
}> {
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const { dispose } = popup(MkDialog, {
|
const { dispose } = popup(MkDialog, {
|
||||||
title: props.title,
|
title: props.title,
|
||||||
|
|
@ -400,7 +404,7 @@ export function inputText(props: {
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
resolve(result ? result : { canceled: true });
|
resolve(result as MkDialogReturnType<string | null>);
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
});
|
});
|
||||||
|
|
@ -414,33 +418,21 @@ export function inputNumber(props: {
|
||||||
placeholder?: string | null;
|
placeholder?: string | null;
|
||||||
autocomplete?: string;
|
autocomplete?: string;
|
||||||
default: number;
|
default: number;
|
||||||
}): Promise<{
|
}): Promise<MkDialogReturnType<number>>;
|
||||||
canceled: true; result: undefined;
|
|
||||||
} | {
|
|
||||||
canceled: false; result: number;
|
|
||||||
}>;
|
|
||||||
export function inputNumber(props: {
|
export function inputNumber(props: {
|
||||||
title?: string;
|
title?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
placeholder?: string | null;
|
placeholder?: string | null;
|
||||||
autocomplete?: string;
|
autocomplete?: string;
|
||||||
default?: number | null;
|
default?: number | null;
|
||||||
}): Promise<{
|
}): Promise<MkDialogReturnType<number | null>>;
|
||||||
canceled: true; result: undefined;
|
|
||||||
} | {
|
|
||||||
canceled: false; result: number | null;
|
|
||||||
}>;
|
|
||||||
export function inputNumber(props: {
|
export function inputNumber(props: {
|
||||||
title?: string;
|
title?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
placeholder?: string | null;
|
placeholder?: string | null;
|
||||||
autocomplete?: string;
|
autocomplete?: string;
|
||||||
default?: number | null;
|
default?: number | null;
|
||||||
}): Promise<{
|
}): Promise<MkDialogReturnType<number | null>> {
|
||||||
canceled: true; result: undefined;
|
|
||||||
} | {
|
|
||||||
canceled: false; result: number | null;
|
|
||||||
}> {
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const { dispose } = popup(MkDialog, {
|
const { dispose } = popup(MkDialog, {
|
||||||
title: props.title,
|
title: props.title,
|
||||||
|
|
@ -453,7 +445,7 @@ export function inputNumber(props: {
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
resolve(result ? result : { canceled: true });
|
resolve(result as MkDialogReturnType<number | null>);
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
});
|
});
|
||||||
|
|
@ -465,11 +457,7 @@ export function inputDatetime(props: {
|
||||||
text?: string;
|
text?: string;
|
||||||
placeholder?: string | null;
|
placeholder?: string | null;
|
||||||
default?: string | null;
|
default?: string | null;
|
||||||
}): Promise<{
|
}): Promise<MkDialogReturnType<Date>> {
|
||||||
canceled: true; result: undefined;
|
|
||||||
} | {
|
|
||||||
canceled: false; result: Date;
|
|
||||||
}> {
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const { dispose } = popup(MkDialog, {
|
const { dispose } = popup(MkDialog, {
|
||||||
title: props.title,
|
title: props.title,
|
||||||
|
|
@ -481,7 +469,7 @@ export function inputDatetime(props: {
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
resolve(result != null && result.result != null ? { result: new Date(result.result), canceled: false } : { result: undefined, canceled: true });
|
resolve(result != null && typeof result.result === 'string' ? { result: new Date(result.result), canceled: false } : { result: undefined, canceled: true });
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
});
|
});
|
||||||
|
|
@ -508,11 +496,7 @@ export function select<C extends OptionValue, D extends C | null = null>(props:
|
||||||
text?: string;
|
text?: string;
|
||||||
default?: D;
|
default?: D;
|
||||||
items: (MkSelectItem<C> | undefined)[];
|
items: (MkSelectItem<C> | undefined)[];
|
||||||
}): Promise<{
|
}): Promise<MkDialogReturnType<Exclude<D, undefined> extends null ? C | null : C>> {
|
||||||
canceled: true; result: undefined;
|
|
||||||
} | {
|
|
||||||
canceled: false; result: Exclude<D, undefined> extends null ? C | null : C;
|
|
||||||
}> {
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const { dispose } = popup(MkDialog, {
|
const { dispose } = popup(MkDialog, {
|
||||||
title: props.title,
|
title: props.title,
|
||||||
|
|
@ -523,7 +507,7 @@ export function select<C extends OptionValue, D extends C | null = null>(props:
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
resolve(result ? result : { canceled: true });
|
resolve(result as MkDialogReturnType<Exclude<D, undefined> extends null ? C | null : C>);
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
});
|
});
|
||||||
|
|
@ -582,7 +566,7 @@ export function form<F extends Form>(title: string, f: F): Promise<{ canceled: t
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkFormDialog.vue')), { title, form: f }, {
|
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkFormDialog.vue')), { title, form: f }, {
|
||||||
done: result => {
|
done: result => {
|
||||||
resolve(result);
|
resolve(result as { canceled?: false, result: GetFormResultType<F> });
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
});
|
});
|
||||||
|
|
@ -634,16 +618,16 @@ export async function pickEmoji(anchorElement: HTMLElement, opts: ComponentProps
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cropImageFile(imageFile: File | Blob, options: {
|
export async function cropImageFile<F extends File | Blob>(imageFile: F, options: {
|
||||||
aspectRatio: number | null;
|
aspectRatio: number | null;
|
||||||
}): Promise<File> {
|
}): Promise<F> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkCropperDialog.vue')), {
|
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkCropperDialog.vue')), {
|
||||||
imageFile: imageFile,
|
imageFile: imageFile,
|
||||||
aspectRatio: options.aspectRatio,
|
aspectRatio: options.aspectRatio,
|
||||||
}, {
|
}, {
|
||||||
ok: x => {
|
ok: x => {
|
||||||
resolve(x);
|
resolve(x as F);
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, defineAsyncComponent, markRaw, ref } from 'vue';
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { computed, markRaw, ref } from 'vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
|
@ -116,7 +117,7 @@ const selectAll = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleSelect = (emoji) => {
|
const toggleSelect = (emoji: Misskey.entities.EmojiDetailed) => {
|
||||||
if (selectedEmojis.value.includes(emoji.id)) {
|
if (selectedEmojis.value.includes(emoji.id)) {
|
||||||
selectedEmojis.value = selectedEmojis.value.filter(x => x !== emoji.id);
|
selectedEmojis.value = selectedEmojis.value.filter(x => x !== emoji.id);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -124,19 +125,23 @@ const toggleSelect = (emoji) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const add = async (ev: MouseEvent) => {
|
const add = async () => {
|
||||||
const { dispose } = await os.popupAsyncWithDialog(import('./emoji-edit-dialog.vue').then(x => x.default), {
|
const { dispose } = await os.popupAsyncWithDialog(import('./emoji-edit-dialog.vue').then(x => x.default), {
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
if (result.created) {
|
if (result.created) {
|
||||||
paginator.prepend(result.created);
|
const nowIso = (new Date()).toISOString();
|
||||||
|
paginator.prepend({
|
||||||
|
...result.created,
|
||||||
|
createdAt: nowIso,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const edit = async (emoji) => {
|
const edit = async (emoji: Misskey.entities.EmojiDetailed) => {
|
||||||
const { dispose } = await os.popupAsyncWithDialog(import('./emoji-edit-dialog.vue').then(x => x.default), {
|
const { dispose } = await os.popupAsyncWithDialog(import('./emoji-edit-dialog.vue').then(x => x.default), {
|
||||||
emoji: emoji,
|
emoji: emoji,
|
||||||
}, {
|
}, {
|
||||||
|
|
|
||||||
|
|
@ -131,10 +131,11 @@ function move() {
|
||||||
|
|
||||||
const f = file.value;
|
const f = file.value;
|
||||||
|
|
||||||
selectDriveFolder(null).then(folder => {
|
selectDriveFolder(null).then(({ canceled, folders }) => {
|
||||||
|
if (canceled) return;
|
||||||
misskeyApi('drive/files/update', {
|
misskeyApi('drive/files/update', {
|
||||||
fileId: f.id,
|
fileId: f.id,
|
||||||
folderId: folder[0] ? folder[0].id : null,
|
folderId: folders[0] ? folders[0].id : null,
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
await _fetch_();
|
await _fetch_();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkInfo warn>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn }}</MkInfo>
|
<MkInfo warn>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn }}</MkInfo>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
<MkSwitch v-model="isSensitive">isSensitive</MkSwitch>
|
<MkSwitch v-model="isSensitive">{{ i18n.ts.sensitive }}</MkSwitch>
|
||||||
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
|
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
|
||||||
<MkButton v-if="emoji" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
<MkButton v-if="emoji" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -99,7 +99,7 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'done', v: { deleted?: boolean; updated?: Misskey.entities.AdminEmojiUpdateRequest; created?: Misskey.entities.AdminEmojiUpdateRequest }): void,
|
(ev: 'done', v: { deleted?: boolean; updated?: Misskey.entities.EmojiDetailed; created?: Misskey.entities.EmojiDetailed }): void,
|
||||||
(ev: 'closed'): void
|
(ev: 'closed'): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
@ -157,19 +157,29 @@ async function done() {
|
||||||
localOnly: localOnly.value,
|
localOnly: localOnly.value,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.value.map(x => x.id),
|
roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.value.map(x => x.id),
|
||||||
fileId: file.value ? file.value.id : undefined,
|
fileId: file.value ? file.value.id : undefined,
|
||||||
};
|
} satisfies Misskey.entities.AdminEmojiUpdateRequest;
|
||||||
|
|
||||||
if (props.emoji) {
|
if (props.emoji) {
|
||||||
|
const emojiDetailed = {
|
||||||
|
id: props.emoji.id,
|
||||||
|
aliases: params.aliases,
|
||||||
|
name: params.name,
|
||||||
|
category: params.category,
|
||||||
|
host: props.emoji.host,
|
||||||
|
url: file.value ? file.value.url : props.emoji.url,
|
||||||
|
license: params.license,
|
||||||
|
isSensitive: params.isSensitive,
|
||||||
|
localOnly: params.localOnly,
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: params.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
|
} satisfies Misskey.entities.EmojiDetailed;
|
||||||
|
|
||||||
await os.apiWithDialog('admin/emoji/update', {
|
await os.apiWithDialog('admin/emoji/update', {
|
||||||
id: props.emoji.id,
|
id: props.emoji.id,
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
|
|
||||||
emit('done', {
|
emit('done', {
|
||||||
updated: {
|
updated: emojiDetailed,
|
||||||
id: props.emoji.id,
|
|
||||||
...params,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
windowEl.value?.close();
|
windowEl.value?.close();
|
||||||
|
|
|
||||||
|
|
@ -296,8 +296,9 @@ if (prefer.s.uploadFolder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function chooseUploadFolder() {
|
function chooseUploadFolder() {
|
||||||
selectDriveFolder(null).then(async folder => {
|
selectDriveFolder(null).then(async ({ canceled, folders }) => {
|
||||||
prefer.commit('uploadFolder', folder[0] ? folder[0].id : null);
|
if (canceled) return;
|
||||||
|
prefer.commit('uploadFolder', folders[0] ? folders[0].id : null);
|
||||||
os.success();
|
os.success();
|
||||||
if (prefer.s.uploadFolder) {
|
if (prefer.s.uploadFolder) {
|
||||||
uploadFolder.value = await misskeyApi('drive/folders/show', {
|
uploadFolder.value = await misskeyApi('drive/folders/show', {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
type FlattenAndDedup<T> = T extends (...args: infer A) => infer R ? (...args: A) => R : never;
|
||||||
|
|
||||||
|
// 10個で足りなかった場合は増やす
|
||||||
|
export type OverloadToUnion<T> = FlattenAndDedup<T extends {
|
||||||
|
(...args: infer A1): infer R1;
|
||||||
|
(...args: infer A2): infer R2;
|
||||||
|
(...args: infer A3): infer R3;
|
||||||
|
(...args: infer A4): infer R4;
|
||||||
|
(...args: infer A5): infer R5;
|
||||||
|
(...args: infer A6): infer R6;
|
||||||
|
(...args: infer A7): infer R7;
|
||||||
|
(...args: infer A8): infer R8;
|
||||||
|
(...args: infer A9): infer R9;
|
||||||
|
(...args: infer A10): infer R10;
|
||||||
|
} ? (
|
||||||
|
((...args: A1) => R1) |
|
||||||
|
((...args: A2) => R2) |
|
||||||
|
((...args: A3) => R3) |
|
||||||
|
((...args: A4) => R4) |
|
||||||
|
((...args: A5) => R5) |
|
||||||
|
((...args: A6) => R6) |
|
||||||
|
((...args: A7) => R7) |
|
||||||
|
((...args: A8) => R8) |
|
||||||
|
((...args: A9) => R9) |
|
||||||
|
((...args: A10) => R10)
|
||||||
|
) : never>;
|
||||||
|
|
@ -213,10 +213,11 @@ export class Autocomplete {
|
||||||
const _y = ref(y);
|
const _y = ref(y);
|
||||||
const _q = ref(q);
|
const _q = ref(q);
|
||||||
|
|
||||||
const { dispose } = await popup(defineAsyncComponent(() => import('@/components/MkAutocomplete.vue')), {
|
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkAutocomplete.vue')), {
|
||||||
textarea: this.textarea,
|
textarea: this.textarea,
|
||||||
close: this.close,
|
close: this.close,
|
||||||
type: type,
|
type: type,
|
||||||
|
//@ts-expect-error popupは今のところジェネリック型のコンポーネントに対応していない
|
||||||
q: _q,
|
q: _q,
|
||||||
x: _x,
|
x: _x,
|
||||||
y: _y,
|
y: _y,
|
||||||
|
|
|
||||||
|
|
@ -301,14 +301,26 @@ export async function createCroppedImageDriveFileFromImageDriveFile(imageDriveFi
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function selectDriveFolder(initialFolder: Misskey.entities.DriveFolder['id'] | null): Promise<(Misskey.entities.DriveFolder | null)[]> {
|
export async function selectDriveFolder(initialFolder: Misskey.entities.DriveFolder['id'] | null): Promise<{
|
||||||
|
canceled: false;
|
||||||
|
folders: (Misskey.entities.DriveFolder | null)[];
|
||||||
|
} | {
|
||||||
|
canceled: true;
|
||||||
|
folders: undefined;
|
||||||
|
}> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let dispose: () => void;
|
let dispose: () => void;
|
||||||
os.popupAsyncWithDialog(import('@/components/MkDriveFolderSelectDialog.vue').then(x => x.default), {
|
os.popupAsyncWithDialog(import('@/components/MkDriveFolderSelectDialog.vue').then(x => x.default), {
|
||||||
initialFolder,
|
initialFolder,
|
||||||
}, {
|
}, {
|
||||||
done: folders => {
|
done: folders => {
|
||||||
resolve(folders);
|
resolve(folders == null ? {
|
||||||
|
canceled: true,
|
||||||
|
folders: undefined,
|
||||||
|
} : {
|
||||||
|
canceled: false,
|
||||||
|
folders,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
}).then(d => dispose = d.dispose, reject);
|
}).then(d => dispose = d.dispose, reject);
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,11 @@ async function describe(file: Misskey.entities.DriveFile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function move(file: Misskey.entities.DriveFile) {
|
function move(file: Misskey.entities.DriveFile) {
|
||||||
selectDriveFolder(null).then(folder => {
|
selectDriveFolder(null).then(({ canceled, folders }) => {
|
||||||
|
if (canceled) return;
|
||||||
misskeyApi('drive/files/update', {
|
misskeyApi('drive/files/update', {
|
||||||
fileId: file.id,
|
fileId: file.id,
|
||||||
folderId: folder[0] ? folder[0].id : null,
|
folderId: folders[0] ? folders[0].id : null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,11 +96,11 @@ const fetch = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const choose = () => {
|
const choose = () => {
|
||||||
selectDriveFolder(null).then(folder => {
|
selectDriveFolder(null).then(({ folders, canceled }) => {
|
||||||
if (folder[0] == null) {
|
if (canceled || folders[0] == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
widgetProps.folderId = folder[0].id;
|
widgetProps.folderId = folders[0].id;
|
||||||
save();
|
save();
|
||||||
fetch();
|
fetch();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -80,8 +80,8 @@ export const useWidgetPropsManager = <F extends FormWithDefault>(
|
||||||
form: form,
|
form: form,
|
||||||
currentSettings: widgetProps,
|
currentSettings: widgetProps,
|
||||||
}, {
|
}, {
|
||||||
saved: (newProps: GetFormResultType<F>) => {
|
saved: (newProps) => {
|
||||||
resolve({ canceled: false, result: newProps });
|
resolve({ canceled: false, result: newProps as GetFormResultType<F> });
|
||||||
},
|
},
|
||||||
canceled: () => {
|
canceled: () => {
|
||||||
resolve({ canceled: true });
|
resolve({ canceled: true });
|
||||||
|
|
|
||||||
|
|
@ -8226,16 +8226,7 @@ export interface operations {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
'application/json': {
|
'application/json': components['schemas']['EmojiDetailed'][];
|
||||||
/** Format: id */
|
|
||||||
id: string;
|
|
||||||
aliases: string[];
|
|
||||||
name: string;
|
|
||||||
category: string | null;
|
|
||||||
/** @description The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files. */
|
|
||||||
host: string | null;
|
|
||||||
url: string;
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Client error */
|
/** @description Client error */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue