mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-16 14:22:32 +00:00
### 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:
+1
-1
Submodule src/lib updated: 5b42808773...cd32d3d326
+3
-3
@@ -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;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user