diff --git a/.dev/Caddyfile b/.dev/Caddyfile
index 8505b3a..8018ced 100644
--- a/.dev/Caddyfile
+++ b/.dev/Caddyfile
@@ -20,12 +20,12 @@ nattyarch.local {
header Accept text/html*
}
- @static {
- path /favicon.ico /favicon.png /favicon.svg /manifest.json /api-doc /sw.js /static-assets* /client-assets* /assets* /twemoji* /url
+ @frontend {
+ path /favicon.ico /favicon.png /favicon.svg /manifest.json /api-doc /sw.js /static-assets* /client-assets* /assets* /twemoji* /url /fe-api*
}
reverse_proxy @render_html 127.0.0.1:4938
- reverse_proxy @static 127.0.0.1:4938
+ reverse_proxy @frontend 127.0.0.1:4938
reverse_proxy 127.0.0.1:4937
}
diff --git a/Cargo.lock b/Cargo.lock
index 3e20f30..d60fcdb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -986,6 +986,7 @@ dependencies = [
"tower-http",
"tracing",
"tracing-subscriber",
+ "walkdir",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index c52c94d..a5b5ce5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -40,6 +40,7 @@ tower-http = "0.4"
tracing = "0.1"
tracing-subscriber = "0.3"
url = "2.3"
+walkdir = "2.3"
[dependencies]
magnetar_core = { path = "./core" }
diff --git a/fe_calckey/Cargo.toml b/fe_calckey/Cargo.toml
index f082f0e..90a01f5 100644
--- a/fe_calckey/Cargo.toml
+++ b/fe_calckey/Cargo.toml
@@ -27,4 +27,6 @@ serde = { workspace = true, features = ["derive"] }
toml = { workspace = true }
serde_json = { workspace = true }
-chrono = { workspace = true }
\ No newline at end of file
+chrono = { workspace = true }
+
+walkdir = { workspace = true }
\ No newline at end of file
diff --git a/fe_calckey/frontend/assets/api-doc.png b/fe_calckey/frontend/assets/api-doc.png
deleted file mode 100644
index 95fe697..0000000
Binary files a/fe_calckey/frontend/assets/api-doc.png and /dev/null differ
diff --git a/fe_calckey/frontend/assets/redoc.html b/fe_calckey/frontend/assets/redoc.html
deleted file mode 100644
index 6f48c17..0000000
--- a/fe_calckey/frontend/assets/redoc.html
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
- Calckey API
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/fe_calckey/frontend/client/.prettierrc b/fe_calckey/frontend/client/.prettierrc
index 77a5016..47c6381 100644
--- a/fe_calckey/frontend/client/.prettierrc
+++ b/fe_calckey/frontend/client/.prettierrc
@@ -1,15 +1,15 @@
{
"tabWidth": 4,
- "useTabs": true,
- "singleQuote": false,
- "vueIndentScriptAndStyle": false,
- "plugins": ["vue"],
- "overrides": [
- {
- "files": "*.vue",
- "options": {
- "parser": "vue"
- }
- }
- ]
+ "useTabs": false,
+ "singleQuote": false,
+ "vueIndentScriptAndStyle": false,
+ "plugins": ["vue"],
+ "overrides": [
+ {
+ "files": "*.vue",
+ "options": {
+ "parser": "vue"
+ }
+ }
+ ]
}
diff --git a/fe_calckey/frontend/client/package.json b/fe_calckey/frontend/client/package.json
index 879d305..9aa1116 100644
--- a/fe_calckey/frontend/client/package.json
+++ b/fe_calckey/frontend/client/package.json
@@ -53,6 +53,7 @@
"insert-text-at-cursor": "0.3.0",
"json5": "2.2.3",
"katex": "0.16.7",
+ "magnetar-common": "workspace:*",
"matter-js": "0.18.0",
"mfm-js": "0.23.3",
"photoswipe": "5.3.7",
diff --git a/fe_calckey/frontend/client/src/config.ts b/fe_calckey/frontend/client/src/config.ts
index 8a18ba9..52fc761 100644
--- a/fe_calckey/frontend/client/src/config.ts
+++ b/fe_calckey/frontend/client/src/config.ts
@@ -7,6 +7,7 @@ export const host = _HOST || address.host;
export const hostname = address.hostname;
export const url = _REMOTE_URL || address.origin;
export const apiUrl = `${url}/api`;
+export const feApiUrl = `${url}/fe-api`;
export const wsUrl = `${url
.replace("http://", "ws://")
.replace("https://", "wss://")}/streaming`;
diff --git a/fe_calckey/frontend/client/src/os.ts b/fe_calckey/frontend/client/src/os.ts
index 32bb8f7..58145e5 100644
--- a/fe_calckey/frontend/client/src/os.ts
+++ b/fe_calckey/frontend/client/src/os.ts
@@ -1,10 +1,10 @@
// TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する
-import { Component, markRaw, Ref, ref, defineAsyncComponent } from "vue";
+import { Component, defineAsyncComponent, markRaw, ref, Ref } from "vue";
import { EventEmitter } from "eventemitter3";
import insertTextAtCursor from "insert-text-at-cursor";
import * as Misskey from "calckey-js";
-import { apiUrl, url } from "@/config";
+import { apiUrl, feApiUrl, url } from "@/config";
import MkPostFormDialog from "@/components/MkPostFormDialog.vue";
import MkWaitingDialog from "@/components/MkWaitingDialog.vue";
import MkToast from "@/components/MkToast.vue";
@@ -12,899 +12,967 @@ import MkDialog from "@/components/MkDialog.vue";
import { MenuItem } from "@/types/menu";
import { $i } from "@/account";
import { i18n } from "./i18n";
+import {
+ FrontendApiEndpoint,
+ FrontendApiEndpoints,
+} from "magnetar-common/built/fe-api";
export const pendingApiRequestsCount = ref(0);
const apiClient = new Misskey.api.APIClient({
- origin: url,
+ origin: url,
});
+export async function feApi(
+ endpointDef: FrontendApiEndpoint<
+ FrontendApiEndpoints[T]["method"],
+ FrontendApiEndpoints[T]["path"],
+ FrontendApiEndpoints[T]["request"],
+ FrontendApiEndpoints[T]["response"]
+ >,
+ data: FrontendApiEndpoints[T]["request"],
+ token?: string | null | undefined
+): Promise {
+ type Response = FrontendApiEndpoints[T]["response"];
+
+ pendingApiRequestsCount.value++;
+
+ const authorizationToken = token ?? $i?.token ?? undefined;
+ const authorization = authorizationToken
+ ? `Bearer ${authorizationToken}`
+ : undefined;
+
+ const endpoint = endpointDef.path;
+
+ let url = `${feApiUrl}/${endpoint}`;
+
+ if (endpointDef.method === "GET") {
+ const query = new URLSearchParams(data as any).toString();
+ if (query) {
+ url += `?${query}`;
+ }
+ }
+
+ const promise: Promise = new Promise((resolve, reject) => {
+ fetch(url, {
+ method: endpointDef.method,
+ body:
+ endpointDef.method !== "GET" ? JSON.stringify(data) : undefined,
+ credentials: "omit",
+ cache: "no-cache",
+ headers: authorization ? { authorization } : {},
+ })
+ .then(async (res) => {
+ const body = res.status === 204 ? null : await res.json();
+
+ if (res.status === 200) {
+ resolve(body as Response);
+ } else if (res.status === 204) {
+ resolve(null as any as Response);
+ } else {
+ reject(body.error);
+ }
+ })
+ .catch(reject);
+ });
+
+ promise.finally(() => {
+ pendingApiRequestsCount.value--;
+ });
+
+ return promise;
+}
+
export const api = ((
- endpoint: string,
- data: Record = {},
- token?: string | null | undefined,
+ endpoint: string,
+ data: Record = {},
+ token?: string | null | undefined
) => {
- pendingApiRequestsCount.value++;
+ pendingApiRequestsCount.value++;
- const onFinally = () => {
- pendingApiRequestsCount.value--;
- };
+ const onFinally = () => {
+ pendingApiRequestsCount.value--;
+ };
- const authorizationToken = token ?? $i?.token ?? undefined;
- const authorization = authorizationToken
- ? `Bearer ${authorizationToken}`
- : undefined;
+ const authorizationToken = token ?? $i?.token ?? undefined;
+ const authorization = authorizationToken
+ ? `Bearer ${authorizationToken}`
+ : undefined;
- const promise = new Promise((resolve, reject) => {
- fetch(endpoint.indexOf("://") > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
- method: "POST",
- body: JSON.stringify(data),
- credentials: "omit",
- cache: "no-cache",
- headers: authorization ? { authorization } : {},
- })
- .then(async (res) => {
- const body = res.status === 204 ? null : await res.json();
+ const promise = new Promise((resolve, reject) => {
+ fetch(
+ endpoint.indexOf("://") > -1 ? endpoint : `${apiUrl}/${endpoint}`,
+ {
+ method: "POST",
+ body: JSON.stringify(data),
+ credentials: "omit",
+ cache: "no-cache",
+ headers: authorization ? { authorization } : {},
+ }
+ )
+ .then(async (res) => {
+ const body = res.status === 204 ? null : await res.json();
- if (res.status === 200) {
- resolve(body);
- } else if (res.status === 204) {
- resolve();
- } else {
- reject(body.error);
- }
- })
- .catch(reject);
- });
+ if (res.status === 200) {
+ resolve(body);
+ } else if (res.status === 204) {
+ resolve();
+ } else {
+ reject(body.error);
+ }
+ })
+ .catch(reject);
+ });
- promise.then(onFinally, onFinally);
+ promise.then(onFinally, onFinally);
- return promise;
+ return promise;
}) as typeof apiClient.request;
export const apiGet = ((
- endpoint: string,
- data: Record = {},
- token?: string | null | undefined,
+ endpoint: string,
+ data: Record = {},
+ token?: string | null | undefined
) => {
- pendingApiRequestsCount.value++;
+ pendingApiRequestsCount.value++;
- const onFinally = () => {
- pendingApiRequestsCount.value--;
- };
+ const onFinally = () => {
+ pendingApiRequestsCount.value--;
+ };
- const query = new URLSearchParams(data);
+ const query = new URLSearchParams(data);
- const authorizationToken = token ?? $i?.token ?? undefined;
- const authorization = authorizationToken
- ? `Bearer ${authorizationToken}`
- : undefined;
+ const authorizationToken = token ?? $i?.token ?? undefined;
+ const authorization = authorizationToken
+ ? `Bearer ${authorizationToken}`
+ : undefined;
- const promise = new Promise((resolve, reject) => {
- // Send request
- fetch(`${apiUrl}/${endpoint}?${query}`, {
- method: "GET",
- credentials: "omit",
- cache: "default",
- headers: authorization ? { authorization } : {},
- })
- .then(async (res) => {
- const body = res.status === 204 ? null : await res.json();
+ const promise = new Promise((resolve, reject) => {
+ // Send request
+ fetch(`${apiUrl}/${endpoint}?${query}`, {
+ method: "GET",
+ credentials: "omit",
+ cache: "default",
+ headers: authorization ? { authorization } : {},
+ })
+ .then(async (res) => {
+ const body = res.status === 204 ? null : await res.json();
- if (res.status === 200) {
- resolve(body);
- } else if (res.status === 204) {
- resolve();
- } else {
- reject(body.error);
- }
- })
- .catch(reject);
- });
+ if (res.status === 200) {
+ resolve(body);
+ } else if (res.status === 204) {
+ resolve();
+ } else {
+ reject(body.error);
+ }
+ })
+ .catch(reject);
+ });
- promise.then(onFinally, onFinally);
+ promise.then(onFinally, onFinally);
- return promise;
+ return promise;
}) as typeof apiClient.request;
export const apiWithDialog = ((
- endpoint: string,
- data: Record = {},
- token?: string | null | undefined,
+ endpoint: string,
+ data: Record = {},
+ token?: string | null | undefined
) => {
- const promise = api(endpoint, data, token);
- promiseDialog(promise, null, (err) => {
- alert({
- type: "error",
- text: err.message + "\n" + (err as any).id,
- });
- });
+ const promise = api(endpoint, data, token);
+ promiseDialog(promise, null, (err) => {
+ alert({
+ type: "error",
+ text: err.message + "\n" + (err as any).id,
+ });
+ });
- return promise;
+ return promise;
}) as typeof api;
export function promiseDialog>(
- promise: T,
- onSuccess?: ((res: any) => void) | null,
- onFailure?: ((err: Error) => void) | null,
- text?: string,
+ promise: T,
+ onSuccess?: ((res: any) => void) | null,
+ onFailure?: ((err: Error) => void) | null,
+ text?: string
): T {
- const showing = ref(true);
- const success = ref(false);
+ const showing = ref(true);
+ const success = ref(false);
- promise
- .then((res) => {
- if (onSuccess) {
- showing.value = false;
- onSuccess(res);
- } else {
- success.value = true;
- window.setTimeout(() => {
- showing.value = false;
- }, 1000);
- }
- })
- .catch((err) => {
- showing.value = false;
- if (onFailure) {
- onFailure(err);
- } else {
- alert({
- type: "error",
- text: err,
- });
- }
- });
+ promise
+ .then((res) => {
+ if (onSuccess) {
+ showing.value = false;
+ onSuccess(res);
+ } else {
+ success.value = true;
+ window.setTimeout(() => {
+ showing.value = false;
+ }, 1000);
+ }
+ })
+ .catch((err) => {
+ showing.value = false;
+ if (onFailure) {
+ onFailure(err);
+ } else {
+ alert({
+ type: "error",
+ text: err,
+ });
+ }
+ });
- // NOTE: dynamic importすると挙動がおかしくなる(showingの変更が伝播しない)
- popup(
- MkWaitingDialog,
- {
- success: success,
- showing: showing,
- text: text,
- },
- {},
- "closed",
- );
+ // NOTE: dynamic importすると挙動がおかしくなる(showingの変更が伝播しない)
+ popup(
+ MkWaitingDialog,
+ {
+ success: success,
+ showing: showing,
+ text: text,
+ },
+ {},
+ "closed"
+ );
- return promise;
+ return promise;
}
let popupIdCount = 0;
export const popups = ref([]) as Ref<
- {
- id: any;
- component: any;
- props: Record;
- }[]
+ {
+ id: any;
+ component: any;
+ props: Record;
+ }[]
>;
const zIndexes = {
- low: 1000000,
- middle: 2000000,
- high: 3000000,
+ low: 1000000,
+ middle: 2000000,
+ high: 3000000,
};
export function claimZIndex(
- priority: "low" | "middle" | "high" = "low",
+ priority: "low" | "middle" | "high" = "low"
): number {
- zIndexes[priority] += 100;
- return zIndexes[priority];
+ zIndexes[priority] += 100;
+ return zIndexes[priority];
}
let uniqueId = 0;
export function getUniqueId(): string {
- return uniqueId++ + "";
+ return uniqueId++ + "";
}
export async function popup(
- component: Component,
- props: Record,
- events = {},
- disposeEvent?: string,
+ component: Component,
+ props: Record,
+ events = {},
+ disposeEvent?: string
) {
- markRaw(component);
+ markRaw(component);
- const id = ++popupIdCount;
- const dispose = () => {
- // このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ?
- window.setTimeout(() => {
- popups.value = popups.value.filter((popup) => popup.id !== id);
- }, 0);
- };
- const state = {
- component,
- props,
- events: disposeEvent
- ? {
- ...events,
- [disposeEvent]: dispose,
- }
- : events,
- id,
- };
+ const id = ++popupIdCount;
+ const dispose = () => {
+ // このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ?
+ window.setTimeout(() => {
+ popups.value = popups.value.filter((popup) => popup.id !== id);
+ }, 0);
+ };
+ const state = {
+ component,
+ props,
+ events: disposeEvent
+ ? {
+ ...events,
+ [disposeEvent]: dispose,
+ }
+ : events,
+ id,
+ };
- popups.value.push(state);
+ popups.value.push(state);
- return {
- dispose,
- };
+ return {
+ dispose,
+ };
}
export function pageWindow(path: string) {
- popup(
- defineAsyncComponent({
- loader: () => import("@/components/MkPageWindow.vue"),
- loadingComponent: MkWaitingDialog,
- delay: 1000,
- }),
- {
- initialPath: path,
- },
- {},
- "closed",
- );
+ popup(
+ defineAsyncComponent({
+ loader: () => import("@/components/MkPageWindow.vue"),
+ loadingComponent: MkWaitingDialog,
+ delay: 1000,
+ }),
+ {
+ initialPath: path,
+ },
+ {},
+ "closed"
+ );
}
export function modalPageWindow(path: string) {
- popup(
- defineAsyncComponent({
- loader: () => import("@/components/MkModalPageWindow.vue"),
- loadingComponent: MkWaitingDialog,
- delay: 1000,
- }),
- {
- initialPath: path,
- },
- {},
- "closed",
- );
+ popup(
+ defineAsyncComponent({
+ loader: () => import("@/components/MkModalPageWindow.vue"),
+ loadingComponent: MkWaitingDialog,
+ delay: 1000,
+ }),
+ {
+ initialPath: path,
+ },
+ {},
+ "closed"
+ );
}
export function toast(message: string) {
- popup(
- MkToast,
- {
- message,
- },
- {},
- "closed",
- );
+ popup(
+ MkToast,
+ {
+ message,
+ },
+ {},
+ "closed"
+ );
}
export function alert(props: {
- type?: "error" | "info" | "success" | "warning" | "waiting" | "question";
- title?: string | null;
- text?: string | null;
+ type?: "error" | "info" | "success" | "warning" | "waiting" | "question";
+ title?: string | null;
+ text?: string | null;
}): Promise {
- return new Promise((resolve, reject) => {
- if (props.text == null && props.type === "error") {
- props.text = i18n.ts.somethingHappened;
- }
- popup(
- MkDialog,
- props,
- {
- done: (result) => {
- resolve();
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ if (props.text == null && props.type === "error") {
+ props.text = i18n.ts.somethingHappened;
+ }
+ popup(
+ MkDialog,
+ props,
+ {
+ done: (result) => {
+ resolve();
+ },
+ },
+ "closed"
+ );
+ });
}
export function confirm(props: {
- type: "error" | "info" | "success" | "warning" | "waiting" | "question";
- title?: string | null;
- text?: string | null;
- okText?: string;
- cancelText?: string;
+ type: "error" | "info" | "success" | "warning" | "waiting" | "question";
+ title?: string | null;
+ text?: string | null;
+ okText?: string;
+ cancelText?: string;
}): Promise<{ canceled: boolean }> {
- return new Promise((resolve, reject) => {
- popup(
- MkDialog,
- {
- ...props,
- showCancelButton: true,
- },
- {
- done: (result) => {
- resolve(result ? result : { canceled: true });
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ MkDialog,
+ {
+ ...props,
+ showCancelButton: true,
+ },
+ {
+ done: (result) => {
+ resolve(result ? result : { canceled: true });
+ },
+ },
+ "closed"
+ );
+ });
}
export function yesno(props: {
- type: "error" | "info" | "success" | "warning" | "waiting" | "question";
- title?: string | null;
- text?: string | null;
+ type: "error" | "info" | "success" | "warning" | "waiting" | "question";
+ title?: string | null;
+ text?: string | null;
}): Promise<{ canceled: boolean }> {
- return new Promise((resolve, reject) => {
- popup(
- defineAsyncComponent({
- loader: () => import("@/components/MkDialog.vue"),
- loadingComponent: MkWaitingDialog,
- delay: 1000,
- }),
- {
- ...props,
- showCancelButton: true,
- isYesNo: true,
- },
- {
- done: (result) => {
- resolve(result ? result : { canceled: true });
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ defineAsyncComponent({
+ loader: () => import("@/components/MkDialog.vue"),
+ loadingComponent: MkWaitingDialog,
+ delay: 1000,
+ }),
+ {
+ ...props,
+ showCancelButton: true,
+ isYesNo: true,
+ },
+ {
+ done: (result) => {
+ resolve(result ? result : { canceled: true });
+ },
+ },
+ "closed"
+ );
+ });
}
export function inputText(props: {
- type?: "text" | "email" | "password" | "url" | "search";
- title?: string | null;
- text?: string | null;
- placeholder?: string | null;
- autocomplete?: string;
- default?: string | null;
- minLength?: number;
- maxLength?: number;
+ type?: "text" | "email" | "password" | "url" | "search";
+ title?: string | null;
+ text?: string | null;
+ placeholder?: string | null;
+ autocomplete?: string;
+ default?: string | null;
+ minLength?: number;
+ maxLength?: number;
}): Promise<
- | { canceled: true; result: undefined }
- | {
- canceled: false;
- result: string;
- }
+ | { canceled: true; result: undefined }
+ | {
+ canceled: false;
+ result: string;
+ }
> {
- return new Promise((resolve, reject) => {
- popup(
- MkDialog,
- {
- title: props.title,
- text: props.text,
- input: {
- type: props.type,
- placeholder: props.placeholder,
- autocomplete: props.autocomplete,
- default: props.default,
- minLength: props.minLength,
- maxLength: props.maxLength,
- },
- },
- {
- done: (result) => {
- resolve(result ? result : { canceled: true });
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ MkDialog,
+ {
+ title: props.title,
+ text: props.text,
+ input: {
+ type: props.type,
+ placeholder: props.placeholder,
+ autocomplete: props.autocomplete,
+ default: props.default,
+ minLength: props.minLength,
+ maxLength: props.maxLength,
+ },
+ },
+ {
+ done: (result) => {
+ resolve(result ? result : { canceled: true });
+ },
+ },
+ "closed"
+ );
+ });
}
export function inputParagraph(props: {
- title?: string | null;
- text?: string | null;
- placeholder?: string | null;
- default?: string | null;
+ title?: string | null;
+ text?: string | null;
+ placeholder?: string | null;
+ default?: string | null;
}): Promise<
- | { canceled: true; result: undefined }
- | {
- canceled: false;
- result: string;
- }
+ | { canceled: true; result: undefined }
+ | {
+ canceled: false;
+ result: string;
+ }
> {
- return new Promise((resolve, reject) => {
- popup(
- defineAsyncComponent({
- loader: () => import("@/components/MkDialog.vue"),
- loadingComponent: MkWaitingDialog,
- delay: 1000,
- }),
- {
- title: props.title,
- text: props.text,
- input: {
- type: "paragraph",
- placeholder: props.placeholder,
- default: props.default,
- },
- },
- {
- done: (result) => {
- resolve(result ? result : { canceled: true });
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ defineAsyncComponent({
+ loader: () => import("@/components/MkDialog.vue"),
+ loadingComponent: MkWaitingDialog,
+ delay: 1000,
+ }),
+ {
+ title: props.title,
+ text: props.text,
+ input: {
+ type: "paragraph",
+ placeholder: props.placeholder,
+ default: props.default,
+ },
+ },
+ {
+ done: (result) => {
+ resolve(result ? result : { canceled: true });
+ },
+ },
+ "closed"
+ );
+ });
}
export function inputNumber(props: {
- title?: string | null;
- text?: string | null;
- placeholder?: string | null;
- default?: number | null;
- autocomplete?: string;
+ title?: string | null;
+ text?: string | null;
+ placeholder?: string | null;
+ default?: number | null;
+ autocomplete?: string;
}): Promise<
- | { canceled: true; result: undefined }
- | {
- canceled: false;
- result: number;
- }
+ | { canceled: true; result: undefined }
+ | {
+ canceled: false;
+ result: number;
+ }
> {
- return new Promise((resolve, reject) => {
- popup(
- defineAsyncComponent({
- loader: () => import("@/components/MkDialog.vue"),
- loadingComponent: MkWaitingDialog,
- delay: 1000,
- }),
- {
- title: props.title,
- text: props.text,
- input: {
- type: "number",
- placeholder: props.placeholder,
- autocomplete: props.autocomplete,
- default: props.default,
- },
- },
- {
- done: (result) => {
- resolve(result ? result : { canceled: true });
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ defineAsyncComponent({
+ loader: () => import("@/components/MkDialog.vue"),
+ loadingComponent: MkWaitingDialog,
+ delay: 1000,
+ }),
+ {
+ title: props.title,
+ text: props.text,
+ input: {
+ type: "number",
+ placeholder: props.placeholder,
+ autocomplete: props.autocomplete,
+ default: props.default,
+ },
+ },
+ {
+ done: (result) => {
+ resolve(result ? result : { canceled: true });
+ },
+ },
+ "closed"
+ );
+ });
}
export function inputDate(props: {
- title?: string | null;
- text?: string | null;
- placeholder?: string | null;
- default?: Date | null;
+ title?: string | null;
+ text?: string | null;
+ placeholder?: string | null;
+ default?: Date | null;
}): Promise<
- | { canceled: true; result: undefined }
- | {
- canceled: false;
- result: Date;
- }
+ | { canceled: true; result: undefined }
+ | {
+ canceled: false;
+ result: Date;
+ }
> {
- return new Promise((resolve, reject) => {
- popup(
- MkDialog,
- {
- title: props.title,
- text: props.text,
- input: {
- type: "date",
- placeholder: props.placeholder,
- default: props.default,
- },
- },
- {
- done: (result) => {
- resolve(
- result
- ? {
- result: new Date(result.result),
- canceled: false,
- }
- : { canceled: true },
- );
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ MkDialog,
+ {
+ title: props.title,
+ text: props.text,
+ input: {
+ type: "date",
+ placeholder: props.placeholder,
+ default: props.default,
+ },
+ },
+ {
+ done: (result) => {
+ resolve(
+ result
+ ? {
+ result: new Date(result.result),
+ canceled: false,
+ }
+ : { canceled: true }
+ );
+ },
+ },
+ "closed"
+ );
+ });
}
export function select(
- props: {
- title?: string | null;
- text?: string | null;
- default?: string | null;
- } & (
- | {
- items: {
- value: C;
- text: string;
- }[];
- }
- | {
- groupedItems: {
- label: string;
- items: {
- value: C;
- text: string;
- }[];
- }[];
- }
- ),
+ props: {
+ title?: string | null;
+ text?: string | null;
+ default?: string | null;
+ } & (
+ | {
+ items: {
+ value: C;
+ text: string;
+ }[];
+ }
+ | {
+ groupedItems: {
+ label: string;
+ items: {
+ value: C;
+ text: string;
+ }[];
+ }[];
+ }
+ )
): Promise<
- | { canceled: true; result: undefined }
- | {
- canceled: false;
- result: C;
- }
+ | { canceled: true; result: undefined }
+ | {
+ canceled: false;
+ result: C;
+ }
> {
- return new Promise((resolve, reject) => {
- popup(
- MkDialog,
- {
- title: props.title,
- text: props.text,
- select: {
- items: props.items,
- groupedItems: props.groupedItems,
- default: props.default,
- },
- },
- {
- done: (result) => {
- resolve(result ? result : { canceled: true });
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ MkDialog,
+ {
+ title: props.title,
+ text: props.text,
+ select: {
+ items: props.items,
+ groupedItems: props.groupedItems,
+ default: props.default,
+ },
+ },
+ {
+ done: (result) => {
+ resolve(result ? result : { canceled: true });
+ },
+ },
+ "closed"
+ );
+ });
}
export function success(): Promise {
- return new Promise((resolve, reject) => {
- const showing = ref(true);
- window.setTimeout(() => {
- showing.value = false;
- }, 1000);
- popup(
- MkWaitingDialog,
- {
- success: true,
- showing: showing,
- },
- {
- done: () => resolve(),
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ const showing = ref(true);
+ window.setTimeout(() => {
+ showing.value = false;
+ }, 1000);
+ popup(
+ MkWaitingDialog,
+ {
+ success: true,
+ showing: showing,
+ },
+ {
+ done: () => resolve(),
+ },
+ "closed"
+ );
+ });
}
export function waiting(): Promise {
- return new Promise((resolve, reject) => {
- const showing = ref(true);
- popup(
- MkWaitingDialog,
- {
- success: false,
- showing: showing,
- },
- {
- done: () => resolve(),
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ const showing = ref(true);
+ popup(
+ MkWaitingDialog,
+ {
+ success: false,
+ showing: showing,
+ },
+ {
+ done: () => resolve(),
+ },
+ "closed"
+ );
+ });
}
export function form(title, form) {
- return new Promise((resolve, reject) => {
- popup(
- defineAsyncComponent({
- loader: () => import("@/components/MkFormDialog.vue"),
- loadingComponent: MkWaitingDialog,
- delay: 1000,
- }),
- { title, form },
- {
- done: (result) => {
- resolve(result);
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ defineAsyncComponent({
+ loader: () => import("@/components/MkFormDialog.vue"),
+ loadingComponent: MkWaitingDialog,
+ delay: 1000,
+ }),
+ { title, form },
+ {
+ done: (result) => {
+ resolve(result);
+ },
+ },
+ "closed"
+ );
+ });
}
export async function selectUser() {
- return new Promise((resolve, reject) => {
- popup(
- defineAsyncComponent({
- loader: () => import("@/components/MkUserSelectDialog.vue"),
- loadingComponent: MkWaitingDialog,
- delay: 1000,
- }),
- {},
- {
- ok: (user) => {
- resolve(user);
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ defineAsyncComponent({
+ loader: () => import("@/components/MkUserSelectDialog.vue"),
+ loadingComponent: MkWaitingDialog,
+ delay: 1000,
+ }),
+ {},
+ {
+ ok: (user) => {
+ resolve(user);
+ },
+ },
+ "closed"
+ );
+ });
}
export async function selectInstance(): Promise {
- return new Promise((resolve, reject) => {
- popup(
- defineAsyncComponent({
- loader: () => import("@/components/MkInstanceSelectDialog.vue"),
- loadingComponent: MkWaitingDialog,
- delay: 1000,
- }),
- {},
- {
- ok: (instance) => {
- resolve(instance);
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ defineAsyncComponent({
+ loader: () => import("@/components/MkInstanceSelectDialog.vue"),
+ loadingComponent: MkWaitingDialog,
+ delay: 1000,
+ }),
+ {},
+ {
+ ok: (instance) => {
+ resolve(instance);
+ },
+ },
+ "closed"
+ );
+ });
}
export async function selectDriveFile(multiple: boolean) {
- return new Promise((resolve, reject) => {
- popup(
- defineAsyncComponent({
- loader: () => import("@/components/MkDriveSelectDialog.vue"),
- loadingComponent: MkWaitingDialog,
- delay: 1000,
- }),
- {
- type: "file",
- multiple,
- },
- {
- done: (files) => {
- if (files) {
- resolve(multiple ? files : files[0]);
- }
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ defineAsyncComponent({
+ loader: () => import("@/components/MkDriveSelectDialog.vue"),
+ loadingComponent: MkWaitingDialog,
+ delay: 1000,
+ }),
+ {
+ type: "file",
+ multiple,
+ },
+ {
+ done: (files) => {
+ if (files) {
+ resolve(multiple ? files : files[0]);
+ }
+ },
+ },
+ "closed"
+ );
+ });
}
export async function selectDriveFolder(multiple: boolean) {
- return new Promise((resolve, reject) => {
- popup(
- defineAsyncComponent({
- loader: () => import("@/components/MkDriveSelectDialog.vue"),
- loadingComponent: MkWaitingDialog,
- delay: 1000,
- }),
- {
- type: "folder",
- multiple,
- },
- {
- done: (folders) => {
- if (folders) {
- resolve(multiple ? folders : folders[0]);
- }
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ defineAsyncComponent({
+ loader: () => import("@/components/MkDriveSelectDialog.vue"),
+ loadingComponent: MkWaitingDialog,
+ delay: 1000,
+ }),
+ {
+ type: "folder",
+ multiple,
+ },
+ {
+ done: (folders) => {
+ if (folders) {
+ resolve(multiple ? folders : folders[0]);
+ }
+ },
+ },
+ "closed"
+ );
+ });
}
export async function pickEmoji(src: HTMLElement | null, opts) {
- return new Promise((resolve, reject) => {
- popup(
- defineAsyncComponent({
- loader: () => import("@/components/MkEmojiPickerDialog.vue"),
- loadingComponent: MkWaitingDialog,
- delay: 1000,
- }),
- {
- src,
- ...opts,
- },
- {
- done: (emoji) => {
- resolve(emoji);
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ defineAsyncComponent({
+ loader: () => import("@/components/MkEmojiPickerDialog.vue"),
+ loadingComponent: MkWaitingDialog,
+ delay: 1000,
+ }),
+ {
+ src,
+ ...opts,
+ },
+ {
+ done: (emoji) => {
+ resolve(emoji);
+ },
+ },
+ "closed"
+ );
+ });
}
export async function cropImage(
- image: Misskey.entities.DriveFile,
- options: {
- aspectRatio: number;
- },
+ image: Misskey.entities.DriveFile,
+ options: {
+ aspectRatio: number;
+ }
): Promise {
- return new Promise((resolve, reject) => {
- popup(
- defineAsyncComponent({
- loader: () => import("@/components/MkCropperDialog.vue"),
- loadingComponent: MkWaitingDialog,
- delay: 1000,
- }),
- {
- file: image,
- aspectRatio: options.aspectRatio,
- },
- {
- ok: (x) => {
- resolve(x);
- },
- },
- "closed",
- );
- });
+ return new Promise((resolve, reject) => {
+ popup(
+ defineAsyncComponent({
+ loader: () => import("@/components/MkCropperDialog.vue"),
+ loadingComponent: MkWaitingDialog,
+ delay: 1000,
+ }),
+ {
+ file: image,
+ aspectRatio: options.aspectRatio,
+ },
+ {
+ ok: (x) => {
+ resolve(x);
+ },
+ },
+ "closed"
+ );
+ });
}
type AwaitType = T extends Promise
- ? U
- : T extends (...args: any[]) => Promise
- ? V
- : T;
+ ? U
+ : T extends (...args: any[]) => Promise
+ ? V
+ : T;
let openingEmojiPicker: AwaitType> | null = null;
let activeTextarea: HTMLTextAreaElement | HTMLInputElement | null = null;
export async function openEmojiPicker(
- src?: HTMLElement,
- opts,
- initialTextarea: typeof activeTextarea,
+ src?: HTMLElement,
+ opts,
+ initialTextarea: typeof activeTextarea
) {
- if (openingEmojiPicker) return;
+ if (openingEmojiPicker) return;
- activeTextarea = initialTextarea;
+ activeTextarea = initialTextarea;
- const textareas = document.querySelectorAll("textarea, input");
- for (const textarea of Array.from(textareas)) {
- textarea.addEventListener("focus", () => {
- activeTextarea = textarea;
- });
- }
+ const textareas = document.querySelectorAll("textarea, input");
+ for (const textarea of Array.from(textareas)) {
+ textarea.addEventListener("focus", () => {
+ activeTextarea = textarea;
+ });
+ }
- const observer = new MutationObserver((records) => {
- for (const record of records) {
- for (const node of Array.from(record.addedNodes).filter(
- (node) => node instanceof HTMLElement,
- ) as HTMLElement[]) {
- const textareas = node.querySelectorAll("textarea, input");
- for (const textarea of Array.from(textareas).filter(
- (textarea) => textarea.dataset.preventEmojiInsert == null,
- )) {
- if (document.activeElement === textarea) activeTextarea = textarea;
- textarea.addEventListener("focus", () => {
- activeTextarea = textarea;
- });
- }
- }
- }
- });
+ const observer = new MutationObserver((records) => {
+ for (const record of records) {
+ for (const node of Array.from(record.addedNodes).filter(
+ (node) => node instanceof HTMLElement
+ ) as HTMLElement[]) {
+ const textareas = node.querySelectorAll("textarea, input");
+ for (const textarea of Array.from(textareas).filter(
+ (textarea) => textarea.dataset.preventEmojiInsert == null
+ )) {
+ if (document.activeElement === textarea)
+ activeTextarea = textarea;
+ textarea.addEventListener("focus", () => {
+ activeTextarea = textarea;
+ });
+ }
+ }
+ }
+ });
- observer.observe(document.body, {
- childList: true,
- subtree: true,
- attributes: false,
- characterData: false,
- });
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true,
+ attributes: false,
+ characterData: false,
+ });
- openingEmojiPicker = await popup(
- defineAsyncComponent({
- loader: () => import("@/components/MkEmojiPickerDialog.vue"),
- loadingComponent: MkWaitingDialog,
- delay: 1000,
- }),
- {
- src,
- ...opts,
- },
- {
- chosen: (emoji) => {
- insertTextAtCursor(activeTextarea, emoji);
- },
- done: (emoji) => {
- insertTextAtCursor(activeTextarea, emoji);
- },
- closed: () => {
- openingEmojiPicker!.dispose();
- openingEmojiPicker = null;
- observer.disconnect();
- },
- },
- );
+ openingEmojiPicker = await popup(
+ defineAsyncComponent({
+ loader: () => import("@/components/MkEmojiPickerDialog.vue"),
+ loadingComponent: MkWaitingDialog,
+ delay: 1000,
+ }),
+ {
+ src,
+ ...opts,
+ },
+ {
+ chosen: (emoji) => {
+ insertTextAtCursor(activeTextarea, emoji);
+ },
+ done: (emoji) => {
+ insertTextAtCursor(activeTextarea, emoji);
+ },
+ closed: () => {
+ openingEmojiPicker!.dispose();
+ openingEmojiPicker = null;
+ observer.disconnect();
+ },
+ }
+ );
}
export function popupMenu(
- items: MenuItem[] | Ref