mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-18 23:32:32 +00:00
Prepare the 1.0 compatibility review flow
This commit is contained in:
@@ -1,18 +1,19 @@
|
||||
import { type App, type Plugin, Notice } from "@/deps";
|
||||
import { scheduleTask, memoIfNotExist, memoObject, retrieveMemoObject, disposeMemoObject } from "@/common/utils";
|
||||
import { EVENT_PLUGIN_UNLOADED } from "@/common/events";
|
||||
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
||||
import type { Confirm } from "@vrtmrz/livesync-commonlib/compat/interfaces/Confirm";
|
||||
import type { Confirm, ConfirmActionLayout } from "@vrtmrz/livesync-commonlib/compat/interfaces/Confirm";
|
||||
import { confirmAction, pickOne, promptPassword, promptText } from "@vrtmrz/obsidian-plugin-kit";
|
||||
import type { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext";
|
||||
import {
|
||||
askYesNo,
|
||||
askString,
|
||||
confirmWithMessageWithWideButton,
|
||||
askSelectString,
|
||||
confirmWithMessage,
|
||||
confirmWithMessage as confirmWithLegacyMessage,
|
||||
} from "@/modules/coreObsidian/UILib/dialogs";
|
||||
|
||||
export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceContext> implements Confirm {
|
||||
private _context: T;
|
||||
private readonly dialogueController = new AbortController();
|
||||
private readonly popupKeys = new Set<string>();
|
||||
get _app(): App {
|
||||
return this._context.app;
|
||||
}
|
||||
@@ -21,12 +22,58 @@ export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceC
|
||||
}
|
||||
constructor(context: T) {
|
||||
this._context = context;
|
||||
context.events.onceEvent(EVENT_PLUGIN_UNLOADED, () => {
|
||||
this.dialogueController.abort();
|
||||
for (const popupKey of this.popupKeys) {
|
||||
this.closePopup(popupKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
askYesNo(message: string): Promise<"yes" | "no"> {
|
||||
return askYesNo(this._app, message);
|
||||
|
||||
private get dialogueLifecycle() {
|
||||
return { signal: this.dialogueController.signal };
|
||||
}
|
||||
askString(title: string, key: string, placeholder: string, isPassword: boolean = false): Promise<string | false> {
|
||||
return askString(this._app, title, key, placeholder, isPassword);
|
||||
|
||||
private hasCountdown(timeout: number | undefined): timeout is number {
|
||||
return timeout !== undefined && timeout > 0;
|
||||
}
|
||||
|
||||
async askYesNo(message: string): Promise<"yes" | "no"> {
|
||||
const result = await confirmAction(
|
||||
this._app,
|
||||
{
|
||||
title: $msg("moduleInputUIObsidian.defaultTitleConfirmation"),
|
||||
message,
|
||||
actions: ["yes", "no"] as const,
|
||||
labels: {
|
||||
yes: $msg("moduleInputUIObsidian.optionYes"),
|
||||
no: $msg("moduleInputUIObsidian.optionNo"),
|
||||
},
|
||||
defaultAction: "no",
|
||||
sourcePath: "/",
|
||||
},
|
||||
this.dialogueLifecycle
|
||||
);
|
||||
return result === "yes" ? "yes" : "no";
|
||||
}
|
||||
|
||||
async askString(
|
||||
title: string,
|
||||
key: string,
|
||||
placeholder: string,
|
||||
isPassword: boolean = false
|
||||
): Promise<string | false> {
|
||||
const prompt = isPassword ? promptPassword : promptText;
|
||||
const result = await prompt(
|
||||
this._app,
|
||||
{
|
||||
title,
|
||||
label: key,
|
||||
placeholder,
|
||||
},
|
||||
this.dialogueLifecycle
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
async askYesNoDialog(
|
||||
@@ -37,6 +84,21 @@ export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceC
|
||||
const yesLabel = $msg("moduleInputUIObsidian.optionYes");
|
||||
const noLabel = $msg("moduleInputUIObsidian.optionNo");
|
||||
const defaultOption = opt.defaultOption === "Yes" ? yesLabel : noLabel;
|
||||
if (!this.hasCountdown(opt.timeout)) {
|
||||
const result = await confirmAction(
|
||||
this._app,
|
||||
{
|
||||
title: opt.title || defaultTitle,
|
||||
message,
|
||||
actions: ["Yes", "No"] as const,
|
||||
labels: { Yes: yesLabel, No: noLabel },
|
||||
defaultAction: opt.defaultOption === "Yes" ? "Yes" : "No",
|
||||
sourcePath: "/",
|
||||
},
|
||||
this.dialogueLifecycle
|
||||
);
|
||||
return result === "Yes" ? "yes" : "no";
|
||||
}
|
||||
const ret = await confirmWithMessageWithWideButton(
|
||||
this._plugin,
|
||||
opt.title || defaultTitle,
|
||||
@@ -48,16 +110,39 @@ export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceC
|
||||
return ret === yesLabel ? "yes" : "no";
|
||||
}
|
||||
|
||||
askSelectString(message: string, items: string[]): Promise<string> {
|
||||
return askSelectString(this._app, message, items);
|
||||
async askSelectString(message: string, items: string[]): Promise<string> {
|
||||
const result = await pickOne(
|
||||
this._app,
|
||||
{
|
||||
items,
|
||||
getText: (item) => item,
|
||||
placeholder: message,
|
||||
},
|
||||
this.dialogueLifecycle
|
||||
);
|
||||
return result ?? "";
|
||||
}
|
||||
|
||||
askSelectStringDialogue<T extends readonly string[]>(
|
||||
async askSelectStringDialogue<T extends readonly string[]>(
|
||||
message: string,
|
||||
buttons: T,
|
||||
opt: { title?: string; defaultAction: T[number]; timeout?: number }
|
||||
): Promise<T[number] | false> {
|
||||
const defaultTitle = $msg("moduleInputUIObsidian.defaultTitleSelect");
|
||||
if (!this.hasCountdown(opt.timeout)) {
|
||||
const result = await confirmAction(
|
||||
this._app,
|
||||
{
|
||||
title: opt.title || defaultTitle,
|
||||
message,
|
||||
actions: buttons,
|
||||
defaultAction: opt.defaultAction,
|
||||
sourcePath: "/",
|
||||
},
|
||||
this.dialogueLifecycle
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
return confirmWithMessageWithWideButton(
|
||||
this._plugin,
|
||||
opt.title || defaultTitle,
|
||||
@@ -68,7 +153,14 @@ export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceC
|
||||
);
|
||||
}
|
||||
|
||||
askInPopup(key: string, dialogText: string, anchorCallback: (anchor: HTMLAnchorElement) => void) {
|
||||
askInPopup(
|
||||
key: string,
|
||||
dialogText: string,
|
||||
anchorCallback: (anchor: HTMLAnchorElement) => void,
|
||||
durationMs: number = 20000
|
||||
) {
|
||||
const popupKey = "popup-" + key;
|
||||
this.popupKeys.add(popupKey);
|
||||
const fragment = createFragment((doc) => {
|
||||
const [beforeText, afterText] = dialogText.split("{HERE}", 2);
|
||||
doc.createSpan(undefined, (a) => {
|
||||
@@ -76,36 +168,62 @@ export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceC
|
||||
a.appendChild(
|
||||
a.createEl("a", undefined, (anchor) => {
|
||||
anchorCallback(anchor);
|
||||
anchor.addEventListener("click", () => this.closePopup(popupKey));
|
||||
})
|
||||
);
|
||||
a.appendText(afterText);
|
||||
});
|
||||
});
|
||||
const popupKey = "popup-" + key;
|
||||
scheduleTask(popupKey, 1000, async () => {
|
||||
if (this.dialogueController.signal.aborted) {
|
||||
this.popupKeys.delete(popupKey);
|
||||
return;
|
||||
}
|
||||
const popup = await memoIfNotExist(popupKey, () => new Notice(fragment, 0));
|
||||
const isShown = popup?.noticeEl?.isShown();
|
||||
if (!isShown) {
|
||||
memoObject(popupKey, new Notice(fragment, 0));
|
||||
}
|
||||
scheduleTask(popupKey + "-close", 20000, () => {
|
||||
const popup = retrieveMemoObject<Notice>(popupKey);
|
||||
if (!popup) return;
|
||||
if (popup?.noticeEl?.isShown()) {
|
||||
popup.hide();
|
||||
}
|
||||
disposeMemoObject(popupKey);
|
||||
});
|
||||
scheduleTask(popupKey + "-close", durationMs, () => this.closePopup(popupKey));
|
||||
});
|
||||
}
|
||||
|
||||
confirmWithMessage(
|
||||
private closePopup(popupKey: string) {
|
||||
const popup = retrieveMemoObject<Notice>(popupKey);
|
||||
if (!popup) {
|
||||
this.popupKeys.delete(popupKey);
|
||||
return;
|
||||
}
|
||||
if (popup.noticeEl?.isShown()) {
|
||||
popup.hide();
|
||||
}
|
||||
disposeMemoObject(popupKey);
|
||||
this.popupKeys.delete(popupKey);
|
||||
}
|
||||
|
||||
async confirmWithMessage(
|
||||
title: string,
|
||||
contentMd: string,
|
||||
buttons: string[],
|
||||
defaultAction: (typeof buttons)[number],
|
||||
timeout?: number
|
||||
timeout?: number,
|
||||
actionLayout?: ConfirmActionLayout
|
||||
): Promise<(typeof buttons)[number] | false> {
|
||||
return confirmWithMessage(this._plugin, title, contentMd, buttons, defaultAction, timeout);
|
||||
if (this.hasCountdown(timeout)) {
|
||||
return confirmWithLegacyMessage(this._plugin, title, contentMd, buttons, defaultAction, timeout);
|
||||
}
|
||||
const result = await confirmAction(
|
||||
this._app,
|
||||
{
|
||||
title,
|
||||
message: contentMd,
|
||||
actions: buttons,
|
||||
defaultAction,
|
||||
sourcePath: "/",
|
||||
...(actionLayout === undefined ? {} : { actionLayout }),
|
||||
},
|
||||
this.dialogueLifecycle
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user