diff --git a/src/ObsidianLiveSyncSettingTab.ts b/src/ObsidianLiveSyncSettingTab.ts index d9d6b6f..87911f6 100644 --- a/src/ObsidianLiveSyncSettingTab.ts +++ b/src/ObsidianLiveSyncSettingTab.ts @@ -1,9 +1,9 @@ import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent, MarkdownRenderer } from "obsidian"; -import { EntryDoc, LOG_LEVEL, RemoteDBSettings } from "./lib/src/types"; +import { DEFAULT_SETTINGS, LOG_LEVEL, RemoteDBSettings } from "./lib/src/types"; import { path2id, id2path } from "./utils"; -import { delay, runWithLock, versionNumberString2Number } from "./lib/src/utils"; +import { delay, versionNumberString2Number } from "./lib/src/utils"; import { Logger } from "./lib/src/logger"; -import { checkSyncInfo } from "./lib/src/utils_couchdb"; +import { checkSyncInfo } from "./lib/src/utils_couchdb.js"; import { testCrypt } from "./lib/src/e2ee_v2"; import ObsidianLiveSyncPlugin from "./main"; @@ -28,6 +28,9 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { containerEl.empty(); containerEl.createEl("h2", { text: "Settings for Self-hosted LiveSync." }); + containerEl.addClass("sls-setting"); + containerEl.removeClass("isWizard"); + const w = containerEl.createDiv(""); const screenElements: { [key: string]: HTMLElement[] } = {}; @@ -39,15 +42,17 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { }; w.addClass("sls-setting-menu"); w.innerHTML = ` - - - - - - - - - + + + + + + + + + + + `; const menuTabs = w.querySelectorAll(".sls-setting-label"); const changeDisplay = (screen: string) => { @@ -58,6 +63,16 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { screenElements[k].forEach((element) => element.addClass("setting-collapsed")); } } + w.querySelectorAll(`.sls-setting-label`).forEach((element) => { + element.removeClass("selected"); + (element.querySelector("input[type=radio]") as HTMLInputElement).checked = false; + }); + console.log(`.sls-setting-label.c-${screen}`) + w.querySelectorAll(`.sls-setting-label.c-${screen}`).forEach((element) => { + console.log(element) + element.addClass("selected"); + (element.querySelector("input[type=radio]") as HTMLInputElement).checked = true; + }); }; menuTabs.forEach((element) => { const e = element.querySelector(".sls-setting-tab"); @@ -97,12 +112,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { addScreenElement("100", containerInformationEl); - const containerRemoteDatabaseEl = containerEl.createDiv(); - containerRemoteDatabaseEl.createEl("h3", { text: "Remote Database configuration" }); - const syncWarn = containerRemoteDatabaseEl.createEl("div", { text: `These settings are kept locked while any synchronization options are enabled. Disable these options in the "Sync Settings" tab to unlock.` }); - syncWarn.addClass("op-warn-info"); - syncWarn.addClass("sls-hidden"); - const isAnySyncEnabled = (): boolean => { if (this.plugin.settings.liveSync) return true; if (this.plugin.settings.periodicReplication) return true; @@ -113,6 +122,55 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { if (this.plugin.localDatabase.syncStatus == "PAUSED") return true; return false; }; + let inWizard = false; + + const setupWizardEl = containerEl.createDiv(); + setupWizardEl.createEl("h3", { text: "Setup wizard" }); + new Setting(setupWizardEl) + .setName("Discard the existing configuration and set up") + .addButton((text) => { + text.setButtonText("Next").onClick(async () => { + if (JSON.stringify(this.plugin.settings) != JSON.stringify(DEFAULT_SETTINGS)) { + this.plugin.settings = { ...DEFAULT_SETTINGS }; + this.plugin.saveSettings(); + Logger("Configuration has been flushed, please open it again", LOG_LEVEL.NOTICE) + this.hide(); + } else { + containerEl.addClass("isWizard"); + applyDisplayEnabled(); + inWizard = true; + changeDisplay("0") + } + }) + }) + new Setting(setupWizardEl) + .setName("Do not discard the existing configuration and set up again") + .addButton((text) => { + text.setButtonText("Next").onClick(async () => { + await this.plugin.resetLocalDatabase(); + this.plugin.settings.liveSync = false; + this.plugin.settings.periodicReplication = false; + this.plugin.settings.syncOnSave = false; + this.plugin.settings.syncOnStart = false; + this.plugin.settings.syncOnFileOpen = false; + await this.plugin.saveSettings(); + containerEl.addClass("isWizard"); + applyDisplayEnabled(); + inWizard = true; + changeDisplay("0") + + }) + }) + + addScreenElement("110", setupWizardEl); + + const containerRemoteDatabaseEl = containerEl.createDiv(); + containerRemoteDatabaseEl.createEl("h3", { text: "Remote Database configuration" }); + const syncWarn = containerRemoteDatabaseEl.createEl("div", { text: `These settings are kept locked while any synchronization options are enabled. Disable these options in the "Sync Settings" tab to unlock.` }); + syncWarn.addClass("op-warn-info"); + syncWarn.addClass("sls-hidden"); + + const applyDisplayEnabled = () => { if (isAnySyncEnabled()) { dbSettings.forEach((e) => { @@ -200,11 +258,18 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { .setDesc("Encrypt contents on the remote database. If you use the plugin's synchronization feature, enabling this is recommend.") .addToggle((toggle) => toggle.setValue(this.plugin.settings.workingEncrypt).onChange(async (value) => { - this.plugin.settings.workingEncrypt = value; - passphrase.setDisabled(!value); - await this.plugin.saveSettings(); + if (inWizard) { + this.plugin.settings.encrypt = value; + passphrase.setDisabled(!value); + await this.plugin.saveSettings(); + } else { + this.plugin.settings.workingEncrypt = value; + passphrase.setDisabled(!value); + await this.plugin.saveSettings(); + } }) ); + const passphrase = new Setting(containerRemoteDatabaseEl) .setName("Passphrase") .setDesc("Encrypting passphrase. If you change the passphrase of a existing database, overwriting the remote database is strongly recommended.") @@ -212,12 +277,18 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { text.setPlaceholder("") .setValue(this.plugin.settings.workingPassphrase) .onChange(async (value) => { - this.plugin.settings.workingPassphrase = value; - await this.plugin.saveSettings(); + if (inWizard) { + this.plugin.settings.passphrase = value; + await this.plugin.saveSettings(); + } else { + this.plugin.settings.workingPassphrase = value; + await this.plugin.saveSettings(); + } }); text.inputEl.setAttribute("type", "password"); }); passphrase.setDisabled(!this.plugin.settings.workingEncrypt); + const checkWorkingPassphrase = async (): Promise => { const settingForCheck: RemoteDBSettings = { ...this.plugin.settings, @@ -263,7 +334,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { this.plugin.settings.passphrase = this.plugin.settings.workingPassphrase; await this.plugin.saveSettings(); - // await this.plugin.resetLocalDatabase(); if (sendToServer) { await this.plugin.initializeDatabase(true); await this.plugin.markRemoteLocked(); @@ -278,6 +348,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { new Setting(containerRemoteDatabaseEl) .setName("Apply") .setDesc("Apply encryption settings") + .setClass("wizardHidden") .addButton((button) => button .setButtonText("Apply") @@ -285,10 +356,11 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { .setDisabled(false) .setClass("sls-btn-right") .onClick(async () => { - await applyEncryption(false); + await applyEncryption(true); }) ); + const rebuildDB = async (method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice") => { this.plugin.settings.liveSync = false; this.plugin.settings.periodicReplication = false; @@ -324,6 +396,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { new Setting(containerRemoteDatabaseEl) .setName("Overwrite remote database") .setDesc("Overwrite remote database with local DB and passphrase.") + .setClass("wizardHidden") .addButton((button) => button .setButtonText("Send") @@ -338,6 +411,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { new Setting(containerRemoteDatabaseEl) .setName("Rebuild everything") .setDesc("Rebuild local and remote database with local files.") + .setClass("wizardHidden") .addButton((button) => button .setButtonText("Rebuild") @@ -548,6 +622,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { new Setting(containerRemoteDatabaseEl) .setName("Lock remote database") .setDesc("Lock remote database to prevent synchronization with other devices.") + .setClass("wizardHidden") .addButton((button) => button .setButtonText("Lock") @@ -557,6 +632,50 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { await this.plugin.markRemoteLocked(); }) ); + + new Setting(containerRemoteDatabaseEl) + .setName("") + .setClass("wizardOnly") + .addButton((button) => + button + .setButtonText("Next") + .setClass("mod-cta") + .setDisabled(false) + .onClick(async () => { + if (!this.plugin.settings.encrypt) { + this.plugin.settings.passphrase = ""; + } + if (this.plugin.settings.couchDB_URI.contains(".cloudantnosqldb.")) { + this.plugin.settings.customChunkSize = 0; + } else { + this.plugin.settings.customChunkSize = 100; + } + + changeDisplay("10") + }) + ); + new Setting(containerRemoteDatabaseEl) + .setName("") + .setClass("wizardOnly") + .addButton((button) => + button + .setButtonText("Discard exist database and proceed") + .setDisabled(false) + .setWarning() + .onClick(async () => { + if (!this.plugin.settings.encrypt) { + this.plugin.settings.passphrase = ""; + } + if (this.plugin.settings.couchDB_URI.contains(".cloudantnosqldb.")) { + this.plugin.settings.customChunkSize = 0; + } else { + this.plugin.settings.customChunkSize = 100; + } + this.plugin.saveSettings(); + await this.plugin.tryResetRemoteDatabase(); + changeDisplay("10") + }) + ); addScreenElement("0", containerRemoteDatabaseEl); const containerLocalDatabaseEl = containerEl.createDiv(); containerLocalDatabaseEl.createEl("h3", { text: "Local Database configuration" }); @@ -576,18 +695,10 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { }) ); - new Setting(containerLocalDatabaseEl).setName("Garbage check").addButton((button) => - button - .setButtonText("Check now") - .setDisabled(false) - .onClick(async () => { - await this.plugin.garbageCheck(); - }) - ); - new Setting(containerLocalDatabaseEl) .setName("Fetch rebuilt DB") .setDesc("Restore or reconstruct local database from remote database.") + .setClass("wizardHidden") .addButton((button) => button .setButtonText("Fetch") @@ -603,7 +714,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { let newDatabaseName = this.plugin.settings.additionalSuffixOfDatabaseName + ""; new Setting(containerLocalDatabaseEl) .setName("Database suffix") - .setDesc("Set unique name for using same vault name on different directory.") + .setDesc("Optional: Set unique name for using same vault name on different directory.") .addText((text) => { text.setPlaceholder("") .setValue(newDatabaseName) @@ -624,7 +735,17 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { await this.plugin.initializeDatabase(); }) }) - + new Setting(containerLocalDatabaseEl) + .setName("") + .setClass("wizardOnly") + .addButton((button) => + button + .setButtonText("Next") + .setDisabled(false) + .onClick(async () => { + changeDisplay("40"); + }) + ); addScreenElement("10", containerLocalDatabaseEl); const containerGeneralSettingsEl = containerEl.createDiv(); @@ -650,6 +771,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { ); new Setting(containerGeneralSettingsEl) .setName("Delete metadata of deleted files.") + .setClass("wizardHidden") .addToggle((toggle) => { toggle.setValue(this.plugin.settings.deleteMetadataOfDeletedFiles).onChange(async (value) => { this.plugin.settings.deleteMetadataOfDeletedFiles = value; @@ -661,6 +783,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { addScreenElement("20", containerGeneralSettingsEl); const containerSyncSettingEl = containerEl.createDiv(); containerSyncSettingEl.createEl("h3", { text: "Sync Settings" }); + containerSyncSettingEl.addClass("wizardHidden") if (this.plugin.settings.versionUpFlash != "") { const c = containerSyncSettingEl.createEl("div", { text: this.plugin.settings.versionUpFlash }); @@ -945,7 +1068,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { } ); - new Setting(containerSyncSettingEl) .setName("Chunk size") .setDesc("Customize chunk size for binary files (0.1MBytes). This cannot be increased when using IBM Cloudant.") @@ -962,6 +1084,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { }); text.inputEl.setAttribute("type", "number"); }); + new Setting(containerSyncSettingEl) .setName("Read chunks online.") .setDesc("If this option is enabled, LiveSync reads chunks online directly instead of replicating them locally. Increasing Custom chunk size is recommended.") @@ -1027,24 +1150,16 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { await this.plugin.saveSettings(); }) ); - new Setting(containerMiscellaneousEl) - .setName("Check integrity on saving") - .setDesc("Check database integrity on saving to database") - .addToggle((toggle) => - toggle.setValue(this.plugin.settings.checkIntegrityOnSave).onChange(async (value) => { - this.plugin.settings.checkIntegrityOnSave = value; - await this.plugin.saveSettings(); - }) - ); - let currentPrest = "NONE"; + let currentPreset = "NONE"; + new Setting(containerMiscellaneousEl) .setName("Presets") .setDesc("Apply preset configuration") .addDropdown((dropdown) => dropdown .addOptions({ NONE: "", LIVESYNC: "LiveSync", PERIODIC: "Periodic w/ batch", DISABLE: "Disable all sync" }) - .setValue(currentPrest) - .onChange((value) => (currentPrest = value)) + .setValue(currentPreset) + .onChange((value) => (currentPreset = value)) ) .addButton((button) => button @@ -1052,7 +1167,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { .setDisabled(false) .setCta() .onClick(async () => { - if (currentPrest == "") { + if (currentPreset == "") { Logger("Select any preset.", LOG_LEVEL.NOTICE); return; } @@ -1062,10 +1177,10 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { this.plugin.settings.syncOnSave = false; this.plugin.settings.syncOnStart = false; this.plugin.settings.syncOnFileOpen = false; - if (currentPrest == "LIVESYNC") { + if (currentPreset == "LIVESYNC") { this.plugin.settings.liveSync = true; Logger("Synchronization setting configured as LiveSync.", LOG_LEVEL.NOTICE); - } else if (currentPrest == "PERIODIC") { + } else if (currentPreset == "PERIODIC") { this.plugin.settings.batchSave = true; this.plugin.settings.periodicReplication = true; this.plugin.settings.syncOnSave = false; @@ -1077,9 +1192,22 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { } this.plugin.saveSettings(); await this.plugin.realizeSettingSyncMode(); + if (inWizard) { + this.hide(); + await this.plugin.resetLocalDatabase(); + await this.plugin.initializeDatabase(true) + await this.plugin.replicate(true); + Logger("All done! Please set up subsequent devices with 'Copy setup URI' and 'Open setup URI'.", LOG_LEVEL.NOTICE) + } + + }) ); + const infoApply = containerMiscellaneousEl.createEl("div", { text: `To finish setup, please select one of the presets` }); + infoApply.addClass("op-warn-info"); + infoApply.addClass("wizardOnly") + addScreenElement("40", containerMiscellaneousEl); const containerHatchEl = containerEl.createDiv(); @@ -1143,36 +1271,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { Logger("done", LOG_LEVEL.NOTICE, "verify"); }) ); - new Setting(containerHatchEl) - .setName("Sanity check") - .setDesc("Verify") - .addButton((button) => - button - .setButtonText("Sanity check") - .setDisabled(false) - .setWarning() - .onClick(async () => { - // const notice = NewNotice("", 0); - Logger(`Begin sanity check`, LOG_LEVEL.NOTICE, "sancheck"); - await runWithLock("sancheck", true, async () => { - const db = this.plugin.localDatabase.localDatabase; - const wf = await db.allDocs(); - const filesDatabase = wf.rows.filter((e) => !e.id.startsWith("h:") && !e.id.startsWith("ps:") && e.id != "obsydian_livesync_version").map((e) => e.id); - let count = 0; - for (const id of filesDatabase) { - count++; - Logger(`${count}/${filesDatabase.length}\n${id2path(id)}`, LOG_LEVEL.NOTICE, "sancheck"); - const w = await db.get(id); - if (!(await this.plugin.localDatabase.sanCheck(w))) { - Logger(`The file ${id2path(id)} missing child(ren)`, LOG_LEVEL.NOTICE); - } - } - }); - Logger(`Done`, LOG_LEVEL.NOTICE, "sancheck"); - // Logger("done", LOG_LEVEL.NOTICE); - }) - ); - new Setting(containerHatchEl) .setName("Suspend file watching") @@ -1184,40 +1282,11 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { }) ); - containerHatchEl.createEl("div", { - text: sanitizeHTMLToDom(`Advanced buttons
- These buttons could break your database easily.`), - }); new Setting(containerHatchEl) - .setName("Reset remote database") - .setDesc("Reset remote database, this affects only database. If you replicate again, remote database will restored by local database.") + .setName("Discard local database to reset or uninstall Self-hosted LiveSync") .addButton((button) => button - .setButtonText("Reset") - .setDisabled(false) - .setWarning() - .onClick(async () => { - await this.plugin.tryResetRemoteDatabase(); - }) - ); - new Setting(containerHatchEl) - .setName("Reset local database") - .setDesc("Reset local database, this affects only database. If you replicate again, local database will restored by remote database.") - .addButton((button) => - button - .setButtonText("Reset") - .setDisabled(false) - .setWarning() - .onClick(async () => { - await this.plugin.resetLocalDatabase(); - }) - ); - new Setting(containerHatchEl) - .setName("Initialize local database again") - .setDesc("WARNING: Reset local database and reconstruct by storage data. It affects local database, but if you replicate remote as is, remote data will be merged or corrupted.") - .addButton((button) => - button - .setButtonText("INITIALIZE") + .setButtonText("Discard") .setWarning() .setDisabled(false) .onClick(async () => { @@ -1226,20 +1295,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { }) ); - new Setting(containerHatchEl) - .setName("Drop old encrypted database") - .setDesc("WARNING: Please use this button only when you have failed on converting old-style local database at v0.10.0.") - .addButton((button) => - button - .setButtonText("Drop") - .setWarning() - .setDisabled(false) - .onClick(async () => { - await this.plugin.resetLocalOldDatabase(); - await this.plugin.initializeDatabase(); - }) - ); - addScreenElement("50", containerHatchEl); // With great respect, thank you TfTHacker! // Refer: https://github.com/TfTHacker/obsidian42-brat/blob/main/src/features/BetaPlugins.ts @@ -1383,9 +1438,17 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { applyDisplayEnabled(); addScreenElement("70", containerCorruptedDataEl); if (lastVersion != this.plugin.settings.lastReadUpdates) { - changeDisplay("100"); + if (JSON.stringify(this.plugin.settings) != JSON.stringify(DEFAULT_SETTINGS)) { + changeDisplay("100"); + } else { + changeDisplay("110") + } } else { - changeDisplay("0"); + if (isAnySyncEnabled()) { + changeDisplay("0"); + } else { + changeDisplay("110") + } } } }