diff --git a/src/common/events.ts b/src/common/events.ts index 97a1d1a..93be1e7 100644 --- a/src/common/events.ts +++ b/src/common/events.ts @@ -1,3 +1,7 @@ +import type { FilePathWithPrefix, ObsidianLiveSyncSettings } from "../lib/src/common/types"; +import { eventHub } from "../lib/src/hub/hub"; +import type ObsidianLiveSyncPlugin from "../main"; + export const EVENT_LAYOUT_READY = "layout-ready"; export const EVENT_PLUGIN_LOADED = "plugin-loaded"; export const EVENT_PLUGIN_UNLOADED = "plugin-unloaded"; @@ -13,7 +17,7 @@ export const EVENT_REQUEST_OPEN_SETTING_WIZARD = "request-open-setting-wizard"; export const EVENT_REQUEST_OPEN_SETUP_URI = "request-open-setup-uri"; export const EVENT_REQUEST_COPY_SETUP_URI = "request-copy-setup-uri"; -export const EVENT_REQUEST_SHOW_HISTORY = "show-history"; + export const EVENT_REQUEST_RELOAD_SETTING_TAB = "reload-setting-tab"; @@ -22,8 +26,28 @@ export const EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG = "request-open-plugin-sync-d // export const EVENT_FILE_CHANGED = "file-changed"; -import { eventHub } from "../lib/src/hub/hub"; -// TODO: Add overloads for the emit method to allow for type checking +declare global { + interface LSEvents { + [EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG]: undefined; + [EVENT_FILE_SAVED]: undefined; + [EVENT_REQUEST_OPEN_SETUP_URI]: undefined; + [EVENT_REQUEST_COPY_SETUP_URI]: undefined; + [EVENT_REQUEST_RELOAD_SETTING_TAB]: undefined; + [EVENT_PLUGIN_UNLOADED]: undefined; + [EVENT_SETTING_SAVED]: ObsidianLiveSyncSettings; + [EVENT_PLUGIN_LOADED]: ObsidianLiveSyncPlugin; + [EVENT_LAYOUT_READY]: undefined; + "event-file-changed": { file: FilePathWithPrefix, automated: boolean }; + "document-stub-created": + { + toc: Set, stub: { [key: string]: { [key: string]: Map> } } + }, + [EVENT_REQUEST_OPEN_SETTINGS]: undefined; + [EVENT_REQUEST_OPEN_SETTING_WIZARD]: undefined; + [EVENT_FILE_RENAMED]: { newPath: FilePathWithPrefix, old: FilePathWithPrefix }; + [EVENT_LEAF_ACTIVE_CHANGED]: undefined; + } +} export { eventHub }; diff --git a/src/common/utils.ts b/src/common/utils.ts index 7e25921..0427ce4 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -143,7 +143,7 @@ export class PeriodicProcessor { if (interval == 0) return; this._timer = window.setInterval(() => fireAndForget(async () => { await this.process(); - if (this._plugin._unloaded) { + if (this._plugin.$$isUnloaded()) { this.disable(); } }), interval); diff --git a/src/features/ConfigSync/CmdConfigSync.ts b/src/features/ConfigSync/CmdConfigSync.ts index 62acee4..42d7003 100644 --- a/src/features/ConfigSync/CmdConfigSync.ts +++ b/src/features/ConfigSync/CmdConfigSync.ts @@ -543,7 +543,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule { filenameToUnifiedKey(path: string, termOverRide?: string) { - const term = termOverRide || this.plugin.deviceAndVaultName; + const term = termOverRide || this.plugin.$$getDeviceAndVaultName(); const category = this.getFileCategory(path); const name = (category == "CONFIG" || category == "SNIPPET") ? (path.split("/").slice(-1)[0]) : @@ -554,7 +554,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule { } filenameWithUnifiedKey(path: string, termOverRide?: string) { - const term = termOverRide || this.plugin.deviceAndVaultName; + const term = termOverRide || this.plugin.$$getDeviceAndVaultName(); const category = this.getFileCategory(path); const name = (category == "CONFIG" || category == "SNIPPET") ? (path.split("/").slice(-1)[0]) : path.split("/").slice(-2)[0]; @@ -563,7 +563,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule { } unifiedKeyPrefixOfTerminal(termOverRide?: string) { - const term = termOverRide || this.plugin.deviceAndVaultName; + const term = termOverRide || this.plugin.$$getDeviceAndVaultName(); return `${ICXHeader}${term}/` as FilePathWithPrefix; } @@ -870,7 +870,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule { await this.plugin.storageAccess.ensureDir(path); // If the content has applied, modified time will be updated to the current time. await this.plugin.storageAccess.writeHiddenFileAuto(path, content); - await this.storeCustomisationFileV2(path, this.plugin.deviceAndVaultName); + await this.storeCustomisationFileV2(path, this.plugin.$$getDeviceAndVaultName()); } else { const files = data.files; @@ -914,7 +914,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule { await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat); } this._log(`Applied ${f.filename} of ${data.displayName || data.name}..`); - await this.storeCustomisationFileV2(path, this.plugin.deviceAndVaultName); + await this.storeCustomisationFileV2(path, this.plugin.$$getDeviceAndVaultName()); } } } catch (ex) { @@ -1189,7 +1189,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule { }) } async storeCustomizationFiles(path: FilePath, termOverRide?: string) { - const term = termOverRide || this.plugin.deviceAndVaultName; + const term = termOverRide || this.plugin.$$getDeviceAndVaultName(); if (term == "") { this._log("We have to configure the device name", LOG_LEVEL_NOTICE); return; @@ -1362,7 +1362,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule { await shareRunningResult("scanAllConfigFiles", async () => { const logLevel = showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO; this._log("Scanning customizing files.", logLevel, "scan-all-config"); - const term = this.plugin.deviceAndVaultName; + const term = this.plugin.$$getDeviceAndVaultName(); if (term == "") { this._log("We have to configure the device name", LOG_LEVEL_NOTICE); return; @@ -1505,7 +1505,10 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule { choices.push(CHOICE_DISABLE); choices.push(CHOICE_DISMISS); - const ret = await this.plugin.confirm.confirmWithMessage("Customisation sync", message, choices, CHOICE_DISMISS, 40); + const ret = await this.plugin.confirm.askSelectStringDialogue(message, choices, { + defaultAction: CHOICE_DISMISS, timeout: 40, + title: "Customisation sync" + }); if (ret == CHOICE_CUSTOMIZE) { await this.configureHiddenFileSync("CUSTOMIZE"); } else if (ret == CHOICE_DISABLE) { @@ -1544,7 +1547,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule { } if (mode == "CUSTOMIZE") { - if (!this.plugin.deviceAndVaultName) { + if (!this.plugin.$$getDeviceAndVaultName()) { let name = await this.plugin.confirm.askString("Device name", "Please set this device name", `desktop`); if (!name) { if (Platform.isAndroidApp) { @@ -1568,7 +1571,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule { } name = name + Math.random().toString(36).slice(-4); } - this.plugin.deviceAndVaultName = name; + this.plugin.$$setDeviceAndVaultName(name); } this.plugin.settings.usePluginSync = true; this.plugin.settings.useAdvancedMode = true; diff --git a/src/features/ConfigSync/PluginCombo.svelte b/src/features/ConfigSync/PluginCombo.svelte index bd07303..4df59d1 100644 --- a/src/features/ConfigSync/PluginCombo.svelte +++ b/src/features/ConfigSync/PluginCombo.svelte @@ -1,5 +1,5 @@ diff --git a/src/modules/features/GlobalHistory/GlobalHistory.svelte b/src/modules/features/GlobalHistory/GlobalHistory.svelte index 30806d9..ae4deec 100644 --- a/src/modules/features/GlobalHistory/GlobalHistory.svelte +++ b/src/modules/features/GlobalHistory/GlobalHistory.svelte @@ -6,7 +6,6 @@ import { diff_match_patch } from "../../../deps.ts"; import { DocumentHistoryModal } from "../DocumentHistory/DocumentHistoryModal.ts"; import { isPlainText, stripAllPrefixes } from "../../../lib/src/string_and_binary/path.ts"; - import { TFile } from "../../../deps.ts"; import { getPath } from "../../../common/utils.ts"; export let plugin: ObsidianLiveSyncPlugin; @@ -105,9 +104,9 @@ } if (rev == docA._rev) { if (checkStorageDiff) { - const isExist = await plugin.storageAccess.isExists(stripAllPrefixes(getPath(docA))); + const isExist = await plugin.storageAccess.isExistsIncludeHidden(stripAllPrefixes(getPath(docA))); if (isExist) { - const data = await plugin.storageAccess.readFileAuto(stripAllPrefixes(getPath(docA))); + const data = await plugin.storageAccess.readHiddenFileBinary(stripAllPrefixes(getPath(docA))); const d = readAsBlob(doc); const result = await isDocContentSame(data, d); if (result) { diff --git a/src/modules/features/ModuleInteractiveConflictResolver.ts b/src/modules/features/ModuleInteractiveConflictResolver.ts index dbfc4b1..5272d57 100644 --- a/src/modules/features/ModuleInteractiveConflictResolver.ts +++ b/src/modules/features/ModuleInteractiveConflictResolver.ts @@ -72,7 +72,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im // In here, some merge has been processed. // So we have to run replication if configured. // TODO: Make this is as a event request - if (this.settings.syncAfterMerge && !this.plugin.suspended) { + if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) { await this.core.$$waitForReplicationOnce(); } // And, check it again. @@ -96,7 +96,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im this._log("There are no conflicted documents", LOG_LEVEL_NOTICE); return false; } - const target = await this.plugin.confirm.askSelectString("File to resolve conflict", notesList); + const target = await this.core.confirm.askSelectString("File to resolve conflict", notesList); if (target) { const targetItem = notes.find(e => e.dispPath == target)!; await this.core.$$queueConflictCheck(targetItem.path); @@ -114,7 +114,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im notes.push({ path: getPath(doc), mtime: doc.mtime }); } if (notes.length > 0) { - this.plugin.confirm.askInPopup(`conflicting-detected-on-safety`, `Some files have been left conflicted! Press {HERE} to resolve them, or you can do it later by "Pick a file to resolve conflict`, (anchor) => { + this.core.confirm.askInPopup(`conflicting-detected-on-safety`, `Some files have been left conflicted! Press {HERE} to resolve them, or you can do it later by "Pick a file to resolve conflict`, (anchor) => { anchor.text = "HERE"; anchor.addEventListener("click", () => { fireAndForget(() => this.allConflictCheck()) diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index 17a601f..2cb3968 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -67,12 +67,12 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule }) return computed(() => formatted.value); } - const labelReplication = padLeftSpComputed(this.plugin.replicationResultCount, `📥`); - const labelDBCount = padLeftSpComputed(this.plugin.databaseQueueCount, `📄`); - const labelStorageCount = padLeftSpComputed(this.plugin.storageApplyingCount, `💾`); + const labelReplication = padLeftSpComputed(this.core.replicationResultCount, `📥`); + const labelDBCount = padLeftSpComputed(this.core.databaseQueueCount, `📄`); + const labelStorageCount = padLeftSpComputed(this.core.storageApplyingCount, `💾`); const labelChunkCount = padLeftSpComputed(collectingChunks, `🧩`); const labelPluginScanCount = padLeftSpComputed(pluginScanningCount, `🔌`); - const labelConflictProcessCount = padLeftSpComputed(this.plugin.conflictProcessQueueCount, `🔩`); + const labelConflictProcessCount = padLeftSpComputed(this.core.conflictProcessQueueCount, `🔩`); const hiddenFilesCount = reactive(() => hiddenFilesEventCount.value + hiddenFilesProcessingCount.value); const labelHiddenFilesCount = padLeftSpComputed(hiddenFilesCount, `⚙️`) const queueCountLabelX = reactive(() => { @@ -81,12 +81,12 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule const queueCountLabel = () => queueCountLabelX.value; const requestingStatLabel = computed(() => { - const diff = this.plugin.requestCount.value - this.plugin.responseCount.value; + const diff = this.core.requestCount.value - this.core.responseCount.value; return diff != 0 ? "📲 " : ""; }) const replicationStatLabel = computed(() => { - const e = this.plugin.replicationStat.value; + const e = this.core.replicationStat.value; const sent = e.sent; const arrived = e.arrived; const maxPullSeq = e.maxPullSeq; @@ -128,9 +128,9 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule } return { w, sent, pushLast, arrived, pullLast }; }) - const labelProc = padLeftSpComputed(this.plugin.processing, `⏳`); - const labelPend = padLeftSpComputed(this.plugin.totalQueued, `🛫`); - const labelInBatchDelay = padLeftSpComputed(this.plugin.batched, `📬`); + const labelProc = padLeftSpComputed(this.core.processing, `⏳`); + const labelPend = padLeftSpComputed(this.core.totalQueued, `🛫`); + const labelInBatchDelay = padLeftSpComputed(this.core.batched, `📬`); const waitingLabel = computed(() => { return `${labelProc()}${labelPend()}${labelInBatchDelay()}`; }) @@ -144,7 +144,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule }; }) const statusBarLabels = reactive(() => { - const scheduleMessage = this.plugin.isReloadingScheduled ? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n` : ""; + const scheduleMessage = this.core.$$isReloadingScheduled() ? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n` : ""; const { message } = statusLineLabel(); const status = scheduleMessage + this.statusLog.value; @@ -181,7 +181,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule const thisFile = this.app.workspace.getActiveFile(); if (!thisFile) return ""; // Case Sensitivity - if (this.core.shouldCheckCaseInsensitive) { + if (this.core.$$shouldCheckCaseInsensitive()) { const f = this.core.storageAccess.getFiles().map(e => e.path).filter(e => e.toLowerCase() == thisFile.path.toLowerCase()); if (f.length > 1) return "Not synchronised: There are multiple files with the same name"; } @@ -274,7 +274,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule } $everyOnloadAfterLoadSettings(): Promise { logStore.pipeTo(new QueueProcessor(logs => logs.forEach(e => this.core.$$addLog(e.message, e.level, e.key)), { suspended: false, batchSize: 20, concurrentLimit: 1, delay: 0 })).startPipeline(); - eventHub.onEvent(EVENT_FILE_RENAMED, (evt: CustomEvent<{ oldPath: string, newPath: string }>) => { + eventHub.onEvent(EVENT_FILE_RENAMED, (data) => { void this.setFileStatus(); }); @@ -290,7 +290,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule this.logHistory = this.statusDiv.createDiv({ cls: "livesync-status-loghistory" }); eventHub.onEvent(EVENT_LAYOUT_READY, () => this.adjustStatusDivPosition()); if (this.settings?.showStatusOnStatusbar) { - this.statusBar = this.plugin.addStatusBarItem(); + this.statusBar = this.core.addStatusBarItem(); this.statusBar.addClass("syncstatusbar"); } this.adjustStatusDivPosition(); @@ -318,7 +318,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL_VERBOSE) { return; } - const vaultName = this.plugin.$$getVaultName(); + const vaultName = this.core.$$getVaultName(); const now = new Date(); const timestamp = now.toLocaleString(); const messageContent = typeof message == "string" ? message : message instanceof Error ? `${message.name}:${message.message}` : JSON.stringify(message, null, 2); diff --git a/src/modules/features/ModuleObsidianDocumentHistory.ts b/src/modules/features/ModuleObsidianDocumentHistory.ts index f8c4353..52bf5b6 100644 --- a/src/modules/features/ModuleObsidianDocumentHistory.ts +++ b/src/modules/features/ModuleObsidianDocumentHistory.ts @@ -1,5 +1,6 @@ import { type TFile } from "obsidian"; -import { eventHub, EVENT_REQUEST_SHOW_HISTORY } from "../../common/events.ts"; +import { eventHub } from "../../common/events.ts"; +import { EVENT_REQUEST_SHOW_HISTORY } from "../../common/obsidianEvents.ts"; import type { FilePathWithPrefix, LoadedEntry, DocumentID } from "../../lib/src/common/types.ts"; import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts"; import { DocumentHistoryModal } from "./DocumentHistory/DocumentHistoryModal.ts"; @@ -29,7 +30,7 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implem fireAndForget(async () => await this.fileHistory()); }, }); - eventHub.on(EVENT_REQUEST_SHOW_HISTORY, ({ file, fileOnDB }: { file: TFile, fileOnDB: LoadedEntry }) => { + eventHub.onEvent(EVENT_REQUEST_SHOW_HISTORY, ({ file, fileOnDB }: { file: TFile | FilePathWithPrefix, fileOnDB: LoadedEntry }) => { this.showHistory(file, fileOnDB._id); }) return Promise.resolve(true); @@ -46,7 +47,7 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implem } notes.sort((a, b) => b.mtime - a.mtime); const notesList = notes.map(e => e.dispPath); - const target = await this.plugin.confirm.askSelectString("File to view History", notesList); + const target = await this.core.confirm.askSelectString("File to view History", notesList); if (target) { const targetId = notes.find(e => e.dispPath == target)!; this.showHistory(targetId.path, targetId.id); diff --git a/src/modules/features/ModuleObsidianSetting.ts b/src/modules/features/ModuleObsidianSetting.ts index 3c43321..584d7ca 100644 --- a/src/modules/features/ModuleObsidianSetting.ts +++ b/src/modules/features/ModuleObsidianSetting.ts @@ -12,7 +12,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO const methods: Record Promise)> = { "": () => Promise.resolve("*"), "LOCALSTORAGE": () => Promise.resolve(localStorage.getItem("ls-setting-passphrase") ?? false), - "ASK_AT_LAUNCH": () => this.plugin.confirm.askString("Passphrase", "passphrase", "") + "ASK_AT_LAUNCH": () => this.core.confirm.askString("Passphrase", "passphrase", "") } const method = settings.configPassphraseStore; const methodFunc = method in methods ? methods[method] : methods[""]; @@ -20,8 +20,8 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO } $$saveDeviceAndVaultName(): void { - const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.plugin.$$getVaultName(); - localStorage.setItem(lsKey, this.plugin.deviceAndVaultName || ""); + const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.core.$$getVaultName(); + localStorage.setItem(lsKey, this.core.$$getDeviceAndVaultName() || ""); } usedPassphrase = ""; @@ -64,7 +64,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO } async $$saveSettingData() { - this.plugin.$$saveDeviceAndVaultName(); + this.core.$$saveDeviceAndVaultName(); const settings = { ...this.settings }; settings.deviceAndVaultName = ""; if (this.usedPassphrase == "" && !await this.getPassphrase(settings)) { @@ -182,11 +182,11 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO // So, use history is always enabled. this.settings.useHistory = true; - const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.plugin.$$getVaultName(); + const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.core.$$getVaultName(); if (this.settings.deviceAndVaultName != "") { if (!localStorage.getItem(lsKey)) { - this.core.deviceAndVaultName = this.settings.deviceAndVaultName; - localStorage.setItem(lsKey, this.core.deviceAndVaultName); + this.core.$$setDeviceAndVaultName(this.settings.deviceAndVaultName); + this.$$saveDeviceAndVaultName(); this.settings.deviceAndVaultName = ""; } } @@ -194,8 +194,8 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO this._log("Configuration verification founds problems with your configuration. This has been fixed automatically. But you may already have data that cannot be synchronised. If this is the case, please rebuild everything.", LOG_LEVEL_NOTICE) this.settings.customChunkSize = 0; } - this.core.deviceAndVaultName = localStorage.getItem(lsKey) || ""; - if (this.core.deviceAndVaultName == "") { + this.core.$$setDeviceAndVaultName(localStorage.getItem(lsKey) || ""); + if (this.core.$$getDeviceAndVaultName() == "") { if (this.settings.usePluginSync) { this._log("Device name is not set. Plug-in sync has been disabled.", LOG_LEVEL_NOTICE); this.settings.usePluginSync = false; diff --git a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts index d52dc01..b5892b0 100644 --- a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts +++ b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts @@ -19,7 +19,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp return this.settings.settingSyncFile != ""; } fireAndForget(async () => { - await this.plugin.$$saveSettingData(); + await this.core.$$saveSettingData(); }); } }) @@ -38,13 +38,12 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp } }, }) - eventHub.on("event-file-changed", (info: { + eventHub.onEvent("event-file-changed", (info: { file: FilePathWithPrefix, automated: boolean }) => { fireAndForget(() => this.checkAndApplySettingFromMarkdown(info.file, info.automated)); }); - eventHub.onEvent(EVENT_SETTING_SAVED, (evt: CustomEvent) => { - const settings = evt.detail; + eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => { if (settings.settingSyncFile != "") { fireAndForget(() => this.saveSettingToMarkdown(settings.settingSyncFile)); } @@ -123,7 +122,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp return } const addMsg = this.settings.settingSyncFile != filename ? " (This is not-active file)" : ""; - this.plugin.confirm.askInPopup("apply-setting-from-md", `Setting markdown ${filename}${addMsg} has been detected. Apply this from {HERE}.`, (anchor) => { + this.core.confirm.askInPopup("apply-setting-from-md", `Setting markdown ${filename}${addMsg} has been detected. Apply this from {HERE}.`, (anchor) => { anchor.text = "HERE"; anchor.addEventListener("click", () => { fireAndForget(async () => { @@ -132,26 +131,26 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp const APPLY_AND_REBUILD = "Apply settings and restart obsidian with red_flag_rebuild.md"; const APPLY_AND_FETCH = "Apply settings and restart obsidian with red_flag_fetch.md"; const CANCEL = "Cancel"; - const result = await this.plugin.confirm.askSelectStringDialogue("Ready for apply the setting.", [ + const result = await this.core.confirm.askSelectStringDialogue("Ready for apply the setting.", [ APPLY_AND_RESTART, APPLY_ONLY, APPLY_AND_FETCH, APPLY_AND_REBUILD, CANCEL], { defaultAction: APPLY_AND_RESTART }); if (result == APPLY_ONLY || result == APPLY_AND_RESTART || result == APPLY_AND_REBUILD || result == APPLY_AND_FETCH) { - this.plugin.settings = settingToApply; - await this.plugin.$$saveSettingData(); + this.core.settings = settingToApply; + await this.core.$$saveSettingData(); if (result == APPLY_ONLY) { this._log("Loaded settings have been applied!", LOG_LEVEL_NOTICE); return; } if (result == APPLY_AND_REBUILD) { - await this.plugin.rebuilder.scheduleRebuild(); + await this.core.rebuilder.scheduleRebuild(); } if (result == APPLY_AND_FETCH) { - await this.plugin.rebuilder.scheduleFetch(); + await this.core.rebuilder.scheduleFetch(); } - this.plugin.$$performRestart(); + this.core.$$performRestart(); } }) }) diff --git a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts index 47c70bf..8132119 100644 --- a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts +++ b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts @@ -20,11 +20,12 @@ import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts"; import { fireAndForget, yieldNextAnimationFrame } from "octagonal-wheels/promises"; import { confirmWithMessage } from "../../coreObsidian/UILib/dialogs.ts"; -import { EVENT_REQUEST_COPY_SETUP_URI, EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, EVENT_REQUEST_OPEN_SETUP_URI, EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_REQUEST_SHOW_HISTORY, eventHub } from "../../../common/events.ts"; +import { EVENT_REQUEST_COPY_SETUP_URI, EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, EVENT_REQUEST_OPEN_SETUP_URI, EVENT_REQUEST_RELOAD_SETTING_TAB, eventHub } from "../../../common/events.ts"; import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; import { JournalSyncMinio } from "../../../lib/src/replication/journal/objectstore/JournalSyncMinio.ts"; import { ICHeader, ICXHeader, PSCHeader } from "../../../common/types.ts"; import { HiddenFileSync } from "../../../features/HiddenFileSync/CmdHiddenFileSync.ts"; +import { EVENT_REQUEST_SHOW_HISTORY } from "../../../common/obsidianEvents.ts"; export type OnUpdateResult = { visibility?: boolean, @@ -162,7 +163,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { return await Promise.resolve(); } if (key == "deviceAndVaultName") { - this.plugin.deviceAndVaultName = this.editingSettings?.[key] ?? ""; + this.plugin.$$setDeviceAndVaultName(this.editingSettings?.[key] ?? ""); this.plugin.$$saveDeviceAndVaultName(); return await Promise.resolve(); } @@ -230,7 +231,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { const ret = { ...OnDialogSettingsDefault }; ret.configPassphrase = localStorage.getItem("ls-setting-passphrase") || ""; ret.preset = "" - ret.deviceAndVaultName = this.plugin.deviceAndVaultName; + ret.deviceAndVaultName = this.plugin.$$getDeviceAndVaultName(); return ret; } computeAllLocalSettings(): Partial { @@ -304,7 +305,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { super(app, plugin); this.plugin = plugin; Setting.env = this; - eventHub.on(EVENT_REQUEST_RELOAD_SETTING_TAB, () => { + eventHub.onEvent(EVENT_REQUEST_RELOAD_SETTING_TAB, () => { this.requestReload(); }) } @@ -710,7 +711,7 @@ Store only the settings. **Caution: This may lead to data corruption**; database const replicator = this.plugin.$anyNewReplicator(settingForCheck); if (!(replicator instanceof LiveSyncCouchDBReplicator)) return true; - const db = await replicator.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.isMobile, true); + const db = await replicator.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.$$isMobile(), true); if (typeof db === "string") { Logger(`ERROR: Failed to check passphrase with the remote server: \n${db}.`, LOG_LEVEL_NOTICE); return false; @@ -1187,7 +1188,7 @@ However, your report is needed to stabilise this. I appreciate you for your grea void addPanel(paneEl, "CouchDB", undefined, onlyOnCouchDB).then(paneEl => { - if (this.plugin.isMobile) { + if (this.plugin.$$isMobile()) { this.createEl(paneEl, "div", { text: `Configured as using non-HTTPS. We cannot connect to the remote. Please set up the credentials and use HTTPS for the remote URI.`, }, undefined, visibleOnly(() => !this.editingSettings.couchDB_URI.startsWith("https://"))) @@ -1280,6 +1281,23 @@ However, your report is needed to stabilise this. I appreciate you for your grea }).setClass("wizardHidden"); }); + + void addPanel(paneEl, "Fetch settings").then((paneEl) => { + new Setting(paneEl) + .setName("Fetch tweaks from the remote") + .setDesc("Fetch other necessary settings from already configured remote.") + .addButton((button) => button + .setButtonText("Fetch") + .setDisabled(false) + .onClick(async () => { + const trialSetting = { ...this.initialSettings, ...this.editingSettings, }; + const newTweaks = await this.plugin.$$checkAndAskUseRemoteConfiguration(trialSetting); + if (newTweaks.result !== false) { + this.editingSettings = { ...this.editingSettings, ...newTweaks.result }; + this.requestUpdate(); + } + })); + }); new Setting(paneEl) .setClass("wizardOnly") .addButton((button) => button @@ -1313,6 +1331,16 @@ However, your report is needed to stabilise this. I appreciate you for your grea } else { this.editingSettings = { ...this.editingSettings, ...PREFERRED_SETTING_SELF_HOSTED }; } + if (await this.plugin.confirm.askYesNoDialog("Do you want to fetch the tweaks from the remote?", { defaultOption: "Yes", title: "Fetch tweaks" }) == "yes") { + const trialSetting = { ...this.initialSettings, ...this.editingSettings, }; + const newTweaks = await this.plugin.$$checkAndAskUseRemoteConfiguration(trialSetting); + if (newTweaks.result !== false) { + this.editingSettings = { ...this.editingSettings, ...newTweaks.result }; + this.requestUpdate(); + } else { + // Messages should be already shown. + } + } changeDisplay("30") })); }); @@ -1360,7 +1388,8 @@ However, your report is needed to stabilise this. I appreciate you for your grea }).addButton(button => { button.setButtonText("Apply"); button.onClick(async () => { - await this.saveSettings(["preset"]); + // await this.saveSettings(["preset"]); + await this.saveAllDirtySettings(); }) }) @@ -1416,7 +1445,7 @@ However, your report is needed to stabilise this. I appreciate you for your grea if (!this.editingSettings.isConfigured) { this.editingSettings.isConfigured = true; await this.saveAllDirtySettings(); - await this.plugin.realizeSettingSyncMode(); + await this.plugin.$$realizeSettingSyncMode(); await rebuildDB("localOnly"); // this.resetEditingSettings(); if (await this.plugin.confirm.askYesNoDialog( @@ -1430,13 +1459,13 @@ However, your report is needed to stabilise this. I appreciate you for your grea await confirmRebuild(); } else { await this.saveAllDirtySettings(); - await this.plugin.realizeSettingSyncMode(); + await this.plugin.$$realizeSettingSyncMode(); this.plugin.$$askReload(); } } } else { await this.saveAllDirtySettings(); - await this.plugin.realizeSettingSyncMode(); + await this.plugin.$$realizeSettingSyncMode(); } }) @@ -1471,7 +1500,7 @@ However, your report is needed to stabilise this. I appreciate you for your grea } await this.saveSettings(["liveSync", "periodicReplication"]); - await this.plugin.realizeSettingSyncMode(); + await this.plugin.$$realizeSettingSyncMode(); }) @@ -1546,6 +1575,8 @@ However, your report is needed to stabilise this. I appreciate you for your grea }); void addPanel(paneEl, "Sync settings via markdown", undefined, undefined, LEVEL_ADVANCED).then((paneEl) => { + paneEl.addClass("wizardHidden"); + new Setting(paneEl) .autoWireText("settingSyncFile", { holdValue: true }) .addApplyButton(["settingSyncFile"]) @@ -1569,7 +1600,6 @@ However, your report is needed to stabilise this. I appreciate you for your grea const hiddenFileSyncSettingEl = hiddenFileSyncSetting.settingEl const hiddenFileSyncSettingDiv = hiddenFileSyncSettingEl.createDiv(""); hiddenFileSyncSettingDiv.innerText = this.editingSettings.syncInternalFiles ? LABEL_ENABLED : LABEL_DISABLED; - if (this.editingSettings.syncInternalFiles) { new Setting(paneEl) .setName("Disable Hidden files sync") @@ -1979,7 +2009,7 @@ ${stringifyYaml(pluginConfig)}`; .split(",").filter(e => e).map(e => new RegExp(e, "i")); this.plugin.localDatabase.hashCaches.clear(); Logger("Start verifying all files", LOG_LEVEL_NOTICE, "verify"); - const files = await this.plugin.storageAccess.getFilesIncludeHidden("/", undefined, ignorePatterns) + const files = this.plugin.settings.syncInternalFiles ? (await this.plugin.storageAccess.getFilesIncludeHidden("/", undefined, ignorePatterns)) : (await this.plugin.storageAccess.getFileNames()); const documents = [] as FilePath[]; const adn = this.plugin.localDatabase.findAllDocs() @@ -1987,6 +2017,7 @@ ${stringifyYaml(pluginConfig)}`; const path = getPath(i); if (path.startsWith(ICXHeader)) continue; if (path.startsWith(PSCHeader)) continue; + if (!this.plugin.settings.syncInternalFiles && path.startsWith(ICHeader)) continue; documents.push(stripAllPrefixes(path)); } const allPaths = [ @@ -2648,9 +2679,9 @@ ${stringifyYaml(pluginConfig)}`; async dryRunGC() { await skipIfDuplicated("cleanup", async () => { - const replicator = this.plugin.getReplicator(); + const replicator = this.plugin.$$getReplicator(); if (!(replicator instanceof LiveSyncCouchDBReplicator)) return; - const remoteDBConn = await replicator.connectRemoteCouchDBWithSetting(this.plugin.settings, this.plugin.isMobile) + const remoteDBConn = await replicator.connectRemoteCouchDBWithSetting(this.plugin.settings, this.plugin.$$isMobile()) if (typeof (remoteDBConn) == "string") { Logger(remoteDBConn); return; @@ -2664,10 +2695,10 @@ ${stringifyYaml(pluginConfig)}`; async dbGC() { // Lock the remote completely once. await skipIfDuplicated("cleanup", async () => { - const replicator = this.plugin.getReplicator(); + const replicator = this.plugin.$$getReplicator(); if (!(replicator instanceof LiveSyncCouchDBReplicator)) return; - await this.plugin.getReplicator().markRemoteLocked(this.plugin.settings, true, true); - const remoteDBConnection = await replicator.connectRemoteCouchDBWithSetting(this.plugin.settings, this.plugin.isMobile) + await this.plugin.$$getReplicator().markRemoteLocked(this.plugin.settings, true, true); + const remoteDBConnection = await replicator.connectRemoteCouchDBWithSetting(this.plugin.settings, this.plugin.$$isMobile()) if (typeof (remoteDBConnection) == "string") { Logger(remoteDBConnection); return; diff --git a/src/modules/main/ModuleLiveSyncMain.ts b/src/modules/main/ModuleLiveSyncMain.ts new file mode 100644 index 0000000..a272c32 --- /dev/null +++ b/src/modules/main/ModuleLiveSyncMain.ts @@ -0,0 +1,182 @@ +import { fireAndForget } from "octagonal-wheels/promises"; +import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, VER, type ObsidianLiveSyncSettings } from "../../lib/src/common/types.ts"; +import { EVENT_LAYOUT_READY, EVENT_PLUGIN_LOADED, EVENT_PLUGIN_UNLOADED, EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts"; +import { $f, setLang } from "../../lib/src/common/i18n.ts"; +import { versionNumberString2Number } from "../../lib/src/string_and_binary/convert.ts"; +import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurrency/task"; +import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor"; +import { AbstractModule } from "../AbstractModule.ts"; +import type { ICoreModule } from "../ModuleTypes.ts"; + +export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule { + + async $$onLiveSyncReady() { + if (!await this.core.$everyOnLayoutReady()) return; + eventHub.emitEvent(EVENT_LAYOUT_READY); + if (this.settings.suspendFileWatching || this.settings.suspendParseReplicationResult) { + const ANSWER_KEEP = "Keep this plug-in suspended"; + const ANSWER_RESUME = "Resume and restart Obsidian"; + const message = `Self-hosted LiveSync has been configured to ignore some events. Is this intentional for you? + +| Type | Status | Note | +|:---:|:---:|---| +| Storage Events | ${this.settings.suspendFileWatching ? "suspended" : "active"} | Every modification will be ignored | +| Database Events | ${this.settings.suspendParseReplicationResult ? "suspended" : "active"} | Every synchronised change will be postponed | + +Do you want to resume them and restart Obsidian? + +> [!DETAILS]- +> These flags are set by the plug-in while rebuilding, or fetching. If the process ends abnormally, it may be kept unintended. +> If you are not sure, you can try to rerun these processes. Make sure to back your vault up. +`; + if (await this.core.confirm.askSelectStringDialogue(message, [ANSWER_KEEP, ANSWER_RESUME], { defaultAction: ANSWER_KEEP, title: "Scram Enabled" }) == ANSWER_RESUME) { + this.settings.suspendFileWatching = false; + this.settings.suspendParseReplicationResult = false; + await this.saveSettings(); + await this.core.$$scheduleAppReload(); + return; + } + } + const isInitialized = await this.core.$$initializeDatabase(false, false); + if (!isInitialized) { + //TODO:stop all sync. + return false; + } + if (!await this.core.$everyOnFirstInitialize()) return; + await this.core.$$realizeSettingSyncMode(); + fireAndForget(async () => { + this._log(`Additional safety scan..`, LOG_LEVEL_VERBOSE); + if (!await this.core.$allScanStat()) { + this._log(`Additional safety scan has been failed on some module`, LOG_LEVEL_NOTICE); + } else { + this._log(`Additional safety scan done`, LOG_LEVEL_VERBOSE); + } + }); + } + + $$wireUpEvents(): void { + eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => { + this.localDatabase.settings = settings; + setLang(settings.displayLanguage); + eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB); + }); + eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => { + fireAndForget(() => this.core.$$realizeSettingSyncMode()); + }) + } + + async $$onLiveSyncLoad(): Promise { + this.$$wireUpEvents(); + // debugger; + eventHub.emitEvent(EVENT_PLUGIN_LOADED, this.core); + this._log("loading plugin"); + if (!await this.core.$everyOnloadStart()) { + this._log("Plugin initialising has been cancelled by some module", LOG_LEVEL_NOTICE); + return; + } + // this.addUIs(); + //@ts-ignore + const manifestVersion: string = MANIFEST_VERSION || "0.0.0"; + //@ts-ignore + const packageVersion: string = PACKAGE_VERSION || "0.0.0"; + + this._log($f`Self-hosted LiveSync${" v"}${manifestVersion} ${packageVersion}`); + await this.core.$$loadSettings(); + if (!await this.core.$everyOnloadAfterLoadSettings()) { + this._log("Plugin initialising has been cancelled by some module", LOG_LEVEL_NOTICE); + return; + } + const lsKey = "obsidian-live-sync-ver" + this.core.$$getVaultName(); + const last_version = localStorage.getItem(lsKey); + + const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000); + if (lastVersion > this.settings.lastReadUpdates && this.settings.isConfigured) { + this._log($f`You have some unread release notes! Please read them once!`, LOG_LEVEL_NOTICE); + } + + //@ts-ignore + if (this.isMobile) { + this.settings.disableRequestURI = true; + } + if (last_version && Number(last_version) < VER) { + this.settings.liveSync = false; + this.settings.syncOnSave = false; + this.settings.syncOnEditorSave = false; + this.settings.syncOnStart = false; + this.settings.syncOnFileOpen = false; + this.settings.syncAfterMerge = false; + this.settings.periodicReplication = false; + this.settings.versionUpFlash = $f`Self-hosted LiveSync has been upgraded and some behaviors have changed incompatibly. All automatic synchronization is now disabled temporary. Ensure that other devices are also upgraded, and enable synchronization again.`; + await this.saveSettings(); + } + localStorage.setItem(lsKey, `${VER}`); + await this.core.$$openDatabase(); + this.core.$$realizeSettingSyncMode = this.core.$$realizeSettingSyncMode.bind(this); + // this.$$parseReplicationResult = this.$$parseReplicationResult.bind(this); + // this.$$replicate = this.$$replicate.bind(this); + this.core.$$onLiveSyncReady = this.core.$$onLiveSyncReady.bind(this); + await this.core.$everyOnload(); + await Promise.all(this.core.addOns.map(e => e.onload())); + } + + async $$onLiveSyncUnload(): Promise { + eventHub.emitEvent(EVENT_PLUGIN_UNLOADED); + await this.core.$allStartOnUnload(); + cancelAllPeriodicTask(); + cancelAllTasks(); + stopAllRunningProcessors(); + await this.core.$allOnUnload(); + this._unloaded = true; + for (const addOn of this.core.addOns) { + addOn.onunload(); + } + if (this.localDatabase != null) { + this.localDatabase.onunload(); + if (this.core.replicator) { + this.core.replicator?.closeReplication(); + } + await this.localDatabase.close(); + } + this._log($f`unloading plugin`); + } + + async $$realizeSettingSyncMode(): Promise { + await this.core.$everyBeforeSuspendProcess(); + await this.core.$everyBeforeRealizeSetting(); + this.localDatabase.refreshSettings(); + await this.core.$everyCommitPendingFileEvent(); + await this.core.$everyRealizeSettingSyncMode(); + // disable all sync temporary. + if (this.core.$$isSuspended()) return; + await this.core.$everyOnResumeProcess(); + await this.core.$everyAfterResumeProcess(); + await this.core.$everyAfterRealizeSetting(); + } + + $$isReloadingScheduled(): boolean { + return this.core._totalProcessingCount !== undefined; + } + + isReady = false; + + $$isReady(): boolean { return this.isReady; } + + $$markIsReady(): void { this.isReady = true; } + + $$resetIsReady(): void { this.isReady = false; } + + + _suspended = false; + $$isSuspended(): boolean { + return this._suspended || !this.settings?.isConfigured; + } + $$setSuspended(value: boolean) { + this._suspended = value; + } + + _unloaded = false; + $$isUnloaded(): boolean { + return this._unloaded; + } + +} \ No newline at end of file diff --git a/styles.css b/styles.css index 5911a2e..5ff1899 100644 --- a/styles.css +++ b/styles.css @@ -426,4 +426,14 @@ span.ls-mark-cr::after { background-color: rgba(var(--background-primary), 0.3); backdrop-filter: blur(4px); border-radius: 30%; +} + +.sls-dialogue-note-wrapper { + display: flex; + justify-content: flex-end; + align-items: center; +} + +.sls-dialogue-note-countdown { + font-size: 0.8em; } \ No newline at end of file