mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-03-24 18:55:17 +00:00
Compare commits
11 Commits
0.25.23.be
...
svelteui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aab0f7f034 | ||
|
|
b3a0deb0e3 | ||
|
|
b9138d1395 | ||
|
|
7eb9807aa5 | ||
|
|
91a4f234f1 | ||
|
|
82f2860938 | ||
|
|
5443317157 | ||
|
|
47fe9d2af3 | ||
|
|
8b81570035 | ||
|
|
d3e50421e4 | ||
|
|
12605f4604 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.25.23.beta1",
|
||||
"version": "0.25.24.beta3",
|
||||
"minAppVersion": "0.9.12",
|
||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"author": "vorotamoroz",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.25.23.beta1",
|
||||
"version": "0.25.24.beta3",
|
||||
"minAppVersion": "0.9.12",
|
||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"author": "vorotamoroz",
|
||||
|
||||
2568
package-lock.json
generated
2568
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.25.23.beta1",
|
||||
"version": "0.25.24.beta3",
|
||||
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
@@ -94,9 +94,9 @@
|
||||
"fflate": "^0.8.2",
|
||||
"idb": "^8.0.3",
|
||||
"minimatch": "^10.0.2",
|
||||
"octagonal-wheels": "^0.1.41",
|
||||
"octagonal-wheels": "^0.1.42",
|
||||
"qrcode-generator": "^1.4.4",
|
||||
"trystero": "github:vrtmrz/trystero#9e892a93ec14eeb57ce806d272fbb7c3935256d8",
|
||||
"trystero": "^0.22.0",
|
||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1741,7 +1741,7 @@ export class ConfigSync extends LiveSyncCommands {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private async _anyConfigureOptionalSyncFeature(mode: keyof OPTIONAL_SYNC_FEATURES) {
|
||||
private async _allConfigureOptionalSyncFeature(mode: keyof OPTIONAL_SYNC_FEATURES) {
|
||||
await this.configureHiddenFileSync(mode);
|
||||
return true;
|
||||
}
|
||||
@@ -1813,6 +1813,6 @@ export class ConfigSync extends LiveSyncCommands {
|
||||
services.databaseEvents.handleDatabaseInitialised(this._everyOnDatabaseInitialized.bind(this));
|
||||
services.setting.handleSuspendExtraSync(this._allSuspendExtraSync.bind(this));
|
||||
services.setting.handleSuggestOptionalFeatures(this._allAskUsingOptionalSyncFeature.bind(this));
|
||||
services.setting.handleEnableOptionalFeature(this._anyConfigureOptionalSyncFeature.bind(this));
|
||||
services.setting.handleEnableOptionalFeature(this._allConfigureOptionalSyncFeature.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1657,7 +1657,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
||||
}
|
||||
|
||||
// --> Configuration handling
|
||||
private async _anyConfigureOptionalSyncFeature(mode: keyof OPTIONAL_SYNC_FEATURES) {
|
||||
private async _allConfigureOptionalSyncFeature(mode: keyof OPTIONAL_SYNC_FEATURES) {
|
||||
await this.configureHiddenFileSync(mode);
|
||||
return true;
|
||||
}
|
||||
@@ -1805,6 +1805,6 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
||||
services.databaseEvents.handleDatabaseInitialised(this._everyOnDatabaseInitialized.bind(this));
|
||||
services.setting.handleSuspendExtraSync(this._allSuspendExtraSync.bind(this));
|
||||
services.setting.handleSuggestOptionalFeatures(this._allAskUsingOptionalSyncFeature.bind(this));
|
||||
services.setting.handleEnableOptionalFeature(this._anyConfigureOptionalSyncFeature.bind(this));
|
||||
services.setting.handleEnableOptionalFeature(this._allConfigureOptionalSyncFeature.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,6 +251,9 @@
|
||||
};
|
||||
cmdSync.setConfig(initialDialogStatusKey, JSON.stringify(dialogStatus));
|
||||
});
|
||||
let isObsidian = $derived.by(() => {
|
||||
return plugin.services.API.getPlatform() === "obsidian";
|
||||
});
|
||||
</script>
|
||||
|
||||
<article>
|
||||
@@ -266,95 +269,105 @@
|
||||
{/each}
|
||||
</details>
|
||||
<h2>Connection Settings</h2>
|
||||
<details bind:open={isSettingOpened}>
|
||||
<summary>{eRelay}</summary>
|
||||
<table class="settings">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th> Enable P2P Replicator </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isP2PEnabledModified }}>
|
||||
<input type="checkbox" bind:checked={eP2PEnabled} />
|
||||
</label>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<th> Relay settings </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isRelayModified }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="wss://exp-relay.vrtmrz.net, wss://xxxxx"
|
||||
bind:value={eRelay}
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button onclick={() => useDefaultRelay()}> Use vrtmrz's relay </button>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> Room ID </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isRoomIdModified }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="anything-you-like"
|
||||
bind:value={eRoomId}
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
autocorrect="off"
|
||||
/>
|
||||
<button onclick={() => chooseRandom()}> Use Random Number </button>
|
||||
</label>
|
||||
<span>
|
||||
<small>
|
||||
This can isolate your connections between devices. Use the same Room ID for the same
|
||||
devices.</small
|
||||
>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> Password </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isPasswordModified }}>
|
||||
<input type="password" placeholder="password" bind:value={ePassword} />
|
||||
</label>
|
||||
<span>
|
||||
<small> This password is used to encrypt the connection. Use something long enough. </small>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> This device name </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isDeviceNameModified }}>
|
||||
<input type="text" placeholder="iphone-16" bind:value={eDeviceName} autocomplete="off" />
|
||||
</label>
|
||||
<span>
|
||||
<small>
|
||||
Device name to identify the device. Please use shorter one for the stable peer
|
||||
detection, i.e., "iphone-16" or "macbook-2021".
|
||||
</small>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> Auto Connect </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isAutoStartModified }}>
|
||||
<input type="checkbox" bind:checked={eAutoStart} />
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> Start change-broadcasting on Connect </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isAutoBroadcastModified }}>
|
||||
<input type="checkbox" bind:checked={eAutoBroadcast} />
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- <tr>
|
||||
{#if isObsidian}
|
||||
You can configure in the Obsidian Plugin Settings.
|
||||
{:else}
|
||||
<details bind:open={isSettingOpened}>
|
||||
<summary>{eRelay}</summary>
|
||||
<table class="settings">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th> Enable P2P Replicator </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isP2PEnabledModified }}>
|
||||
<input type="checkbox" bind:checked={eP2PEnabled} />
|
||||
</label>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<th> Relay settings </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isRelayModified }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="wss://exp-relay.vrtmrz.net, wss://xxxxx"
|
||||
bind:value={eRelay}
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button onclick={() => useDefaultRelay()}> Use vrtmrz's relay </button>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> Room ID </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isRoomIdModified }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="anything-you-like"
|
||||
bind:value={eRoomId}
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
autocorrect="off"
|
||||
/>
|
||||
<button onclick={() => chooseRandom()}> Use Random Number </button>
|
||||
</label>
|
||||
<span>
|
||||
<small>
|
||||
This can isolate your connections between devices. Use the same Room ID for the same
|
||||
devices.</small
|
||||
>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> Password </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isPasswordModified }}>
|
||||
<input type="password" placeholder="password" bind:value={ePassword} />
|
||||
</label>
|
||||
<span>
|
||||
<small>
|
||||
This password is used to encrypt the connection. Use something long enough.
|
||||
</small>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> This device name </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isDeviceNameModified }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="iphone-16"
|
||||
bind:value={eDeviceName}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</label>
|
||||
<span>
|
||||
<small>
|
||||
Device name to identify the device. Please use shorter one for the stable peer
|
||||
detection, i.e., "iphone-16" or "macbook-2021".
|
||||
</small>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> Auto Connect </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isAutoStartModified }}>
|
||||
<input type="checkbox" bind:checked={eAutoStart} />
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> Start change-broadcasting on Connect </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isAutoBroadcastModified }}>
|
||||
<input type="checkbox" bind:checked={eAutoBroadcast} />
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- <tr>
|
||||
<th> Auto Accepting </th>
|
||||
<td>
|
||||
<label class={{ "is-dirty": isAutoAcceptModified }}>
|
||||
@@ -362,11 +375,12 @@
|
||||
</label>
|
||||
</td>
|
||||
</tr> -->
|
||||
</tbody>
|
||||
</table>
|
||||
<button disabled={!isAnyModified} class="button mod-cta" onclick={saveAndApply}>Save and Apply</button>
|
||||
<button disabled={!isAnyModified} class="button" onclick={revert}>Revert changes</button>
|
||||
</details>
|
||||
</tbody>
|
||||
</table>
|
||||
<button disabled={!isAnyModified} class="button mod-cta" onclick={saveAndApply}>Save and Apply</button>
|
||||
<button disabled={!isAnyModified} class="button" onclick={revert}>Revert changes</button>
|
||||
</details>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
<h2>Signaling Server Connection</h2>
|
||||
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: 82c68435a1...5e352d3093
@@ -33,7 +33,8 @@ import { ModuleLog } from "./modules/features/ModuleLog.ts";
|
||||
import { ModuleObsidianSettings } from "./modules/features/ModuleObsidianSetting.ts";
|
||||
import { ModuleRedFlag } from "./modules/coreFeatures/ModuleRedFlag.ts";
|
||||
import { ModuleObsidianMenu } from "./modules/essentialObsidian/ModuleObsidianMenu.ts";
|
||||
import { ModuleSetupObsidian, SetupManager } from "./modules/features/ModuleSetupObsidian.ts";
|
||||
import { ModuleSetupObsidian } from "./modules/features/ModuleSetupObsidian.ts";
|
||||
import { SetupManager } from "./modules/features/SetupManager.ts";
|
||||
import type { StorageAccess } from "./modules/interfaces/StorageAccess.ts";
|
||||
import type { Confirm } from "./lib/src/interfaces/Confirm.ts";
|
||||
import type { Rebuilder } from "./modules/interfaces/DatabaseRebuilder.ts";
|
||||
|
||||
@@ -353,6 +353,10 @@ export class ModuleFileHandler extends AbstractModule {
|
||||
this._log(`File ${entry.path} is not the target file`, LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
}
|
||||
if (this.services.vault.isFileSizeTooLarge(entry.size)) {
|
||||
this._log(`File ${entry.path} is too large (on database) to be processed`, LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
}
|
||||
if (shouldBeIgnored(entry.path)) {
|
||||
this._log(`File ${entry.path} should be ignored`, LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
@@ -365,6 +369,10 @@ export class ModuleFileHandler extends AbstractModule {
|
||||
// Nothing to do and other modules should also nothing to do.
|
||||
return true;
|
||||
} else {
|
||||
if (targetFile && this.services.vault.isFileSizeTooLarge(targetFile.stat.size)) {
|
||||
this._log(`File ${targetFile.path} is too large (on storage) to be processed`, LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
}
|
||||
this._log(
|
||||
`Processing ${path} (${entry._id.substring(0, 8)} :${entry._rev?.substring(0, 5)}) : Started...`,
|
||||
LOG_LEVEL_VERBOSE
|
||||
|
||||
@@ -15,7 +15,7 @@ import { isMetaEntry } from "../../lib/src/common/types.ts";
|
||||
import { isDeletedEntry, isDocContentSame, isLoadedEntry, readAsBlob } from "../../lib/src/common/utils.ts";
|
||||
import { countCompromisedChunks } from "../../lib/src/pouchdb/negotiation.ts";
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
import { SetupManager } from "../features/ModuleSetupObsidian.ts";
|
||||
import { SetupManager } from "../features/SetupManager.ts";
|
||||
|
||||
type ErrorInfo = {
|
||||
path: string;
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
DEFAULT_SETTINGS,
|
||||
type ObsidianLiveSyncSettings,
|
||||
SALT_OF_PASSPHRASE,
|
||||
SETTING_KEY_P2P_DEVICE_NAME,
|
||||
} from "../../lib/src/common/types";
|
||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger";
|
||||
import { $msg, setLang } from "../../lib/src/common/i18n.ts";
|
||||
@@ -111,6 +112,11 @@ export class ModuleObsidianSettings extends AbstractObsidianModule {
|
||||
this.services.setting.saveDeviceAndVaultName();
|
||||
const settings = { ...this.settings };
|
||||
settings.deviceAndVaultName = "";
|
||||
if (settings.P2P_DevicePeerName && settings.P2P_DevicePeerName.trim() !== "") {
|
||||
console.log("Saving device peer name to small config");
|
||||
this.services.config.setSmallConfig(SETTING_KEY_P2P_DEVICE_NAME, settings.P2P_DevicePeerName.trim());
|
||||
settings.P2P_DevicePeerName = "";
|
||||
}
|
||||
if (this.usedPassphrase == "" && !(await this.getPassphrase(settings))) {
|
||||
this._log("Failed to retrieve passphrase. data.json contains unencrypted items!", LOG_LEVEL_NOTICE);
|
||||
} else {
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
import {
|
||||
type ObsidianLiveSyncSettings,
|
||||
DEFAULT_SETTINGS,
|
||||
LOG_LEVEL_NOTICE,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
REMOTE_COUCHDB,
|
||||
REMOTE_MINIO,
|
||||
REMOTE_P2P,
|
||||
} from "../../lib/src/common/types.ts";
|
||||
import { SETTING_KEY_P2P_DEVICE_NAME } from "../../lib/src/common/types.ts";
|
||||
import { type ObsidianLiveSyncSettings, LOG_LEVEL_NOTICE } from "../../lib/src/common/types.ts";
|
||||
import { configURIBase } from "../../common/types.ts";
|
||||
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.js";
|
||||
import { fireAndForget, generatePatchObj, isObjectDifferent } from "../../lib/src/common/utils.ts";
|
||||
import { fireAndForget } from "../../lib/src/common/utils.ts";
|
||||
import {
|
||||
EVENT_REQUEST_COPY_SETUP_URI,
|
||||
EVENT_REQUEST_OPEN_P2P_SETTINGS,
|
||||
EVENT_REQUEST_OPEN_SETUP_URI,
|
||||
EVENT_REQUEST_SHOW_SETUP_QR,
|
||||
eventHub,
|
||||
@@ -21,268 +13,13 @@ import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||
import { $msg } from "../../lib/src/common/i18n.ts";
|
||||
// import { performDoctorConsultation, RebuildOptions } from "@/lib/src/common/configForDoc.ts";
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
import { SvelteDialogManager } from "./SetupWizard/ObsidianSvelteDialog.ts";
|
||||
import Intro from "./SetupWizard/dialogs/Intro.svelte";
|
||||
import SelectMethodNewUser from "./SetupWizard/dialogs/SelectMethodNewUser.svelte";
|
||||
import SelectMethodExisting from "./SetupWizard/dialogs/SelectMethodExisting.svelte";
|
||||
import ScanQRCode from "./SetupWizard/dialogs/ScanQRCode.svelte";
|
||||
import UseSetupURI from "./SetupWizard/dialogs/UseSetupURI.svelte";
|
||||
import OutroNewUser from "./SetupWizard/dialogs/OutroNewUser.svelte";
|
||||
import OutroExistingUser from "./SetupWizard/dialogs/OutroExistingUser.svelte";
|
||||
import OutroAskUserMode from "./SetupWizard/dialogs/OutroAskUserMode.svelte";
|
||||
import SetupRemote from "./SetupWizard/dialogs/SetupRemote.svelte";
|
||||
import SetupRemoteCouchDB from "./SetupWizard/dialogs/SetupRemoteCouchDB.svelte";
|
||||
import SetupRemoteBucket from "./SetupWizard/dialogs/SetupRemoteBucket.svelte";
|
||||
import SetupRemoteP2P from "./SetupWizard/dialogs/SetupRemoteP2P.svelte";
|
||||
import SetupRemoteE2EE from "./SetupWizard/dialogs/SetupRemoteE2EE.svelte";
|
||||
import {
|
||||
decodeSettingsFromQRCodeData,
|
||||
encodeQR,
|
||||
encodeSettingsToQRCodeData,
|
||||
encodeSettingsToSetupURI,
|
||||
OutputFormat,
|
||||
} from "../../lib/src/API/processSetting.ts";
|
||||
// import type ObsidianLiveSyncPlugin from "../../main.ts";
|
||||
export const enum UserMode {
|
||||
NewUser = "new-user",
|
||||
ExistingUser = "existing-user",
|
||||
Unknown = "unknown",
|
||||
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
|
||||
Update = "unknown", // Alias for Unknown for better readability
|
||||
}
|
||||
|
||||
export class SetupManager extends AbstractObsidianModule {
|
||||
private dialogManager: SvelteDialogManager = new SvelteDialogManager(this.plugin);
|
||||
|
||||
async startOnBoarding(): Promise<boolean> {
|
||||
const isUserNewOrExisting = await this.dialogManager.openWithExplicitCancel(Intro);
|
||||
if (isUserNewOrExisting === "new-user") {
|
||||
await this.onBoard(UserMode.NewUser);
|
||||
} else if (isUserNewOrExisting === "existing-user") {
|
||||
await this.onBoard(UserMode.ExistingUser);
|
||||
} else if (isUserNewOrExisting === "cancelled") {
|
||||
this._log("Onboarding cancelled by user.", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async onBoard(userMode: UserMode): Promise<boolean> {
|
||||
const originalSetting = userMode === UserMode.NewUser ? DEFAULT_SETTINGS : this.core.settings;
|
||||
if (userMode === UserMode.NewUser) {
|
||||
//Ask how to apply initial setup
|
||||
const method = await this.dialogManager.openWithExplicitCancel(SelectMethodNewUser);
|
||||
if (method === "use-setup-uri") {
|
||||
await this.onUseSetupURI(userMode);
|
||||
} else if (method === "configure-manually") {
|
||||
await this.onConfigureManually(originalSetting, userMode);
|
||||
} else if (method === "cancelled") {
|
||||
this._log("Onboarding cancelled by user.", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
} else if (userMode === UserMode.ExistingUser) {
|
||||
const method = await this.dialogManager.openWithExplicitCancel(SelectMethodExisting);
|
||||
if (method === "use-setup-uri") {
|
||||
await this.onUseSetupURI(userMode);
|
||||
} else if (method === "configure-manually") {
|
||||
await this.onConfigureManually(originalSetting, userMode);
|
||||
} else if (method === "scan-qr-code") {
|
||||
await this.onPromptQRCodeInstruction();
|
||||
} else if (method === "cancelled") {
|
||||
this._log("Onboarding cancelled by user.", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async onUseSetupURI(userMode: UserMode, setupURI: string = ""): Promise<boolean> {
|
||||
const newSetting = await this.dialogManager.openWithExplicitCancel(UseSetupURI, setupURI);
|
||||
if (newSetting === "cancelled") {
|
||||
this._log("Setup URI dialog cancelled.", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
this._log("Setup URI dialog closed.", LOG_LEVEL_VERBOSE);
|
||||
return await this.confirmApplySettingsFromWizard(newSetting, userMode);
|
||||
}
|
||||
async onCouchDBManualSetup(
|
||||
userMode: UserMode,
|
||||
currentSetting: ObsidianLiveSyncSettings,
|
||||
activate = true
|
||||
): Promise<boolean> {
|
||||
const originalSetting = JSON.parse(JSON.stringify(currentSetting)) as ObsidianLiveSyncSettings;
|
||||
const baseSetting = JSON.parse(JSON.stringify(originalSetting)) as ObsidianLiveSyncSettings;
|
||||
const couchConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteCouchDB, originalSetting);
|
||||
if (couchConf === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await this.onBoard(userMode);
|
||||
}
|
||||
const newSetting = { ...baseSetting, ...couchConf } as ObsidianLiveSyncSettings;
|
||||
if (activate) {
|
||||
newSetting.remoteType = REMOTE_COUCHDB;
|
||||
}
|
||||
return await this.confirmApplySettingsFromWizard(newSetting, userMode, activate);
|
||||
}
|
||||
|
||||
async onBucketManualSetup(
|
||||
userMode: UserMode,
|
||||
currentSetting: ObsidianLiveSyncSettings,
|
||||
activate = true
|
||||
): Promise<boolean> {
|
||||
const bucketConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteBucket, currentSetting);
|
||||
if (bucketConf === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await this.onBoard(userMode);
|
||||
}
|
||||
const newSetting = { ...currentSetting, ...bucketConf } as ObsidianLiveSyncSettings;
|
||||
if (activate) {
|
||||
newSetting.remoteType = REMOTE_MINIO;
|
||||
}
|
||||
return await this.confirmApplySettingsFromWizard(newSetting, userMode, activate);
|
||||
}
|
||||
async onP2PManualSetup(
|
||||
userMode: UserMode,
|
||||
currentSetting: ObsidianLiveSyncSettings,
|
||||
activate = true
|
||||
): Promise<boolean> {
|
||||
const p2pConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteP2P, currentSetting);
|
||||
if (p2pConf === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await this.onBoard(userMode);
|
||||
}
|
||||
const newSetting = { ...currentSetting, ...p2pConf.info } as ObsidianLiveSyncSettings;
|
||||
if (activate) {
|
||||
newSetting.remoteType = REMOTE_P2P;
|
||||
}
|
||||
return await this.confirmApplySettingsFromWizard(newSetting, userMode, activate, () => {
|
||||
this.services.config.setSmallConfig(SETTING_KEY_P2P_DEVICE_NAME, p2pConf.devicePeerId);
|
||||
});
|
||||
}
|
||||
async onlyE2EEConfiguration(userMode: UserMode, currentSetting: ObsidianLiveSyncSettings): Promise<boolean> {
|
||||
const e2eeConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteE2EE, currentSetting);
|
||||
if (e2eeConf === "cancelled") {
|
||||
this._log("E2EE configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await false;
|
||||
}
|
||||
const newSetting = {
|
||||
...currentSetting,
|
||||
...e2eeConf,
|
||||
} as ObsidianLiveSyncSettings;
|
||||
return await this.confirmApplySettingsFromWizard(newSetting, userMode);
|
||||
}
|
||||
async onConfigureManually(originalSetting: ObsidianLiveSyncSettings, userMode: UserMode): Promise<boolean> {
|
||||
const e2eeConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteE2EE, originalSetting);
|
||||
if (e2eeConf === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await this.onBoard(userMode);
|
||||
}
|
||||
const currentSetting = {
|
||||
...originalSetting,
|
||||
...e2eeConf,
|
||||
} as ObsidianLiveSyncSettings;
|
||||
return await this.selectServer(currentSetting, userMode);
|
||||
}
|
||||
|
||||
async selectServer(currentSetting: ObsidianLiveSyncSettings, userMode: UserMode): Promise<boolean> {
|
||||
const method = await this.dialogManager.openWithExplicitCancel(SetupRemote);
|
||||
if (method === "couchdb") {
|
||||
return await this.onCouchDBManualSetup(userMode, currentSetting, true);
|
||||
} else if (method === "bucket") {
|
||||
return await this.onBucketManualSetup(userMode, currentSetting, true);
|
||||
} else if (method === "p2p") {
|
||||
return await this.onP2PManualSetup(userMode, currentSetting, true);
|
||||
} else if (method === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
if (userMode !== UserMode.Unknown) {
|
||||
return await this.onBoard(userMode);
|
||||
}
|
||||
}
|
||||
// Should not reach here.
|
||||
return false;
|
||||
}
|
||||
async confirmApplySettingsFromWizard(
|
||||
newConf: ObsidianLiveSyncSettings,
|
||||
_userMode: UserMode,
|
||||
activate: boolean = true,
|
||||
extra: () => void = () => {}
|
||||
): Promise<boolean> {
|
||||
let userMode = _userMode;
|
||||
// let rebuildRequired = true;
|
||||
if (userMode === UserMode.Unknown) {
|
||||
if (isObjectDifferent(this.settings, newConf, true) === false) {
|
||||
this._log("No changes in settings detected. Skipping applying settings from wizard.", LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
}
|
||||
const patch = generatePatchObj(this.settings, newConf);
|
||||
console.log(`Changes:`);
|
||||
console.dir(patch);
|
||||
if (!activate) {
|
||||
extra();
|
||||
await this.applySetting(newConf, UserMode.ExistingUser);
|
||||
this._log("Setting Applied", LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
}
|
||||
const userModeResult = await this.dialogManager.openWithExplicitCancel(OutroAskUserMode);
|
||||
if (userModeResult === "new-user") {
|
||||
userMode = UserMode.NewUser;
|
||||
} else if (userModeResult === "existing-user") {
|
||||
userMode = UserMode.ExistingUser;
|
||||
} else if (userModeResult === "compatible-existing-user") {
|
||||
extra();
|
||||
await this.applySetting(newConf, UserMode.ExistingUser);
|
||||
this._log("Settings from wizard applied.", LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
} else if (userModeResult === "cancelled") {
|
||||
this._log("User cancelled applying settings from wizard.", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const component = userMode === UserMode.NewUser ? OutroNewUser : OutroExistingUser;
|
||||
const confirm = await this.dialogManager.openWithExplicitCancel(component);
|
||||
if (confirm === "cancelled") {
|
||||
this._log("User cancelled applying settings from wizard..", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
if (confirm) {
|
||||
extra();
|
||||
await this.applySetting(newConf, userMode);
|
||||
if (userMode === UserMode.NewUser) {
|
||||
// For new users, schedule a rebuild everything.
|
||||
await this.core.rebuilder.scheduleRebuild();
|
||||
} else {
|
||||
// For existing users, schedule a fetch.
|
||||
await this.core.rebuilder.scheduleFetch();
|
||||
}
|
||||
}
|
||||
// Settings applied, but may require rebuild to take effect.
|
||||
return false;
|
||||
}
|
||||
|
||||
async onPromptQRCodeInstruction(): Promise<boolean> {
|
||||
const qrResult = await this.dialogManager.open(ScanQRCode);
|
||||
this._log("QR Code dialog closed.", LOG_LEVEL_VERBOSE);
|
||||
// Result is not used, but log it for debugging.
|
||||
this._log(`QR Code result: ${qrResult}`, LOG_LEVEL_VERBOSE);
|
||||
// QR Code instruction dialog never yields settings directly.
|
||||
return false;
|
||||
}
|
||||
|
||||
async decodeQR(qr: string) {
|
||||
const newSettings = decodeSettingsFromQRCodeData(qr);
|
||||
return await this.confirmApplySettingsFromWizard(newSettings, UserMode.Unknown);
|
||||
}
|
||||
|
||||
async applySetting(newConf: ObsidianLiveSyncSettings, userMode: UserMode) {
|
||||
const newSetting = {
|
||||
...this.core.settings,
|
||||
...newConf,
|
||||
};
|
||||
this.core.settings = newSetting;
|
||||
this.services.setting.clearUsedPassphrase();
|
||||
await this.services.setting.saveSettingData();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
import { SetupManager, UserMode } from "./SetupManager.ts";
|
||||
|
||||
export class ModuleSetupObsidian extends AbstractObsidianModule {
|
||||
private _setupManager!: SetupManager;
|
||||
@@ -330,6 +67,11 @@ export class ModuleSetupObsidian extends AbstractObsidianModule {
|
||||
eventHub.onEvent(EVENT_REQUEST_OPEN_SETUP_URI, () => fireAndForget(() => this.command_openSetupURI()));
|
||||
eventHub.onEvent(EVENT_REQUEST_COPY_SETUP_URI, () => fireAndForget(() => this.command_copySetupURI()));
|
||||
eventHub.onEvent(EVENT_REQUEST_SHOW_SETUP_QR, () => fireAndForget(() => this.encodeQR()));
|
||||
eventHub.onEvent(EVENT_REQUEST_OPEN_P2P_SETTINGS, () =>
|
||||
fireAndForget(() => {
|
||||
return this._setupManager.onP2PManualSetup(UserMode.Update, this.settings, false);
|
||||
})
|
||||
);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
async encodeQR() {
|
||||
@@ -380,6 +122,8 @@ export class ModuleSetupObsidian extends AbstractObsidianModule {
|
||||
await this._setupManager.onUseSetupURI(UserMode.Unknown);
|
||||
}
|
||||
|
||||
// TODO: Where to implement these?
|
||||
|
||||
// async askSyncWithRemoteConfig(tryingSettings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
||||
// const buttons = {
|
||||
// fetch: $msg("Setup.FetchRemoteConf.Buttons.Fetch"),
|
||||
@@ -447,125 +191,6 @@ export class ModuleSetupObsidian extends AbstractObsidianModule {
|
||||
// }
|
||||
// }
|
||||
|
||||
// async applySettingWizard(
|
||||
// oldConf: ObsidianLiveSyncSettings,
|
||||
// newConf: ObsidianLiveSyncSettings,
|
||||
// method = "Setup URI"
|
||||
// ) {
|
||||
// const result = await this.core.confirm.askYesNoDialog(
|
||||
// "Importing Configuration from the " + method + ". Are you sure to proceed ? ",
|
||||
// {}
|
||||
// );
|
||||
// if (result == "yes") {
|
||||
// let newSettingW = Object.assign({}, DEFAULT_SETTINGS, newConf) as ObsidianLiveSyncSettings;
|
||||
// this.core.replicator.closeReplication();
|
||||
// this.settings.suspendFileWatching = true;
|
||||
// newSettingW = await this.askSyncWithRemoteConfig(newSettingW);
|
||||
// const { settings, shouldRebuild, isModified } = await this.askPerformDoctor(newSettingW);
|
||||
// if (isModified) {
|
||||
// newSettingW = settings;
|
||||
// }
|
||||
// // Back into the default method once.
|
||||
// newSettingW.configPassphraseStore = "";
|
||||
// newSettingW.encryptedPassphrase = "";
|
||||
// newSettingW.encryptedCouchDBConnection = "";
|
||||
// newSettingW.additionalSuffixOfDatabaseName = `${"appId" in this.app ? this.app.appId : ""} `;
|
||||
// const setupJustImport = $msg("Setup.Apply.Buttons.OnlyApply");
|
||||
// const setupAsNew = $msg("Setup.Apply.Buttons.ApplyAndFetch");
|
||||
// const setupAsMerge = $msg("Setup.Apply.Buttons.ApplyAndMerge");
|
||||
// const setupAgain = $msg("Setup.Apply.Buttons.ApplyAndRebuild");
|
||||
// const setupCancel = $msg("Setup.Apply.Buttons.Cancel");
|
||||
// newSettingW.syncInternalFiles = false;
|
||||
// newSettingW.usePluginSync = false;
|
||||
// newSettingW.isConfigured = true;
|
||||
// // Migrate completely obsoleted configuration.
|
||||
// if (!newSettingW.useIndexedDBAdapter) {
|
||||
// newSettingW.useIndexedDBAdapter = true;
|
||||
// }
|
||||
// const warn = shouldRebuild ? $msg("Setup.Apply.WarningRebuildRecommended") : "";
|
||||
// const message = $msg("Setup.Apply.Message", {
|
||||
// method,
|
||||
// warn,
|
||||
// });
|
||||
|
||||
// const setupType = await this.core.confirm.askSelectStringDialogue(
|
||||
// message,
|
||||
// [setupAsNew, setupAsMerge, setupAgain, setupJustImport, setupCancel],
|
||||
// { defaultAction: setupAsNew, title: $msg("Setup.Apply.Title", { method }), timeout: 0 }
|
||||
// );
|
||||
// if (setupType == setupJustImport) {
|
||||
// this.core.settings = newSettingW;
|
||||
// this.services.setting.clearUsedPassphrase();
|
||||
// await this.core.saveSettings();
|
||||
// } else if (setupType == setupAsNew) {
|
||||
// this.core.settings = newSettingW;
|
||||
// this.services.setting.clearUsedPassphrase();
|
||||
// await this.core.saveSettings();
|
||||
// await this.core.rebuilder.$fetchLocal();
|
||||
// } else if (setupType == setupAsMerge) {
|
||||
// this.core.settings = newSettingW;
|
||||
// this.services.setting.clearUsedPassphrase();
|
||||
// await this.core.saveSettings();
|
||||
// await this.core.rebuilder.$fetchLocal(true);
|
||||
// } else if (setupType == setupAgain) {
|
||||
// const confirm =
|
||||
// "This operation will rebuild all databases with files on this device. Any files on the remote database not synced here will be lost.";
|
||||
// if (
|
||||
// (await this.core.confirm.askSelectStringDialogue(
|
||||
// "Are you sure you want to do this?",
|
||||
// ["Cancel", confirm],
|
||||
// { defaultAction: "Cancel" }
|
||||
// )) != confirm
|
||||
// ) {
|
||||
// return;
|
||||
// }
|
||||
// this.core.settings = newSettingW;
|
||||
// await this.core.saveSettings();
|
||||
// this.services.setting.clearUsedPassphrase();
|
||||
// await this.core.rebuilder.$rebuildEverything();
|
||||
// } else {
|
||||
// // Explicitly cancel the operation or the dialog was closed.
|
||||
// this._log("Cancelled", LOG_LEVEL_NOTICE);
|
||||
// this.core.settings = oldConf;
|
||||
// return;
|
||||
// }
|
||||
// this._log("Configuration loaded.", LOG_LEVEL_NOTICE);
|
||||
// } else {
|
||||
// this._log("Cancelled", LOG_LEVEL_NOTICE);
|
||||
// this.core.settings = oldConf;
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// async setupWizard(confString: string) {
|
||||
// try {
|
||||
// const oldConf = JSON.parse(JSON.stringify(this.settings));
|
||||
// const encryptingPassphrase = await this.core.confirm.askString(
|
||||
// "Passphrase",
|
||||
// "The passphrase to decrypt your setup URI",
|
||||
// "",
|
||||
// true
|
||||
// );
|
||||
// if (encryptingPassphrase === false) return;
|
||||
// const newConf = await JSON.parse(await decryptString(confString, encryptingPassphrase));
|
||||
// if (newConf) {
|
||||
// await this.applySettingWizard(oldConf, newConf);
|
||||
// this._log("Configuration loaded.", LOG_LEVEL_NOTICE);
|
||||
// } else {
|
||||
// this._log("Cancelled.", LOG_LEVEL_NOTICE);
|
||||
// }
|
||||
// } catch (ex) {
|
||||
// this._log("Couldn't parse or decrypt configuration uri.", LOG_LEVEL_NOTICE);
|
||||
// this._log(ex, LOG_LEVEL_VERBOSE);
|
||||
// }
|
||||
// }
|
||||
|
||||
// async askHowToApplySetupURI() {
|
||||
// const method = await this.dialogManager.openWithExplicitCancel(OutroAskUserMode);
|
||||
// if( method === "new-user") {
|
||||
// return UserMode.NewUser;
|
||||
// }
|
||||
// }
|
||||
|
||||
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||
}
|
||||
|
||||
@@ -4,77 +4,12 @@
|
||||
* Mostly used in the Setting Dialogue
|
||||
*/
|
||||
import { type SveltePanelProps } from "./SveltePanel";
|
||||
import InfoTable from "@lib/UI/components/InfoTable.svelte";
|
||||
type Props = SveltePanelProps<{
|
||||
info: Record<string, any>;
|
||||
}>;
|
||||
const { port }: Props = $props();
|
||||
const info = $derived.by(() => $port?.info ?? {});
|
||||
const infoEntries = $derived(Object.entries(info ?? {}));
|
||||
</script>
|
||||
|
||||
<div class="info-panel">
|
||||
<div class="info-grid" role="list">
|
||||
{#each infoEntries as [key, value]}
|
||||
<div class="info-entry info-key" role="listitem" aria-label={key}>
|
||||
<div class="key">{key}</div>
|
||||
</div>
|
||||
<div class="info-entry info-item" role="listitem" aria-label={key}>
|
||||
<div class="value">{value}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.info-panel {
|
||||
padding: 0.6rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Main Grid (Info Items) 220px to 1fr, repeat */
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 0.6rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.info-entry {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.5rem;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
min-height: 1.2em;
|
||||
}
|
||||
|
||||
.info-key {
|
||||
font-weight: 600;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--background-modifier-hover);
|
||||
border-bottom: 1px solid var(--background-modifier-hover);
|
||||
/* color: var(--text-muted, #6b6b6b); */
|
||||
}
|
||||
.info-item {
|
||||
align-items: start;
|
||||
padding: 0.5rem;
|
||||
background: var(--background-modifier-hover, rgba(0, 0, 0, 0.03));
|
||||
}
|
||||
|
||||
.value {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
color: var(--text-normal, #e6e6e6);
|
||||
min-height: 1em;
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.info-item {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
/* .label {
|
||||
order: -1;
|
||||
white-space: normal;
|
||||
padding-bottom: 0.25rem;
|
||||
} */
|
||||
}
|
||||
</style>
|
||||
<InfoTable {info} />
|
||||
|
||||
@@ -143,6 +143,9 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement,
|
||||
pluginConfig.jwtKid = redact(pluginConfig.jwtKid);
|
||||
pluginConfig.bucketCustomHeaders = redact(pluginConfig.bucketCustomHeaders);
|
||||
pluginConfig.couchDB_CustomHeaders = redact(pluginConfig.couchDB_CustomHeaders);
|
||||
pluginConfig.P2P_turnCredential = redact(pluginConfig.P2P_turnCredential);
|
||||
pluginConfig.P2P_turnUsername = redact(pluginConfig.P2P_turnUsername);
|
||||
pluginConfig.P2P_turnServers = `(${pluginConfig.P2P_turnServers.split(",").length} servers configured)`;
|
||||
const endpoint = pluginConfig.endpoint;
|
||||
if (endpoint == "") {
|
||||
pluginConfig.endpoint = "Not configured or AWS";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { $msg } from "../../../lib/src/common/i18n.ts";
|
||||
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
|
||||
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
||||
import type { PageFunctions } from "./SettingPane.ts";
|
||||
import { visibleOnly } from "./SettingPane.ts";
|
||||
// import { visibleOnly } from "./SettingPane.ts";
|
||||
import InfoPanel from "./InfoPanel.svelte";
|
||||
import { writable } from "svelte/store";
|
||||
import { SveltePanel } from "./SveltePanel.ts";
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
getE2EEConfigSummary,
|
||||
} from "./settingUtils.ts";
|
||||
import { SETTING_KEY_P2P_DEVICE_NAME } from "../../../lib/src/common/types.ts";
|
||||
import { SetupManager, UserMode } from "../ModuleSetupObsidian.ts";
|
||||
import { SetupManager, UserMode } from "../SetupManager.ts";
|
||||
import { OnDialogSettingsDefault, type AllSettings } from "./settingConstants.ts";
|
||||
|
||||
function getSettingsFromEditingSettings(editingSettings: AllSettings): ObsidianLiveSyncSettings {
|
||||
@@ -30,6 +30,14 @@ function getSettingsFromEditingSettings(editingSettings: AllSettings): ObsidianL
|
||||
}
|
||||
return workObj;
|
||||
}
|
||||
const toggleActiveSyncClass = (el: HTMLElement, isActive: () => boolean) => {
|
||||
if (isActive()) {
|
||||
el.addClass("active-pane");
|
||||
} else {
|
||||
el.removeClass("active-pane");
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export function paneRemoteConfig(
|
||||
this: ObsidianLiveSyncSettingTab,
|
||||
@@ -56,39 +64,46 @@ export function paneRemoteConfig(
|
||||
void addPanel(paneEl, "E2EE Configuration", () => {}).then((paneEl) => {
|
||||
new SveltePanel(InfoPanel, paneEl, E2EESummaryWritable);
|
||||
const setupButton = new Setting(paneEl).setName("Configure E2EE");
|
||||
setupButton.addButton((button) =>
|
||||
button
|
||||
.onClick(async () => {
|
||||
const setupManager = this.plugin.getModule(SetupManager);
|
||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||
await setupManager.onlyE2EEConfiguration(UserMode.Update, originalSettings);
|
||||
updateE2EESummary();
|
||||
})
|
||||
.setButtonText("Configure")
|
||||
.setWarning()
|
||||
);
|
||||
setupButton
|
||||
.addButton((button) =>
|
||||
button
|
||||
.onClick(async () => {
|
||||
const setupManager = this.plugin.getModule(SetupManager);
|
||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||
await setupManager.onlyE2EEConfiguration(UserMode.Update, originalSettings);
|
||||
updateE2EESummary();
|
||||
})
|
||||
.setButtonText("Configure")
|
||||
.setWarning()
|
||||
)
|
||||
.addButton((button) =>
|
||||
button
|
||||
.onClick(async () => {
|
||||
const setupManager = this.plugin.getModule(SetupManager);
|
||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||
await setupManager.onConfigureManually(originalSettings, UserMode.Update);
|
||||
updateE2EESummary();
|
||||
})
|
||||
.setButtonText("Configure And Change Remote")
|
||||
.setWarning()
|
||||
);
|
||||
updateE2EESummary();
|
||||
});
|
||||
}
|
||||
{
|
||||
void addPanel(
|
||||
paneEl,
|
||||
$msg("obsidianLiveSyncSettingTab.titleRemoteServer"),
|
||||
() => {},
|
||||
() => ({ classes: this.editingSettings.remoteType === REMOTE_COUCHDB ? ["active-sync"] : [] })
|
||||
).then((paneEl) => {
|
||||
const nSetting = new Setting(paneEl).setName("Active Remote Configuration");
|
||||
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleRemoteServer"), () => {}).then((paneEl) => {
|
||||
const setting = new Setting(paneEl).setName("Active Remote Configuration");
|
||||
|
||||
const el = nSetting.controlEl.createDiv({});
|
||||
const el = setting.controlEl.createDiv({});
|
||||
el.setText(`${remoteNameMap[this.editingSettings.remoteType] || " - "}`);
|
||||
nSetting.addButton((button) =>
|
||||
setting.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Change Remote and Setup")
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
const setupManager = this.plugin.getModule(SetupManager);
|
||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||
await setupManager.selectServer(originalSettings, UserMode.Update);
|
||||
await setupManager.onSelectServer(originalSettings, UserMode.Update);
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -103,30 +118,29 @@ export function paneRemoteConfig(
|
||||
info: getCouchDBConfigSummary(this.editingSettings),
|
||||
});
|
||||
};
|
||||
void addPanel(
|
||||
paneEl,
|
||||
$msg("obsidianLiveSyncSettingTab.titleCouchDB"),
|
||||
() => {},
|
||||
() => ({ classes: this.editingSettings.remoteType === REMOTE_COUCHDB ? ["active-sync"] : [] })
|
||||
).then((paneEl) => {
|
||||
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleCouchDB"), () => {}).then((paneEl) => {
|
||||
new SveltePanel(InfoPanel, paneEl, summaryWritable);
|
||||
const setupButton = new Setting(paneEl).setName("Configure Remote");
|
||||
setupButton.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Configure")
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
const setupManager = this.plugin.getModule(SetupManager);
|
||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||
await setupManager.onCouchDBManualSetup(
|
||||
UserMode.Update,
|
||||
originalSettings,
|
||||
this.editingSettings.remoteType === REMOTE_COUCHDB
|
||||
);
|
||||
setupButton
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Configure")
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
const setupManager = this.plugin.getModule(SetupManager);
|
||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||
await setupManager.onCouchDBManualSetup(
|
||||
UserMode.Update,
|
||||
originalSettings,
|
||||
this.editingSettings.remoteType === REMOTE_COUCHDB
|
||||
);
|
||||
|
||||
updateSummary();
|
||||
})
|
||||
);
|
||||
updateSummary();
|
||||
})
|
||||
)
|
||||
.addOnUpdate(() =>
|
||||
toggleActiveSyncClass(paneEl, () => this.editingSettings.remoteType === REMOTE_COUCHDB)
|
||||
);
|
||||
});
|
||||
}
|
||||
{
|
||||
@@ -139,30 +153,29 @@ export function paneRemoteConfig(
|
||||
info: getBucketConfigSummary(this.editingSettings),
|
||||
});
|
||||
};
|
||||
void addPanel(
|
||||
paneEl,
|
||||
$msg("obsidianLiveSyncSettingTab.titleMinioS3R2"),
|
||||
() => {},
|
||||
() => ({ classes: this.editingSettings.remoteType === REMOTE_MINIO ? ["active-sync"] : [] })
|
||||
).then((paneEl) => {
|
||||
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleMinioS3R2"), () => {}).then((paneEl) => {
|
||||
new SveltePanel(InfoPanel, paneEl, summaryWritable);
|
||||
const setupButton = new Setting(paneEl).setName("Configure Remote");
|
||||
setupButton.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Configure")
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
const setupManager = this.plugin.getModule(SetupManager);
|
||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||
await setupManager.onBucketManualSetup(
|
||||
UserMode.Update,
|
||||
originalSettings,
|
||||
this.editingSettings.remoteType === REMOTE_MINIO
|
||||
);
|
||||
//TODO
|
||||
updateSummary();
|
||||
})
|
||||
);
|
||||
setupButton
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Configure")
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
const setupManager = this.plugin.getModule(SetupManager);
|
||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||
await setupManager.onBucketManualSetup(
|
||||
UserMode.Update,
|
||||
originalSettings,
|
||||
this.editingSettings.remoteType === REMOTE_MINIO
|
||||
);
|
||||
//TODO
|
||||
updateSummary();
|
||||
})
|
||||
)
|
||||
.addOnUpdate(() =>
|
||||
toggleActiveSyncClass(paneEl, () => this.editingSettings.remoteType === REMOTE_MINIO)
|
||||
);
|
||||
});
|
||||
}
|
||||
{
|
||||
@@ -180,54 +193,35 @@ export function paneRemoteConfig(
|
||||
}),
|
||||
});
|
||||
};
|
||||
void addPanel(
|
||||
paneEl,
|
||||
"Peer-to-Peer Synchronisation",
|
||||
() => {},
|
||||
() => ({ classes: this.editingSettings.remoteType === REMOTE_P2P ? ["active-sync"] : [] })
|
||||
).then((paneEl) => {
|
||||
void addPanel(paneEl, "Peer-to-Peer Synchronisation", () => {}).then((paneEl) => {
|
||||
new SveltePanel(InfoPanel, paneEl, summaryWritable);
|
||||
const setupButton = new Setting(paneEl).setName("Configure Remote");
|
||||
setupButton.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Configure")
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
const setupManager = this.plugin.getModule(SetupManager);
|
||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||
await setupManager.onP2PManualSetup(
|
||||
UserMode.Update,
|
||||
originalSettings,
|
||||
this.editingSettings.remoteType === REMOTE_P2P
|
||||
);
|
||||
//TODO
|
||||
updateSummary();
|
||||
})
|
||||
);
|
||||
setupButton
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Configure")
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
const setupManager = this.plugin.getModule(SetupManager);
|
||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||
await setupManager.onP2PManualSetup(
|
||||
UserMode.Update,
|
||||
originalSettings,
|
||||
this.editingSettings.remoteType === REMOTE_P2P
|
||||
);
|
||||
//TODO
|
||||
updateSummary();
|
||||
})
|
||||
)
|
||||
.addOnUpdate(() =>
|
||||
toggleActiveSyncClass(
|
||||
paneEl,
|
||||
() => this.editingSettings.remoteType === REMOTE_P2P || this.editingSettings.P2P_Enabled
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleActiveRemoteServer")).then((paneEl) => {
|
||||
// const containerRemoteDatabaseEl = containerEl.createDiv();
|
||||
this.createEl(
|
||||
paneEl,
|
||||
"div",
|
||||
{
|
||||
text: $msg("obsidianLiveSyncSettingTab.msgSettingsUnchangeableDuringSync"),
|
||||
},
|
||||
undefined,
|
||||
visibleOnly(() => this.isAnySyncEnabled())
|
||||
).addClass("op-warn-info");
|
||||
|
||||
new Setting(paneEl)
|
||||
.autoWireDropDown("remoteType", {
|
||||
holdValue: true,
|
||||
options: remoteNameMap,
|
||||
onUpdate: this.enableOnlySyncDisabled,
|
||||
})
|
||||
.addApplyButton(["remoteType"]);
|
||||
});
|
||||
|
||||
// new Setting(paneEl)
|
||||
// .setDesc("Generate ES256 Keypair for testing")
|
||||
// .addButton((button) =>
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { PageFunctions } from "./SettingPane.ts";
|
||||
import { visibleOnly } from "./SettingPane.ts";
|
||||
import { DEFAULT_SETTINGS } from "../../../lib/src/common/types.ts";
|
||||
import { request } from "obsidian";
|
||||
import { SetupManager, UserMode } from "../ModuleSetupObsidian.ts";
|
||||
import { SetupManager, UserMode } from "../SetupManager.ts";
|
||||
export function paneSetup(
|
||||
this: ObsidianLiveSyncSettingTab,
|
||||
paneEl: HTMLElement,
|
||||
@@ -36,7 +36,7 @@ export function paneSetup(
|
||||
.addButton((text) => {
|
||||
text.setButtonText("Rerun Wizard").onClick(async () => {
|
||||
const setupManager = this.plugin.getModule(SetupManager);
|
||||
await setupManager.onBoard(UserMode.ExistingUser);
|
||||
await setupManager.onOnboard(UserMode.ExistingUser);
|
||||
// await this.plugin.moduleSetupObsidian.onBoardingWizard(true);
|
||||
});
|
||||
});
|
||||
|
||||
378
src/modules/features/SetupManager.ts
Normal file
378
src/modules/features/SetupManager.ts
Normal file
@@ -0,0 +1,378 @@
|
||||
import {
|
||||
type ObsidianLiveSyncSettings,
|
||||
DEFAULT_SETTINGS,
|
||||
LOG_LEVEL_NOTICE,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
REMOTE_COUCHDB,
|
||||
REMOTE_MINIO,
|
||||
REMOTE_P2P,
|
||||
} from "../../lib/src/common/types.ts";
|
||||
import { generatePatchObj, isObjectDifferent } from "../../lib/src/common/utils.ts";
|
||||
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||
import { SvelteDialogManager } from "./SetupWizard/ObsidianSvelteDialog.ts";
|
||||
import Intro from "./SetupWizard/dialogs/Intro.svelte";
|
||||
import SelectMethodNewUser from "./SetupWizard/dialogs/SelectMethodNewUser.svelte";
|
||||
import SelectMethodExisting from "./SetupWizard/dialogs/SelectMethodExisting.svelte";
|
||||
import ScanQRCode from "./SetupWizard/dialogs/ScanQRCode.svelte";
|
||||
import UseSetupURI from "./SetupWizard/dialogs/UseSetupURI.svelte";
|
||||
import OutroNewUser from "./SetupWizard/dialogs/OutroNewUser.svelte";
|
||||
import OutroExistingUser from "./SetupWizard/dialogs/OutroExistingUser.svelte";
|
||||
import OutroAskUserMode from "./SetupWizard/dialogs/OutroAskUserMode.svelte";
|
||||
import SetupRemote from "./SetupWizard/dialogs/SetupRemote.svelte";
|
||||
import SetupRemoteCouchDB from "./SetupWizard/dialogs/SetupRemoteCouchDB.svelte";
|
||||
import SetupRemoteBucket from "./SetupWizard/dialogs/SetupRemoteBucket.svelte";
|
||||
import SetupRemoteP2P from "./SetupWizard/dialogs/SetupRemoteP2P.svelte";
|
||||
import SetupRemoteE2EE from "./SetupWizard/dialogs/SetupRemoteE2EE.svelte";
|
||||
import { decodeSettingsFromQRCodeData } from "../../lib/src/API/processSetting.ts";
|
||||
|
||||
/**
|
||||
* User modes for onboarding and setup
|
||||
*/
|
||||
export const enum UserMode {
|
||||
/**
|
||||
* New User Mode - for users who are new to the plugin
|
||||
*/
|
||||
NewUser = "new-user",
|
||||
/**
|
||||
* Existing User Mode - for users who have used the plugin before, or just configuring again
|
||||
*/
|
||||
ExistingUser = "existing-user",
|
||||
/**
|
||||
* Unknown User Mode - for cases where the user mode is not determined
|
||||
*/
|
||||
Unknown = "unknown",
|
||||
/**
|
||||
* Update User Mode - for users who are updating configuration. May be `existing-user` as well, but possibly they want to treat it differently.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
|
||||
Update = "unknown", // Alias for Unknown for better readability
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup Manager to handle onboarding and configuration setup
|
||||
*/
|
||||
export class SetupManager extends AbstractObsidianModule {
|
||||
/**
|
||||
* Dialog manager for handling Svelte dialogs
|
||||
*/
|
||||
private dialogManager: SvelteDialogManager = new SvelteDialogManager(this.plugin);
|
||||
|
||||
/**
|
||||
* Starts the onboarding process
|
||||
* @returns Promise that resolves to true if onboarding completed successfully, false otherwise
|
||||
*/
|
||||
async startOnBoarding(): Promise<boolean> {
|
||||
const isUserNewOrExisting = await this.dialogManager.openWithExplicitCancel(Intro);
|
||||
if (isUserNewOrExisting === "new-user") {
|
||||
await this.onOnboard(UserMode.NewUser);
|
||||
} else if (isUserNewOrExisting === "existing-user") {
|
||||
await this.onOnboard(UserMode.ExistingUser);
|
||||
} else if (isUserNewOrExisting === "cancelled") {
|
||||
this._log("Onboarding cancelled by user.", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the onboarding process based on user mode
|
||||
* @param userMode
|
||||
* @returns Promise that resolves to true if onboarding completed successfully, false otherwise
|
||||
*/
|
||||
async onOnboard(userMode: UserMode): Promise<boolean> {
|
||||
const originalSetting = userMode === UserMode.NewUser ? DEFAULT_SETTINGS : this.core.settings;
|
||||
if (userMode === UserMode.NewUser) {
|
||||
//Ask how to apply initial setup
|
||||
const method = await this.dialogManager.openWithExplicitCancel(SelectMethodNewUser);
|
||||
if (method === "use-setup-uri") {
|
||||
await this.onUseSetupURI(userMode);
|
||||
} else if (method === "configure-manually") {
|
||||
await this.onConfigureManually(originalSetting, userMode);
|
||||
} else if (method === "cancelled") {
|
||||
this._log("Onboarding cancelled by user.", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
} else if (userMode === UserMode.ExistingUser) {
|
||||
const method = await this.dialogManager.openWithExplicitCancel(SelectMethodExisting);
|
||||
if (method === "use-setup-uri") {
|
||||
await this.onUseSetupURI(userMode);
|
||||
} else if (method === "configure-manually") {
|
||||
await this.onConfigureManually(originalSetting, userMode);
|
||||
} else if (method === "scan-qr-code") {
|
||||
await this.onPromptQRCodeInstruction();
|
||||
} else if (method === "cancelled") {
|
||||
this._log("Onboarding cancelled by user.", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles setup using a setup URI
|
||||
* @param userMode
|
||||
* @param setupURI
|
||||
* @returns Promise that resolves to true if onboarding completed successfully, false otherwise
|
||||
*/
|
||||
async onUseSetupURI(userMode: UserMode, setupURI: string = ""): Promise<boolean> {
|
||||
const newSetting = await this.dialogManager.openWithExplicitCancel(UseSetupURI, setupURI);
|
||||
if (newSetting === "cancelled") {
|
||||
this._log("Setup URI dialog cancelled.", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
this._log("Setup URI dialog closed.", LOG_LEVEL_VERBOSE);
|
||||
return await this.onConfirmApplySettingsFromWizard(newSetting, userMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles manual setup for CouchDB
|
||||
* @param userMode
|
||||
* @param currentSetting
|
||||
* @param activate Whether to activate the CouchDB as remote type
|
||||
* @returns Promise that resolves to true if setup completed successfully, false otherwise
|
||||
*/
|
||||
async onCouchDBManualSetup(
|
||||
userMode: UserMode,
|
||||
currentSetting: ObsidianLiveSyncSettings,
|
||||
activate = true
|
||||
): Promise<boolean> {
|
||||
const originalSetting = JSON.parse(JSON.stringify(currentSetting)) as ObsidianLiveSyncSettings;
|
||||
const baseSetting = JSON.parse(JSON.stringify(originalSetting)) as ObsidianLiveSyncSettings;
|
||||
const couchConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteCouchDB, originalSetting);
|
||||
if (couchConf === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await this.onOnboard(userMode);
|
||||
}
|
||||
const newSetting = { ...baseSetting, ...couchConf } as ObsidianLiveSyncSettings;
|
||||
if (activate) {
|
||||
newSetting.remoteType = REMOTE_COUCHDB;
|
||||
}
|
||||
return await this.onConfirmApplySettingsFromWizard(newSetting, userMode, activate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles manual setup for S3-compatible bucket
|
||||
* @param userMode
|
||||
* @param currentSetting
|
||||
* @param activate Whether to activate the Bucket as remote type
|
||||
* @returns Promise that resolves to true if setup completed successfully, false otherwise
|
||||
*/
|
||||
async onBucketManualSetup(
|
||||
userMode: UserMode,
|
||||
currentSetting: ObsidianLiveSyncSettings,
|
||||
activate = true
|
||||
): Promise<boolean> {
|
||||
const bucketConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteBucket, currentSetting);
|
||||
if (bucketConf === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await this.onOnboard(userMode);
|
||||
}
|
||||
const newSetting = { ...currentSetting, ...bucketConf } as ObsidianLiveSyncSettings;
|
||||
if (activate) {
|
||||
newSetting.remoteType = REMOTE_MINIO;
|
||||
}
|
||||
return await this.onConfirmApplySettingsFromWizard(newSetting, userMode, activate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles manual setup for P2P
|
||||
* @param userMode
|
||||
* @param currentSetting
|
||||
* @param activate Whether to activate the P2P as remote type (as P2P Only setup)
|
||||
* @returns Promise that resolves to true if setup completed successfully, false otherwise
|
||||
*/
|
||||
async onP2PManualSetup(
|
||||
userMode: UserMode,
|
||||
currentSetting: ObsidianLiveSyncSettings,
|
||||
activate = true
|
||||
): Promise<boolean> {
|
||||
const p2pConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteP2P, currentSetting);
|
||||
if (p2pConf === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await this.onOnboard(userMode);
|
||||
}
|
||||
const newSetting = { ...currentSetting, ...p2pConf } as ObsidianLiveSyncSettings;
|
||||
if (activate) {
|
||||
newSetting.remoteType = REMOTE_P2P;
|
||||
}
|
||||
return await this.onConfirmApplySettingsFromWizard(newSetting, userMode, activate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles only E2EE configuration
|
||||
* @param userMode
|
||||
* @param currentSetting
|
||||
* @returns
|
||||
*/
|
||||
async onlyE2EEConfiguration(userMode: UserMode, currentSetting: ObsidianLiveSyncSettings): Promise<boolean> {
|
||||
const e2eeConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteE2EE, currentSetting);
|
||||
if (e2eeConf === "cancelled") {
|
||||
this._log("E2EE configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await false;
|
||||
}
|
||||
const newSetting = {
|
||||
...currentSetting,
|
||||
...e2eeConf,
|
||||
} as ObsidianLiveSyncSettings;
|
||||
return await this.onConfirmApplySettingsFromWizard(newSetting, userMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles manual configuration flow (E2EE + select server)
|
||||
* @param originalSetting
|
||||
* @param userMode
|
||||
* @returns
|
||||
*/
|
||||
async onConfigureManually(originalSetting: ObsidianLiveSyncSettings, userMode: UserMode): Promise<boolean> {
|
||||
const e2eeConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteE2EE, originalSetting);
|
||||
if (e2eeConf === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await this.onOnboard(userMode);
|
||||
}
|
||||
const currentSetting = {
|
||||
...originalSetting,
|
||||
...e2eeConf,
|
||||
} as ObsidianLiveSyncSettings;
|
||||
return await this.onSelectServer(currentSetting, userMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles server selection during manual configuration
|
||||
* @param currentSetting
|
||||
* @param userMode
|
||||
* @returns
|
||||
*/
|
||||
async onSelectServer(currentSetting: ObsidianLiveSyncSettings, userMode: UserMode): Promise<boolean> {
|
||||
const method = await this.dialogManager.openWithExplicitCancel(SetupRemote);
|
||||
if (method === "couchdb") {
|
||||
return await this.onCouchDBManualSetup(userMode, currentSetting, true);
|
||||
} else if (method === "bucket") {
|
||||
return await this.onBucketManualSetup(userMode, currentSetting, true);
|
||||
} else if (method === "p2p") {
|
||||
return await this.onP2PManualSetup(userMode, currentSetting, true);
|
||||
} else if (method === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
if (userMode !== UserMode.Unknown) {
|
||||
return await this.onOnboard(userMode);
|
||||
}
|
||||
}
|
||||
// Should not reach here.
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Confirms and applies settings obtained from the wizard
|
||||
* @param newConf
|
||||
* @param _userMode
|
||||
* @param activate Whether to activate the remote type in the new settings
|
||||
* @param extra Extra function to run before applying settings
|
||||
* @returns Promise that resolves to true if settings applied successfully, false otherwise
|
||||
*/
|
||||
async onConfirmApplySettingsFromWizard(
|
||||
newConf: ObsidianLiveSyncSettings,
|
||||
_userMode: UserMode,
|
||||
activate: boolean = true,
|
||||
extra: () => void = () => {}
|
||||
): Promise<boolean> {
|
||||
let userMode = _userMode;
|
||||
if (userMode === UserMode.Unknown) {
|
||||
if (isObjectDifferent(this.settings, newConf, true) === false) {
|
||||
this._log("No changes in settings detected. Skipping applying settings from wizard.", LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
}
|
||||
const patch = generatePatchObj(this.settings, newConf);
|
||||
console.log(`Changes:`);
|
||||
console.dir(patch);
|
||||
if (!activate) {
|
||||
extra();
|
||||
await this.applySetting(newConf, UserMode.ExistingUser);
|
||||
this._log("Setting Applied", LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
}
|
||||
// Check virtual changes
|
||||
const original = { ...this.settings, P2P_DevicePeerName: "" } as ObsidianLiveSyncSettings;
|
||||
const modified = { ...newConf, P2P_DevicePeerName: "" } as ObsidianLiveSyncSettings;
|
||||
const isOnlyVirtualChange = isObjectDifferent(original, modified, true) === false;
|
||||
if (isOnlyVirtualChange) {
|
||||
extra();
|
||||
await this.applySetting(newConf, UserMode.ExistingUser);
|
||||
this._log("Settings from wizard applied.", LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
} else {
|
||||
const userModeResult = await this.dialogManager.openWithExplicitCancel(OutroAskUserMode);
|
||||
if (userModeResult === "new-user") {
|
||||
userMode = UserMode.NewUser;
|
||||
} else if (userModeResult === "existing-user") {
|
||||
userMode = UserMode.ExistingUser;
|
||||
} else if (userModeResult === "compatible-existing-user") {
|
||||
extra();
|
||||
await this.applySetting(newConf, UserMode.ExistingUser);
|
||||
this._log("Settings from wizard applied.", LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
} else if (userModeResult === "cancelled") {
|
||||
this._log("User cancelled applying settings from wizard.", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
const component = userMode === UserMode.NewUser ? OutroNewUser : OutroExistingUser;
|
||||
const confirm = await this.dialogManager.openWithExplicitCancel(component);
|
||||
if (confirm === "cancelled") {
|
||||
this._log("User cancelled applying settings from wizard..", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
if (confirm) {
|
||||
extra();
|
||||
await this.applySetting(newConf, userMode);
|
||||
if (userMode === UserMode.NewUser) {
|
||||
// For new users, schedule a rebuild everything.
|
||||
await this.core.rebuilder.scheduleRebuild();
|
||||
} else {
|
||||
// For existing users, schedule a fetch.
|
||||
await this.core.rebuilder.scheduleFetch();
|
||||
}
|
||||
}
|
||||
// Settings applied, but may require rebuild to take effect.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user with QR code scanning instructions
|
||||
* @returns Promise that resolves to false as QR code instruction dialog does not yield settings directly
|
||||
*/
|
||||
|
||||
async onPromptQRCodeInstruction(): Promise<boolean> {
|
||||
const qrResult = await this.dialogManager.open(ScanQRCode);
|
||||
this._log("QR Code dialog closed.", LOG_LEVEL_VERBOSE);
|
||||
// Result is not used, but log it for debugging.
|
||||
this._log(`QR Code result: ${qrResult}`, LOG_LEVEL_VERBOSE);
|
||||
// QR Code instruction dialog never yields settings directly.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes settings from a QR code string and applies them
|
||||
* @param qr QR code string containing encoded settings
|
||||
* @returns Promise that resolves to true if settings applied successfully, false otherwise
|
||||
*/
|
||||
async decodeQR(qr: string) {
|
||||
const newSettings = decodeSettingsFromQRCodeData(qr);
|
||||
return await this.onConfirmApplySettingsFromWizard(newSettings, UserMode.Unknown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the new settings to the core settings and saves them
|
||||
* @param newConf
|
||||
* @param userMode
|
||||
* @returns Promise that resolves to true if settings applied successfully, false otherwise
|
||||
*/
|
||||
async applySetting(newConf: ObsidianLiveSyncSettings, userMode: UserMode) {
|
||||
const newSetting = {
|
||||
...this.core.settings,
|
||||
...newConf,
|
||||
};
|
||||
this.core.settings = newSetting;
|
||||
this.services.setting.clearUsedPassphrase();
|
||||
await this.services.setting.saveSettingData();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -26,18 +26,15 @@
|
||||
import { onMount } from "svelte";
|
||||
import type { GuestDialogProps } from "../../../../lib/src/UI/svelteDialog";
|
||||
import { SETTING_KEY_P2P_DEVICE_NAME } from "../../../../lib/src/common/types";
|
||||
import ExtraItems from "../../../../lib/src/UI/components/ExtraItems.svelte";
|
||||
|
||||
const default_setting = pickP2PSyncSettings(DEFAULT_SETTINGS);
|
||||
let syncSetting = $state<P2PConnectionInfo>({ ...default_setting });
|
||||
|
||||
const context = getObsidianDialogContext();
|
||||
let error = $state("");
|
||||
let devicePeerId = $state("");
|
||||
const TYPE_CANCELLED = "cancelled";
|
||||
type SettingInfo = {
|
||||
info: P2PConnectionInfo;
|
||||
devicePeerId: string;
|
||||
};
|
||||
type SettingInfo = P2PConnectionInfo;
|
||||
type ResultType = typeof TYPE_CANCELLED | SettingInfo;
|
||||
type Props = GuestDialogProps<ResultType, P2PSyncSetting>;
|
||||
|
||||
@@ -49,7 +46,11 @@
|
||||
copyTo(initialData, syncSetting);
|
||||
}
|
||||
if (context.services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME)) {
|
||||
devicePeerId = context.services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME) as string;
|
||||
syncSetting.P2P_DevicePeerName = context.services.config.getSmallConfig(
|
||||
SETTING_KEY_P2P_DEVICE_NAME
|
||||
) as string;
|
||||
} else {
|
||||
syncSetting.P2P_DevicePeerName = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -103,7 +104,7 @@
|
||||
confirm: context.plugin.confirm,
|
||||
db: dummyPouch,
|
||||
simpleStore: store,
|
||||
deviceName: devicePeerId || "unnamed-device",
|
||||
deviceName: syncSetting.P2P_DevicePeerName || "unnamed-device",
|
||||
platform: "setup-wizard",
|
||||
};
|
||||
const replicator = new TrysteroReplicator(env);
|
||||
@@ -164,10 +165,7 @@
|
||||
error = (await checkConnection()) || "";
|
||||
if (!error) {
|
||||
const setting = generateSetting();
|
||||
setResult({
|
||||
info: pickP2PSyncSettings(setting),
|
||||
devicePeerId: devicePeerId,
|
||||
});
|
||||
setResult(pickP2PSyncSettings(setting));
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -177,10 +175,7 @@
|
||||
}
|
||||
function commit() {
|
||||
const setting = pickP2PSyncSettings(generateSetting());
|
||||
setResult({
|
||||
info: setting,
|
||||
devicePeerId: devicePeerId,
|
||||
});
|
||||
setResult(setting);
|
||||
}
|
||||
function cancel() {
|
||||
setResult(TYPE_CANCELLED);
|
||||
@@ -190,13 +185,16 @@
|
||||
syncSetting.P2P_relays.trim() !== "" &&
|
||||
syncSetting.P2P_roomID.trim() !== "" &&
|
||||
syncSetting.P2P_passphrase.trim() !== "" &&
|
||||
devicePeerId.trim() !== ""
|
||||
(syncSetting.P2P_DevicePeerName ?? "").trim() !== ""
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<DialogHeader title="P2P Configuration" />
|
||||
<Guidance>Please enter the Peer-to-Peer Synchronisation information below.</Guidance>
|
||||
<InputRow label="Enabled">
|
||||
<input type="checkbox" name="p2p-enabled" bind:checked={syncSetting.P2P_Enabled} />
|
||||
</InputRow>
|
||||
<InputRow label="Relay URL">
|
||||
<input
|
||||
type="text"
|
||||
@@ -237,10 +235,63 @@
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
bind:value={devicePeerId}
|
||||
bind:value={syncSetting.P2P_DevicePeerName}
|
||||
/>
|
||||
</InputRow>
|
||||
|
||||
<InputRow label="Auto Start P2P Connection">
|
||||
<input type="checkbox" name="p2p-auto-start" bind:checked={syncSetting.P2P_AutoStart} />
|
||||
</InputRow>
|
||||
<InfoNote>
|
||||
If "Auto Start P2P Connection" is enabled, the P2P connection will be started automatically when the plug-in
|
||||
launches.
|
||||
</InfoNote>
|
||||
<InputRow label="Auto Broadcast Changes">
|
||||
<input type="checkbox" name="p2p-auto-broadcast" bind:checked={syncSetting.P2P_AutoBroadcast} />
|
||||
</InputRow>
|
||||
<InfoNote>
|
||||
If "Auto Broadcast Changes" is enabled, changes will be automatically broadcasted to connected peers without
|
||||
requiring manual intervention. This requests peers to fetch this device's changes.
|
||||
</InfoNote>
|
||||
<ExtraItems title="Advanced Settings">
|
||||
<InfoNote>
|
||||
TURN server settings are only necessary if you are behind a strict NAT or firewall that prevents direct P2P
|
||||
connections. In most cases, you can leave these fields blank.
|
||||
</InfoNote>
|
||||
<InfoNote warning>
|
||||
Using public TURN servers may have privacy implications, as your data will be relayed through third-party
|
||||
servers. Even if your data are encrypted, your existence may be known to them. Please ensure you trust the TURN
|
||||
server provider before using their services. Also your `network administrator` too. You should consider setting
|
||||
up your own TURN server for your FQDN, if possible.
|
||||
</InfoNote>
|
||||
<InputRow label="TURN Server URLs (comma-separated)">
|
||||
<textarea
|
||||
name="p2p-turn-servers"
|
||||
placeholder="turn:turn.example.com:3478,turn:turn.example.com:443"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
bind:value={syncSetting.P2P_turnServers}
|
||||
rows="5"
|
||||
></textarea>
|
||||
</InputRow>
|
||||
<InputRow label="TURN Username">
|
||||
<input
|
||||
type="text"
|
||||
name="p2p-turn-username"
|
||||
placeholder="Enter TURN username"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
bind:value={syncSetting.P2P_turnUsername}
|
||||
/>
|
||||
</InputRow>
|
||||
<InputRow label="TURN Credential">
|
||||
<Password
|
||||
name="p2p-turn-credential"
|
||||
placeholder="Enter TURN credential"
|
||||
bind:value={syncSetting.P2P_turnCredential}
|
||||
/>
|
||||
</InputRow>
|
||||
</ExtraItems>
|
||||
<InfoNote error visible={error !== ""}>
|
||||
{error}
|
||||
</InfoNote>
|
||||
|
||||
@@ -20,7 +20,11 @@ import { ObsidianUIService } from "./ObsidianUIService.ts";
|
||||
// All Services will be migrated to be based on Plain Services, not Injectable Services.
|
||||
// This is a migration step.
|
||||
|
||||
export class ObsidianAPIService extends InjectableAPIService {}
|
||||
export class ObsidianAPIService extends InjectableAPIService {
|
||||
getPlatform(): string {
|
||||
return "obsidian";
|
||||
}
|
||||
}
|
||||
export class ObsidianPathService extends InjectablePathService {}
|
||||
export class ObsidianDatabaseService extends InjectableDatabaseService {}
|
||||
|
||||
|
||||
@@ -393,13 +393,13 @@ span.ls-mark-cr::after {
|
||||
div.workspace-leaf-content[data-type=bases] .livesync-status {
|
||||
top: calc(var(--bases-header-height) + var(--header-height));
|
||||
padding: 5px;
|
||||
padding-right:18px;
|
||||
padding-right: 18px;
|
||||
}
|
||||
|
||||
.is-mobile div.workspace-leaf-content[data-type=bases] .livesync-status {
|
||||
top: calc(var(--bases-header-height) + var(--view-header-height));
|
||||
padding: 6px;
|
||||
padding-right:18px;
|
||||
padding-right: 18px;
|
||||
}
|
||||
|
||||
.livesync-status div {
|
||||
@@ -444,6 +444,10 @@ div.workspace-leaf-content[data-type=bases] .livesync-status {
|
||||
padding: 0.5em 1.0em;
|
||||
}
|
||||
|
||||
.active-pane .sls-setting-panel-title {
|
||||
border: 1px solid var(--interactive-accent);
|
||||
}
|
||||
|
||||
.sls-dialogue-note-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"importHelpers": false,
|
||||
"alwaysStrict": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"lib": ["es2018", "DOM", "ES5", "ES6", "ES7", "es2019.array", "ES2021.WeakRef", "ES2020.BigInt", "ESNext.Intl"],
|
||||
"strictBindCallApply": true,
|
||||
|
||||
162
updates.md
162
updates.md
@@ -4,6 +4,101 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||
|
||||
The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope.
|
||||
|
||||
## 0.25.24.beta3
|
||||
|
||||
31st October, 2025
|
||||
|
||||
### TURN server support and important notice
|
||||
|
||||
TURN server settings are only necessary if you are behind a strict NAT or firewall that prevents direct P2P
|
||||
connections. In most cases, you do not need to set up a TURN server.
|
||||
|
||||
Using public TURN servers may have privacy implications, as your data will be relayed through third-party
|
||||
servers. Even if your data are encrypted, your existence may be known to them. Please ensure you trust the TURN
|
||||
server provider before using their services. Also your `network administrator` too. You should consider setting
|
||||
up your own TURN server for your FQDN, if possible.
|
||||
|
||||
### Fixed
|
||||
|
||||
- We can enter the fields in some dialogues correctly on mobile devices now.
|
||||
- The bottom padding is adjusted dynamically according to the keyboard height.
|
||||
|
||||
### New features
|
||||
|
||||
- We can use the TURN server for P2P connections now.
|
||||
|
||||
## ~~0.25.24.beta1~~ 0.25.24.beta2 (For release mistake)
|
||||
|
||||
30th October, 2025
|
||||
|
||||
### Fixed
|
||||
|
||||
- P2P Replication got more robust and stable.
|
||||
- Update [Trystero](https://github.com/dmotz/trystero) to the official v0.22.0!
|
||||
- Fixed a bug that caused P2P connections to drop or (unwanted reconnection to the relay server) unexpectedly in some environments.
|
||||
- Now, the connection status is more accurately reported.
|
||||
- While in the background, the connection to the signalling server is now disconnected to save resources.
|
||||
- When returning to the foreground, it will not reconnect automatically for safety. Please reconnect manually.
|
||||
- All connection configurations should be edited in each dedicated dialogue now.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- Sending configuration via Peer-to-Peer connection is not compatible with older versions.
|
||||
- Please upgrade all devices to v0.25.24.beta1 or later to use this feature again.
|
||||
- This is due to security improvements in the encryption scheme.
|
||||
|
||||
## 0.25.23
|
||||
|
||||
26th October, 2025
|
||||
|
||||
The next version we are preparing (you know that as 0.25.23.beta1) is now still on beta, resulting in this rather unfortunate versioning situation. Apologies for the confusion. The next v0.25.23.beta2 will be v0.25.24.beta1. In other words, this is a v0.25.22.patch-1 actually, but possibly not allowed by Obsidian's rule.
|
||||
(Perhaps we ought to declare 1.0.0 with a little more confidence. The current minor part has been effectively a major one for a long time. If it were 1.22.1 and 1.23.0.beta1, no confusion ).
|
||||
|
||||
### Fixed
|
||||
|
||||
- We are now able to enable optional features correctly again (#732).
|
||||
- No longer oversized files have been processed, furthermore.
|
||||
- Before creating a chunk, the file is verified as the target.
|
||||
- The behaviour upon receiving replication has been changed as follows:
|
||||
- If the remote file is oversized, it is ignored.
|
||||
- If not, but while the local file is oversized, it is also ignored.
|
||||
|
||||
## 0.25.23.beta1
|
||||
|
||||
22nd October, 2025
|
||||
|
||||
Since several issues were pointed out, our setup procedure had been quite `system-oriented`. This is not good for users. Therefore, I have changed the procedure to be more `goal-oriented`. I have made extensive use of Svelte, resulting in a very straightforward setup.
|
||||
While I would like to accelerate documentation and i18n adoption, I do not want to confuse everyone who's already working on it. Therefore, I have decided to release a Beta version at this stage. Significant changes are not expected from this point onward, so I will proceed to stabilise the codebase. (However, this is significant).
|
||||
|
||||
### Fixed (This should be backported to 0.25.22 if the beta phase is prolonged)
|
||||
|
||||
- No longer will larger files create chunks during preparing `Reset Synchronisation on This Device`.
|
||||
|
||||
### Behaviour changes
|
||||
|
||||
- The setup wizard is now more `goal-oriented`. Brand-new screens are introduced.
|
||||
- `Fetch everything` and `Rebuild everything` are now `Reset Synchronisation on This Device` and `Overwrite Server Data with This Device's Files`.
|
||||
- Remote configuration and E2EE settings are now separated into each modal dialogue.
|
||||
- Remote configuration is now more straightforward. And if we need the rebuild (No... `Overwrite Server Data with This Device's Files`), it is now clearly indicated.
|
||||
- Peer-to-Peer settings are also separated into their own modal dialogue (still in progress, and we need to open a P2P pane, still).
|
||||
- Setup-URI, and Report for the Issue are now not copied to the clipboard automatically. Instead, there are copy-dialogue and buttons to copy them explicitly.
|
||||
- This is to avoid confusion for users who do not want to use these features.
|
||||
- No longer optional features are introduced during the setup, or `Reset Synchronisation on This Device`, `Overwrite Server Data with This Device's Files`.
|
||||
- This is to avoid confusion for users who do not want to use these features. Instead, we will be informed that optional features are available after the setup is completed.
|
||||
- We cannot perform `Fetch everything` and `Rebuild everything` (Removed, so the old name) without restarting Obsidian now.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- Setup QR Code generation is separated into a src/lib/src/API/processSetting.ts file. Please use it as a subrepository if you want to generate QR codes in your own application.
|
||||
- Setup-URI is also separated into a src/lib/src/API/processSetting.ts
|
||||
- Some direct access to web APIs is now wrapped into the services layer.
|
||||
|
||||
### Dependency updates
|
||||
|
||||
- Many dependencies are updated. Please see `package.json`.
|
||||
- This is the hardest part of this update. I read most of the changes in the dependencies. If you find any extra information, please let me know.
|
||||
- As upgrading TypeScript, Fixed many UInt8Array<ArrayBuffer> and Uint8Array type mismatches.
|
||||
|
||||
## 0.25.23.beta1
|
||||
|
||||
22nd October, 2025
|
||||
@@ -100,72 +195,5 @@ If you have found any issues, please let me know. I am now on the following:
|
||||
- Event handling now does not rely on 'convention over configuration'.
|
||||
- Services.ts now have a proper event handler registration system.
|
||||
|
||||
## 0.25.20
|
||||
|
||||
26th September, 2025
|
||||
|
||||
### Fixed
|
||||
|
||||
- Chunk fetching no longer reports errors when the fetched chunk could not be saved (#710).
|
||||
- Just using the fetched chunk temporarily.
|
||||
- Chunk fetching reports errors when the fetched chunk is surely corrupted (#710, #712).
|
||||
- It no longer detects files that the plug-in has modified.
|
||||
- It may reduce unnecessary file comparisons and unexpected file states.
|
||||
|
||||
### Improved
|
||||
|
||||
- Now checking the remote database configuration respecting the CouchDB version (#714).
|
||||
|
||||
## 0.25.19
|
||||
|
||||
18th September, 2025
|
||||
|
||||
### Improved
|
||||
|
||||
- Now encoding/decoding for chunk data and encryption/decryption are performed in native functions (if they were available).
|
||||
- This uses Uint8Array.fromBase64 and Uint8Array.toBase64, which are natively available in iOS 18.2+ and Android with Chrome 140+.
|
||||
- In Android, WebView is by default updated with Chrome, so it should be available in most cases.
|
||||
- Note that this is not available in Desktop yet (due to being based on Electron). We are staying tuned for future updates.
|
||||
- This realised by an external(?) package [octagonal-wheels](https://github.com/vrtmrz/octagonal-wheels). Therefore, this update only updates the dependency.
|
||||
|
||||
## 0.25.18
|
||||
|
||||
17th September, 2025
|
||||
|
||||
### Fixed
|
||||
|
||||
- Property encryption detection now works correctly (On Self-hosted LiveSync, it was not broken, but as a library, it was not working correctly).
|
||||
- Initialising the chunk splitter is now surely performed.
|
||||
- DirectFileManipulator now works fine (as a library)
|
||||
- Old `DirectFileManipulatorV1` is now removed.
|
||||
|
||||
### Refactored
|
||||
|
||||
- Removed some unnecessary intermediate files.
|
||||
|
||||
## 0.25.17
|
||||
|
||||
16th September, 2025
|
||||
|
||||
### Fixed
|
||||
|
||||
- No longer information-level logs have produced during toggling `Show only notifications` in the settings (#708).
|
||||
- Ignoring filters for Hidden file sync now works correctly (#709).
|
||||
|
||||
### Refactored
|
||||
|
||||
- Removed some unnecessary intermediate files.
|
||||
|
||||
## 0.25.16
|
||||
|
||||
4th September, 2025
|
||||
|
||||
### Improved
|
||||
|
||||
- Improved connectivity for P2P connections
|
||||
- The connection to the signalling server can now be disconnected while in the background or when explicitly disconnected.
|
||||
- These features use a patch that has not been incorporated upstream.
|
||||
- This patch is available at [vrtmrz/trystero](https://github.com/vrtmrz/trystero).
|
||||
|
||||
Older notes are in
|
||||
[updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md).
|
||||
|
||||
@@ -10,6 +10,73 @@ As a result, this is the first time in a while that forward compatibility has be
|
||||
|
||||
---
|
||||
|
||||
## 0.25.20
|
||||
|
||||
26th September, 2025
|
||||
|
||||
### Fixed
|
||||
|
||||
- Chunk fetching no longer reports errors when the fetched chunk could not be saved (#710).
|
||||
- Just using the fetched chunk temporarily.
|
||||
- Chunk fetching reports errors when the fetched chunk is surely corrupted (#710, #712).
|
||||
- It no longer detects files that the plug-in has modified.
|
||||
- It may reduce unnecessary file comparisons and unexpected file states.
|
||||
|
||||
### Improved
|
||||
|
||||
- Now checking the remote database configuration respecting the CouchDB version (#714).
|
||||
|
||||
## 0.25.19
|
||||
|
||||
18th September, 2025
|
||||
|
||||
### Improved
|
||||
|
||||
- Now encoding/decoding for chunk data and encryption/decryption are performed in native functions (if they were available).
|
||||
- This uses Uint8Array.fromBase64 and Uint8Array.toBase64, which are natively available in iOS 18.2+ and Android with Chrome 140+.
|
||||
- In Android, WebView is by default updated with Chrome, so it should be available in most cases.
|
||||
- Note that this is not available in Desktop yet (due to being based on Electron). We are staying tuned for future updates.
|
||||
- This realised by an external(?) package [octagonal-wheels](https://github.com/vrtmrz/octagonal-wheels). Therefore, this update only updates the dependency.
|
||||
|
||||
## 0.25.18
|
||||
|
||||
17th September, 2025
|
||||
|
||||
### Fixed
|
||||
|
||||
- Property encryption detection now works correctly (On Self-hosted LiveSync, it was not broken, but as a library, it was not working correctly).
|
||||
- Initialising the chunk splitter is now surely performed.
|
||||
- DirectFileManipulator now works fine (as a library)
|
||||
- Old `DirectFileManipulatorV1` is now removed.
|
||||
|
||||
### Refactored
|
||||
|
||||
- Removed some unnecessary intermediate files.
|
||||
|
||||
## 0.25.17
|
||||
|
||||
16th September, 2025
|
||||
|
||||
### Fixed
|
||||
|
||||
- No longer information-level logs have produced during toggling `Show only notifications` in the settings (#708).
|
||||
- Ignoring filters for Hidden file sync now works correctly (#709).
|
||||
|
||||
### Refactored
|
||||
|
||||
- Removed some unnecessary intermediate files.
|
||||
|
||||
## 0.25.16
|
||||
|
||||
4th September, 2025
|
||||
|
||||
### Improved
|
||||
|
||||
- Improved connectivity for P2P connections
|
||||
- The connection to the signalling server can now be disconnected while in the background or when explicitly disconnected.
|
||||
- These features use a patch that has not been incorporated upstream.
|
||||
- This patch is available at [vrtmrz/trystero](https://github.com/vrtmrz/trystero).
|
||||
|
||||
## 0.25.15
|
||||
|
||||
3rd September, 2025
|
||||
|
||||
Reference in New Issue
Block a user