### Fixed

- No longer `No available splitter for settings!!` errors occur after fetching old remote settings while rebuilding local database.

### Improved

- Boot sequence warning is now kept in the in-editor notification area. (#748)

### New feature

- We can now set the maximum modified time for reflect events in the settings. (for #754)

### Refactored
- Module to service refactoring has been started for better maintainability:
  - UI module has been moved to UI service.

### Behaviour change
- Default chunk splitter version has been changed to `Rabin-Karp` for new installations.
This commit is contained in:
vorotamoroz
2026-01-24 16:25:04 +09:00
parent 465af4f3aa
commit 7c43c61b85
9 changed files with 248 additions and 139 deletions
+1 -1
Submodule src/lib updated: 5b42808773...cd32d3d326
+3 -3
View File
@@ -23,7 +23,6 @@ import type { IObsidianModule } from "./modules/AbstractObsidianModule.ts";
import { ModuleDev } from "./modules/extras/ModuleDev.ts"; import { ModuleDev } from "./modules/extras/ModuleDev.ts";
import { ModuleFileAccessObsidian } from "./modules/coreObsidian/ModuleFileAccessObsidian.ts"; import { ModuleFileAccessObsidian } from "./modules/coreObsidian/ModuleFileAccessObsidian.ts";
import { ModuleInputUIObsidian } from "./modules/coreObsidian/ModuleInputUIObsidian.ts";
import { ModuleMigration } from "./modules/essential/ModuleMigration.ts"; import { ModuleMigration } from "./modules/essential/ModuleMigration.ts";
import { ModuleCheckRemoteSize } from "./modules/essentialObsidian/ModuleCheckRemoteSize.ts"; import { ModuleCheckRemoteSize } from "./modules/essentialObsidian/ModuleCheckRemoteSize.ts";
@@ -137,7 +136,6 @@ export default class ObsidianLiveSyncPlugin
new ModuleObsidianSettingsAsMarkdown(this, this), new ModuleObsidianSettingsAsMarkdown(this, this),
new ModuleObsidianSettingDialogue(this, this), new ModuleObsidianSettingDialogue(this, this),
new ModuleLog(this, this), new ModuleLog(this, this),
new ModuleInputUIObsidian(this, this),
new ModuleObsidianMenu(this, this), new ModuleObsidianMenu(this, this),
new ModuleRebuilder(this), new ModuleRebuilder(this),
new ModuleSetupObsidian(this, this), new ModuleSetupObsidian(this, this),
@@ -166,7 +164,9 @@ export default class ObsidianLiveSyncPlugin
managers!: LiveSyncManagers; managers!: LiveSyncManagers;
simpleStore!: SimpleStore<CheckPointInfo>; simpleStore!: SimpleStore<CheckPointInfo>;
replicator!: LiveSyncAbstractReplicator; replicator!: LiveSyncAbstractReplicator;
confirm!: Confirm; get confirm(): Confirm {
return this.services.UI.confirm;
}
storageAccess!: StorageAccess; storageAccess!: StorageAccess;
databaseFileAccess!: DatabaseFileAccess; databaseFileAccess!: DatabaseFileAccess;
fileHandler!: ModuleFileHandler; fileHandler!: ModuleFileHandler;
+26
View File
@@ -194,6 +194,32 @@ Please enable them from the settings screen after setup is complete.`,
// await this.askUseNewAdapter(); // await this.askUseNewAdapter();
this.core.settings.isConfigured = true; this.core.settings.isConfigured = true;
this.core.settings.notifyThresholdOfRemoteStorageSize = DEFAULT_SETTINGS.notifyThresholdOfRemoteStorageSize; this.core.settings.notifyThresholdOfRemoteStorageSize = DEFAULT_SETTINGS.notifyThresholdOfRemoteStorageSize;
if (this.core.settings.maxMTimeForReflectEvents > 0) {
const date = new Date(this.core.settings.maxMTimeForReflectEvents);
const ask = `Your settings restrict file reflection times to no later than ${date}.
**This is a recovery configuration.**
This operation should only be performed on an empty vault.
Are you sure you wish to proceed?`;
const PROCEED = "I understand, proceed";
const CANCEL = "Cancel operation";
const CLEARANDPROCEED = "Clear restriction and proceed";
const choices = [PROCEED, CLEARANDPROCEED, CANCEL] as const;
const ret = await this.core.confirm.askSelectStringDialogue(ask, choices, {
title: "Confirm restricted fetch",
defaultAction: CANCEL,
timeout: 0,
});
if (ret == CLEARANDPROCEED) {
this.core.settings.maxMTimeForReflectEvents = 0;
await this.core.saveSettings();
}
if (ret == CANCEL) {
return;
}
}
await this.suspendReflectingDatabase(); await this.suspendReflectingDatabase();
await this.services.setting.realiseSetting(); await this.services.setting.realiseSetting();
await this.resetLocalDatabase(); await this.resetLocalDatabase();
@@ -318,6 +318,20 @@ export class ReplicateResultProcessor {
*/ */
async parseDocumentChange(change: PouchDB.Core.ExistingDocument<EntryDoc>) { async parseDocumentChange(change: PouchDB.Core.ExistingDocument<EntryDoc>) {
try { try {
if (isAnyNote(change)) {
const docMtime = change.mtime ?? 0;
const maxMTime = this.replicator.settings.maxMTimeForReflectEvents;
if (maxMTime > 0 && docMtime > maxMTime) {
const docPath = getPath(change);
this.log(
`Processing ${docPath} has been skipped due to modification time (${new Date(
docMtime * 1000
).toISOString()}) exceeding the limit`,
LOG_LEVEL_INFO
);
return;
}
}
// If the document is a virtual document, process it in the virtual document processor. // If the document is a virtual document, process it in the virtual document processor.
if (await this.services.replication.processVirtualDocument(change)) return; if (await this.services.replication.processVirtualDocument(change)) return;
// If the document is version info, check compatibility and return. // If the document is version info, check compatibility and return.
@@ -1,118 +0,0 @@
// ModuleInputUIObsidian.ts
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { scheduleTask } from "octagonal-wheels/concurrency/task";
import { disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject } from "../../common/utils.ts";
import {
askSelectString,
askString,
askYesNo,
confirmWithMessage,
confirmWithMessageWithWideButton,
} from "./UILib/dialogs.ts";
import { Notice } from "../../deps.ts";
import type { Confirm } from "../../lib/src/interfaces/Confirm.ts";
import { setConfirmInstance } from "../../lib/src/PlatformAPIs/obsidian/Confirm.ts";
import { $msg } from "src/lib/src/common/i18n.ts";
import type { LiveSyncCore } from "../../main.ts";
// This module cannot be a common module because it depends on Obsidian's API.
// However, we have to make compatible one for other platform.
export class ModuleInputUIObsidian extends AbstractObsidianModule implements Confirm {
private _everyOnload(): Promise<boolean> {
this.core.confirm = this;
setConfirmInstance(this);
return Promise.resolve(true);
}
askYesNo(message: string): Promise<"yes" | "no"> {
return askYesNo(this.app, message);
}
askString(title: string, key: string, placeholder: string, isPassword: boolean = false): Promise<string | false> {
return askString(this.app, title, key, placeholder, isPassword);
}
async askYesNoDialog(
message: string,
opt: { title?: string; defaultOption?: "Yes" | "No"; timeout?: number } = { title: "Confirmation" }
): Promise<"yes" | "no"> {
const defaultTitle = $msg("moduleInputUIObsidian.defaultTitleConfirmation");
const yesLabel = $msg("moduleInputUIObsidian.optionYes");
const noLabel = $msg("moduleInputUIObsidian.optionNo");
const defaultOption = opt.defaultOption === "Yes" ? yesLabel : noLabel;
const ret = await confirmWithMessageWithWideButton(
this.plugin,
opt.title || defaultTitle,
message,
[yesLabel, noLabel],
defaultOption,
opt.timeout
);
return ret === yesLabel ? "yes" : "no";
}
askSelectString(message: string, items: string[]): Promise<string> {
return askSelectString(this.app, message, items);
}
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");
return confirmWithMessageWithWideButton(
this.plugin,
opt.title || defaultTitle,
message,
buttons,
opt.defaultAction,
opt.timeout
);
}
askInPopup(key: string, dialogText: string, anchorCallback: (anchor: HTMLAnchorElement) => void) {
const fragment = createFragment((doc) => {
const [beforeText, afterText] = dialogText.split("{HERE}", 2);
doc.createEl("span", undefined, (a) => {
a.appendText(beforeText);
a.appendChild(
a.createEl("a", undefined, (anchor) => {
anchorCallback(anchor);
})
);
a.appendText(afterText);
});
});
const popupKey = "popup-" + key;
scheduleTask(popupKey, 1000, async () => {
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);
});
});
}
confirmWithMessage(
title: string,
contentMd: string,
buttons: string[],
defaultAction: (typeof buttons)[number],
timeout?: number
): Promise<(typeof buttons)[number] | false> {
return confirmWithMessage(this.plugin, title, contentMd, buttons, defaultAction, timeout);
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this));
}
}
@@ -243,6 +243,9 @@ export class StorageEventManagerObsidian extends StorageEventManager {
async appendQueue(params: FileEvent[], ctx?: any) { async appendQueue(params: FileEvent[], ctx?: any) {
if (!this.core.settings.isConfigured) return; if (!this.core.settings.isConfigured) return;
if (this.core.settings.suspendFileWatching) return; if (this.core.settings.suspendFileWatching) return;
if (this.core.settings.maxMTimeForReflectEvents > 0) {
return;
}
this.core.services.vault.markFileListPossiblyChanged(); this.core.services.vault.markFileListPossiblyChanged();
// Flag up to be reload // Flag up to be reload
for (const param of params) { for (const param of params) {
+36 -16
View File
@@ -1,6 +1,6 @@
import { unique } from "octagonal-wheels/collection"; import { unique } from "octagonal-wheels/collection";
import { throttle } from "octagonal-wheels/function"; import { throttle } from "octagonal-wheels/function";
import { eventHub } from "../../common/events.ts"; import { EVENT_ON_UNRESOLVED_ERROR, eventHub } from "../../common/events.ts";
import { BASE_IS_NEW, compareFileFreshness, EVEN, getPath, isValidPath, TARGET_IS_NEW } from "../../common/utils.ts"; import { BASE_IS_NEW, compareFileFreshness, EVEN, getPath, isValidPath, TARGET_IS_NEW } from "../../common/utils.ts";
import { import {
type FilePathWithPrefixLC, type FilePathWithPrefixLC,
@@ -13,6 +13,7 @@ import {
LOG_LEVEL_INFO, LOG_LEVEL_INFO,
LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG,
type UXFileInfoStub, type UXFileInfoStub,
type LOG_LEVEL,
} from "../../lib/src/common/types.ts"; } from "../../lib/src/common/types.ts";
import { isAnyNote } from "../../lib/src/common/utils.ts"; import { isAnyNote } from "../../lib/src/common/utils.ts";
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts"; import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
@@ -21,30 +22,43 @@ import { withConcurrency } from "octagonal-wheels/iterable/map";
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts"; import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
import type { LiveSyncCore } from "../../main.ts"; import type { LiveSyncCore } from "../../main.ts";
export class ModuleInitializerFile extends AbstractModule { export class ModuleInitializerFile extends AbstractModule {
private _detectedErrors = new Set<string>();
private logDetectedError(message: string, logLevel: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) {
this._detectedErrors.add(message);
eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR);
this._log(message, logLevel, key);
}
private resetDetectedError(message: string) {
eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR);
this._detectedErrors.delete(message);
}
private async _performFullScan(showingNotice?: boolean, ignoreSuspending: boolean = false): Promise<boolean> { private async _performFullScan(showingNotice?: boolean, ignoreSuspending: boolean = false): Promise<boolean> {
this._log("Opening the key-value database", LOG_LEVEL_VERBOSE); this._log("Opening the key-value database", LOG_LEVEL_VERBOSE);
const isInitialized = (await this.core.kvDB.get<boolean>("initialized")) || false; const isInitialized = (await this.core.kvDB.get<boolean>("initialized")) || false;
// synchronize all files between database and storage. // synchronize all files between database and storage.
const ERR_NOT_CONFIGURED =
"LiveSync is not configured yet. Synchronising between the storage and the local database is now prevented.";
if (!this.settings.isConfigured) { if (!this.settings.isConfigured) {
if (showingNotice) { this.logDetectedError(ERR_NOT_CONFIGURED, showingNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO, "syncAll");
this._log(
"LiveSync is not configured yet. Synchronising between the storage and the local database is now prevented.",
LOG_LEVEL_NOTICE,
"syncAll"
);
}
return false; return false;
} }
this.resetDetectedError(ERR_NOT_CONFIGURED);
const ERR_SUSPENDING =
"Now suspending file watching. Synchronising between the storage and the local database is now prevented.";
if (!ignoreSuspending && this.settings.suspendFileWatching) { if (!ignoreSuspending && this.settings.suspendFileWatching) {
if (showingNotice) { this.logDetectedError(ERR_SUSPENDING, showingNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO, "syncAll");
this._log(
"Now suspending file watching. Synchronising between the storage and the local database is now prevented.",
LOG_LEVEL_NOTICE,
"syncAll"
);
}
return false; return false;
} }
const MSG_IN_REMEDIATION = `Started in remediation Mode! (Max mtime for reflect events is set). Synchronising between the storage and the local database is now prevented.`;
this.resetDetectedError(ERR_SUSPENDING);
if (this.settings.maxMTimeForReflectEvents > 0) {
this.logDetectedError(MSG_IN_REMEDIATION, LOG_LEVEL_NOTICE, "syncAll");
return false;
}
this.resetDetectedError(MSG_IN_REMEDIATION);
if (showingNotice) { if (showingNotice) {
this._log("Initializing", LOG_LEVEL_NOTICE, "syncAll"); this._log("Initializing", LOG_LEVEL_NOTICE, "syncAll");
@@ -383,10 +397,12 @@ export class ModuleInitializerFile extends AbstractModule {
if (this.localDatabase.isReady) { if (this.localDatabase.isReady) {
await this.services.vault.scanVault(showingNotice, ignoreSuspending); await this.services.vault.scanVault(showingNotice, ignoreSuspending);
} }
const ERR_INITIALISATION_FAILED = `Initializing database has been failed on some module!`;
if (!(await this.services.databaseEvents.onDatabaseInitialised(showingNotice))) { if (!(await this.services.databaseEvents.onDatabaseInitialised(showingNotice))) {
this._log(`Initializing database has been failed on some module!`, LOG_LEVEL_NOTICE); this.logDetectedError(ERR_INITIALISATION_FAILED, LOG_LEVEL_NOTICE);
return false; return false;
} }
this.resetDetectedError(ERR_INITIALISATION_FAILED);
this.services.appLifecycle.markIsReady(); this.services.appLifecycle.markIsReady();
// run queued event once. // run queued event once.
await this.services.fileProcessing.commitPendingFileEvents(); await this.services.fileProcessing.commitPendingFileEvents();
@@ -396,7 +412,11 @@ export class ModuleInitializerFile extends AbstractModule {
return false; return false;
} }
} }
private _reportDetectedErrors(): Promise<string[]> {
return Promise.resolve(Array.from(this._detectedErrors));
}
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
services.appLifecycle.getUnresolvedMessages.addHandler(this._reportDetectedErrors.bind(this));
services.databaseEvents.initialiseDatabase.setHandler(this._initializeDatabase.bind(this)); services.databaseEvents.initialiseDatabase.setHandler(this._initializeDatabase.bind(this));
services.vault.scanVault.setHandler(this._performFullScan.bind(this)); services.vault.scanVault.setHandler(this._performFullScan.bind(this));
} }
@@ -173,7 +173,62 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen
void addPanel(paneEl, "Compatibility (Trouble addressed)").then((paneEl) => { void addPanel(paneEl, "Compatibility (Trouble addressed)").then((paneEl) => {
new Setting(paneEl).autoWireToggle("disableCheckingConfigMismatch"); new Setting(paneEl).autoWireToggle("disableCheckingConfigMismatch");
}); });
void addPanel(paneEl, "Remediation").then((paneEl) => {
let dateEl: HTMLSpanElement;
new Setting(paneEl)
.addText((text) => {
const updateDateText = () => {
if (this.editingSettings.maxMTimeForReflectEvents == 0) {
dateEl.textContent = `No limit configured`;
} else {
const date = new Date(this.editingSettings.maxMTimeForReflectEvents);
dateEl.textContent = `Limit: ${date.toLocaleString()} (${this.editingSettings.maxMTimeForReflectEvents})`;
}
this.requestUpdate();
};
text.inputEl.before((dateEl = document.createElement("span")));
text.inputEl.type = "datetime-local";
if (this.editingSettings.maxMTimeForReflectEvents > 0) {
const date = new Date(this.editingSettings.maxMTimeForReflectEvents);
const isoString = date.toISOString().slice(0, 16);
text.setValue(isoString);
} else {
text.setValue("");
}
text.onChange((value) => {
if (value == "") {
this.editingSettings.maxMTimeForReflectEvents = 0;
updateDateText();
return;
}
const date = new Date(value);
if (!isNaN(date.getTime())) {
this.editingSettings.maxMTimeForReflectEvents = date.getTime();
}
updateDateText();
});
updateDateText();
return text;
})
.setAuto("maxMTimeForReflectEvents")
.addApplyButton(["maxMTimeForReflectEvents"]);
this.addOnSaved("maxMTimeForReflectEvents", async (key) => {
const buttons = ["Restart Now", "Later"] as const;
const reboot = await this.plugin.confirm.askSelectStringDialogue(
"Restarting Obsidian is strongly recommended. Until restart, some changes may not take effect, and display may be inconsistent. Are you sure to restart now?",
buttons,
{
title: "Remediation Setting Changed",
defaultAction: "Restart Now",
}
);
if (reboot !== "Later") {
Logger("Remediation setting changed. Restarting Obsidian...", LOG_LEVEL_NOTICE);
this.services.appLifecycle.performRestart();
}
});
});
void addPanel(paneEl, "Remote Database Tweak (In sunset)").then((paneEl) => { void addPanel(paneEl, "Remote Database Tweak (In sunset)").then((paneEl) => {
// new Setting(paneEl).autoWireToggle("useEden").setClass("wizardHidden"); // new Setting(paneEl).autoWireToggle("useEden").setClass("wizardHidden");
// const onlyUsingEden = visibleOnly(() => this.isConfiguredAs("useEden", true)); // const onlyUsingEden = visibleOnly(() => this.isConfiguredAs("useEden", true));
+110 -1
View File
@@ -1,14 +1,118 @@
import { UIService } from "../../lib/src/services/Services"; import { UIService } from "../../lib/src/services/Services";
import type { Plugin } from "@/deps"; import { Notice, type App, type Plugin } from "@/deps";
import { SvelteDialogManager } from "../features/SetupWizard/ObsidianSvelteDialog"; import { SvelteDialogManager } from "../features/SetupWizard/ObsidianSvelteDialog";
import DialogueToCopy from "../../lib/src/UI/dialogues/DialogueToCopy.svelte"; import DialogueToCopy from "../../lib/src/UI/dialogues/DialogueToCopy.svelte";
import type { ObsidianServiceContext } from "./ObsidianServices"; import type { ObsidianServiceContext } from "./ObsidianServices";
import type ObsidianLiveSyncPlugin from "@/main"; import type ObsidianLiveSyncPlugin from "@/main";
import type { Confirm } from "@/lib/src/interfaces/Confirm";
import {
askSelectString,
askString,
askYesNo,
confirmWithMessage,
confirmWithMessageWithWideButton,
} from "../coreObsidian/UILib/dialogs";
import { $msg } from "@/lib/src/common/i18n";
import { disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject, scheduleTask } from "@/common/utils";
export class ObsidianConfirm implements Confirm {
private _app: App;
private _plugin: Plugin;
constructor(app: App, plugin: Plugin) {
this._app = app;
this._plugin = plugin;
}
askYesNo(message: string): Promise<"yes" | "no"> {
return askYesNo(this._app, message);
}
askString(title: string, key: string, placeholder: string, isPassword: boolean = false): Promise<string | false> {
return askString(this._app, title, key, placeholder, isPassword);
}
async askYesNoDialog(
message: string,
opt: { title?: string; defaultOption?: "Yes" | "No"; timeout?: number } = { title: "Confirmation" }
): Promise<"yes" | "no"> {
const defaultTitle = $msg("moduleInputUIObsidian.defaultTitleConfirmation");
const yesLabel = $msg("moduleInputUIObsidian.optionYes");
const noLabel = $msg("moduleInputUIObsidian.optionNo");
const defaultOption = opt.defaultOption === "Yes" ? yesLabel : noLabel;
const ret = await confirmWithMessageWithWideButton(
this._plugin,
opt.title || defaultTitle,
message,
[yesLabel, noLabel],
defaultOption,
opt.timeout
);
return ret === yesLabel ? "yes" : "no";
}
askSelectString(message: string, items: string[]): Promise<string> {
return askSelectString(this._app, message, items);
}
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");
return confirmWithMessageWithWideButton(
this._plugin,
opt.title || defaultTitle,
message,
buttons,
opt.defaultAction,
opt.timeout
);
}
askInPopup(key: string, dialogText: string, anchorCallback: (anchor: HTMLAnchorElement) => void) {
const fragment = createFragment((doc) => {
const [beforeText, afterText] = dialogText.split("{HERE}", 2);
doc.createEl("span", undefined, (a) => {
a.appendText(beforeText);
a.appendChild(
a.createEl("a", undefined, (anchor) => {
anchorCallback(anchor);
})
);
a.appendText(afterText);
});
});
const popupKey = "popup-" + key;
scheduleTask(popupKey, 1000, async () => {
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);
});
});
}
confirmWithMessage(
title: string,
contentMd: string,
buttons: string[],
defaultAction: (typeof buttons)[number],
timeout?: number
): Promise<(typeof buttons)[number] | false> {
return confirmWithMessage(this._plugin, title, contentMd, buttons, defaultAction, timeout);
}
}
export class ObsidianUIService extends UIService<ObsidianServiceContext> { export class ObsidianUIService extends UIService<ObsidianServiceContext> {
private _dialogManager: SvelteDialogManager; private _dialogManager: SvelteDialogManager;
private _plugin: Plugin; private _plugin: Plugin;
private _liveSyncPlugin: ObsidianLiveSyncPlugin; private _liveSyncPlugin: ObsidianLiveSyncPlugin;
private _confirmInstance: ObsidianConfirm;
get dialogManager() { get dialogManager() {
return this._dialogManager; return this._dialogManager;
} }
@@ -17,6 +121,7 @@ export class ObsidianUIService extends UIService<ObsidianServiceContext> {
this._liveSyncPlugin = context.liveSyncPlugin; this._liveSyncPlugin = context.liveSyncPlugin;
this._dialogManager = new SvelteDialogManager(this._liveSyncPlugin); this._dialogManager = new SvelteDialogManager(this._liveSyncPlugin);
this._plugin = context.plugin; this._plugin = context.plugin;
this._confirmInstance = new ObsidianConfirm(this._plugin.app, this._plugin);
} }
async promptCopyToClipboard(title: string, value: string): Promise<boolean> { async promptCopyToClipboard(title: string, value: string): Promise<boolean> {
@@ -44,4 +149,8 @@ export class ObsidianUIService extends UIService<ObsidianServiceContext> {
timeout: 0, timeout: 0,
}); });
} }
get confirm(): Confirm {
return this._confirmInstance;
}
} }