mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-12-12 17:25:56 +00:00
### Fixed
- P2P Replication got more robust and stable. ### Breaking changes - Send configuration via Peer-to-Peer connection is not compatible with older versions.
This commit is contained in:
2564
package-lock.json
generated
2564
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -94,9 +94,9 @@
|
|||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"idb": "^8.0.3",
|
"idb": "^8.0.3",
|
||||||
"minimatch": "^10.0.2",
|
"minimatch": "^10.0.2",
|
||||||
"octagonal-wheels": "^0.1.41",
|
"octagonal-wheels": "^0.1.42",
|
||||||
"qrcode-generator": "^1.4.4",
|
"qrcode-generator": "^1.4.4",
|
||||||
"trystero": "github:vrtmrz/trystero#9e892a93ec14eeb57ce806d272fbb7c3935256d8",
|
"trystero": "^0.22.0",
|
||||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,6 +251,9 @@
|
|||||||
};
|
};
|
||||||
cmdSync.setConfig(initialDialogStatusKey, JSON.stringify(dialogStatus));
|
cmdSync.setConfig(initialDialogStatusKey, JSON.stringify(dialogStatus));
|
||||||
});
|
});
|
||||||
|
let isObsidian = $derived.by(() => {
|
||||||
|
return plugin.services.API.getPlatform() === "obsidian";
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<article>
|
<article>
|
||||||
@@ -266,95 +269,105 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</details>
|
</details>
|
||||||
<h2>Connection Settings</h2>
|
<h2>Connection Settings</h2>
|
||||||
<details bind:open={isSettingOpened}>
|
{#if isObsidian}
|
||||||
<summary>{eRelay}</summary>
|
You can configure in the Obsidian Plugin Settings.
|
||||||
<table class="settings">
|
{:else}
|
||||||
<tbody>
|
<details bind:open={isSettingOpened}>
|
||||||
<tr>
|
<summary>{eRelay}</summary>
|
||||||
<th> Enable P2P Replicator </th>
|
<table class="settings">
|
||||||
<td>
|
<tbody>
|
||||||
<label class={{ "is-dirty": isP2PEnabledModified }}>
|
<tr>
|
||||||
<input type="checkbox" bind:checked={eP2PEnabled} />
|
<th> Enable P2P Replicator </th>
|
||||||
</label>
|
<td>
|
||||||
</td>
|
<label class={{ "is-dirty": isP2PEnabledModified }}>
|
||||||
</tr><tr>
|
<input type="checkbox" bind:checked={eP2PEnabled} />
|
||||||
<th> Relay settings </th>
|
</label>
|
||||||
<td>
|
</td>
|
||||||
<label class={{ "is-dirty": isRelayModified }}>
|
</tr><tr>
|
||||||
<input
|
<th> Relay settings </th>
|
||||||
type="text"
|
<td>
|
||||||
placeholder="wss://exp-relay.vrtmrz.net, wss://xxxxx"
|
<label class={{ "is-dirty": isRelayModified }}>
|
||||||
bind:value={eRelay}
|
<input
|
||||||
autocomplete="off"
|
type="text"
|
||||||
/>
|
placeholder="wss://exp-relay.vrtmrz.net, wss://xxxxx"
|
||||||
<button onclick={() => useDefaultRelay()}> Use vrtmrz's relay </button>
|
bind:value={eRelay}
|
||||||
</label>
|
autocomplete="off"
|
||||||
</td>
|
/>
|
||||||
</tr>
|
<button onclick={() => useDefaultRelay()}> Use vrtmrz's relay </button>
|
||||||
<tr>
|
</label>
|
||||||
<th> Room ID </th>
|
</td>
|
||||||
<td>
|
</tr>
|
||||||
<label class={{ "is-dirty": isRoomIdModified }}>
|
<tr>
|
||||||
<input
|
<th> Room ID </th>
|
||||||
type="text"
|
<td>
|
||||||
placeholder="anything-you-like"
|
<label class={{ "is-dirty": isRoomIdModified }}>
|
||||||
bind:value={eRoomId}
|
<input
|
||||||
autocomplete="off"
|
type="text"
|
||||||
spellcheck="false"
|
placeholder="anything-you-like"
|
||||||
autocorrect="off"
|
bind:value={eRoomId}
|
||||||
/>
|
autocomplete="off"
|
||||||
<button onclick={() => chooseRandom()}> Use Random Number </button>
|
spellcheck="false"
|
||||||
</label>
|
autocorrect="off"
|
||||||
<span>
|
/>
|
||||||
<small>
|
<button onclick={() => chooseRandom()}> Use Random Number </button>
|
||||||
This can isolate your connections between devices. Use the same Room ID for the same
|
</label>
|
||||||
devices.</small
|
<span>
|
||||||
>
|
<small>
|
||||||
</span>
|
This can isolate your connections between devices. Use the same Room ID for the same
|
||||||
</td>
|
devices.</small
|
||||||
</tr>
|
>
|
||||||
<tr>
|
</span>
|
||||||
<th> Password </th>
|
</td>
|
||||||
<td>
|
</tr>
|
||||||
<label class={{ "is-dirty": isPasswordModified }}>
|
<tr>
|
||||||
<input type="password" placeholder="password" bind:value={ePassword} />
|
<th> Password </th>
|
||||||
</label>
|
<td>
|
||||||
<span>
|
<label class={{ "is-dirty": isPasswordModified }}>
|
||||||
<small> This password is used to encrypt the connection. Use something long enough. </small>
|
<input type="password" placeholder="password" bind:value={ePassword} />
|
||||||
</span>
|
</label>
|
||||||
</td>
|
<span>
|
||||||
</tr>
|
<small>
|
||||||
<tr>
|
This password is used to encrypt the connection. Use something long enough.
|
||||||
<th> This device name </th>
|
</small>
|
||||||
<td>
|
</span>
|
||||||
<label class={{ "is-dirty": isDeviceNameModified }}>
|
</td>
|
||||||
<input type="text" placeholder="iphone-16" bind:value={eDeviceName} autocomplete="off" />
|
</tr>
|
||||||
</label>
|
<tr>
|
||||||
<span>
|
<th> This device name </th>
|
||||||
<small>
|
<td>
|
||||||
Device name to identify the device. Please use shorter one for the stable peer
|
<label class={{ "is-dirty": isDeviceNameModified }}>
|
||||||
detection, i.e., "iphone-16" or "macbook-2021".
|
<input
|
||||||
</small>
|
type="text"
|
||||||
</span>
|
placeholder="iphone-16"
|
||||||
</td>
|
bind:value={eDeviceName}
|
||||||
</tr>
|
autocomplete="off"
|
||||||
<tr>
|
/>
|
||||||
<th> Auto Connect </th>
|
</label>
|
||||||
<td>
|
<span>
|
||||||
<label class={{ "is-dirty": isAutoStartModified }}>
|
<small>
|
||||||
<input type="checkbox" bind:checked={eAutoStart} />
|
Device name to identify the device. Please use shorter one for the stable peer
|
||||||
</label>
|
detection, i.e., "iphone-16" or "macbook-2021".
|
||||||
</td>
|
</small>
|
||||||
</tr>
|
</span>
|
||||||
<tr>
|
</td>
|
||||||
<th> Start change-broadcasting on Connect </th>
|
</tr>
|
||||||
<td>
|
<tr>
|
||||||
<label class={{ "is-dirty": isAutoBroadcastModified }}>
|
<th> Auto Connect </th>
|
||||||
<input type="checkbox" bind:checked={eAutoBroadcast} />
|
<td>
|
||||||
</label>
|
<label class={{ "is-dirty": isAutoStartModified }}>
|
||||||
</td>
|
<input type="checkbox" bind:checked={eAutoStart} />
|
||||||
</tr>
|
</label>
|
||||||
<!-- <tr>
|
</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>
|
<th> Auto Accepting </th>
|
||||||
<td>
|
<td>
|
||||||
<label class={{ "is-dirty": isAutoAcceptModified }}>
|
<label class={{ "is-dirty": isAutoAcceptModified }}>
|
||||||
@@ -362,11 +375,12 @@
|
|||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
</tr> -->
|
</tr> -->
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<button disabled={!isAnyModified} class="button mod-cta" onclick={saveAndApply}>Save and Apply</button>
|
<button disabled={!isAnyModified} class="button mod-cta" onclick={saveAndApply}>Save and Apply</button>
|
||||||
<button disabled={!isAnyModified} class="button" onclick={revert}>Revert changes</button>
|
<button disabled={!isAnyModified} class="button" onclick={revert}>Revert changes</button>
|
||||||
</details>
|
</details>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>Signaling Server Connection</h2>
|
<h2>Signaling Server Connection</h2>
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: 82c68435a1...b1597d6878
@@ -33,7 +33,8 @@ import { ModuleLog } from "./modules/features/ModuleLog.ts";
|
|||||||
import { ModuleObsidianSettings } from "./modules/features/ModuleObsidianSetting.ts";
|
import { ModuleObsidianSettings } from "./modules/features/ModuleObsidianSetting.ts";
|
||||||
import { ModuleRedFlag } from "./modules/coreFeatures/ModuleRedFlag.ts";
|
import { ModuleRedFlag } from "./modules/coreFeatures/ModuleRedFlag.ts";
|
||||||
import { ModuleObsidianMenu } from "./modules/essentialObsidian/ModuleObsidianMenu.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 { StorageAccess } from "./modules/interfaces/StorageAccess.ts";
|
||||||
import type { Confirm } from "./lib/src/interfaces/Confirm.ts";
|
import type { Confirm } from "./lib/src/interfaces/Confirm.ts";
|
||||||
import type { Rebuilder } from "./modules/interfaces/DatabaseRebuilder.ts";
|
import type { Rebuilder } from "./modules/interfaces/DatabaseRebuilder.ts";
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { isMetaEntry } from "../../lib/src/common/types.ts";
|
|||||||
import { isDeletedEntry, isDocContentSame, isLoadedEntry, readAsBlob } from "../../lib/src/common/utils.ts";
|
import { isDeletedEntry, isDocContentSame, isLoadedEntry, readAsBlob } from "../../lib/src/common/utils.ts";
|
||||||
import { countCompromisedChunks } from "../../lib/src/pouchdb/negotiation.ts";
|
import { countCompromisedChunks } from "../../lib/src/pouchdb/negotiation.ts";
|
||||||
import type { LiveSyncCore } from "../../main.ts";
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
import { SetupManager } from "../features/ModuleSetupObsidian.ts";
|
import { SetupManager } from "../features/SetupManager.ts";
|
||||||
|
|
||||||
type ErrorInfo = {
|
type ErrorInfo = {
|
||||||
path: string;
|
path: string;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
DEFAULT_SETTINGS,
|
DEFAULT_SETTINGS,
|
||||||
type ObsidianLiveSyncSettings,
|
type ObsidianLiveSyncSettings,
|
||||||
SALT_OF_PASSPHRASE,
|
SALT_OF_PASSPHRASE,
|
||||||
|
SETTING_KEY_P2P_DEVICE_NAME,
|
||||||
} from "../../lib/src/common/types";
|
} from "../../lib/src/common/types";
|
||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger";
|
||||||
import { $msg, setLang } from "../../lib/src/common/i18n.ts";
|
import { $msg, setLang } from "../../lib/src/common/i18n.ts";
|
||||||
@@ -111,6 +112,11 @@ export class ModuleObsidianSettings extends AbstractObsidianModule {
|
|||||||
this.services.setting.saveDeviceAndVaultName();
|
this.services.setting.saveDeviceAndVaultName();
|
||||||
const settings = { ...this.settings };
|
const settings = { ...this.settings };
|
||||||
settings.deviceAndVaultName = "";
|
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))) {
|
if (this.usedPassphrase == "" && !(await this.getPassphrase(settings))) {
|
||||||
this._log("Failed to retrieve passphrase. data.json contains unencrypted items!", LOG_LEVEL_NOTICE);
|
this._log("Failed to retrieve passphrase. data.json contains unencrypted items!", LOG_LEVEL_NOTICE);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
import {
|
import { type ObsidianLiveSyncSettings, LOG_LEVEL_NOTICE } from "../../lib/src/common/types.ts";
|
||||||
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 { configURIBase } from "../../common/types.ts";
|
import { configURIBase } from "../../common/types.ts";
|
||||||
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.js";
|
// 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 {
|
import {
|
||||||
EVENT_REQUEST_COPY_SETUP_URI,
|
EVENT_REQUEST_COPY_SETUP_URI,
|
||||||
|
EVENT_REQUEST_OPEN_P2P_SETTINGS,
|
||||||
EVENT_REQUEST_OPEN_SETUP_URI,
|
EVENT_REQUEST_OPEN_SETUP_URI,
|
||||||
EVENT_REQUEST_SHOW_SETUP_QR,
|
EVENT_REQUEST_SHOW_SETUP_QR,
|
||||||
eventHub,
|
eventHub,
|
||||||
@@ -21,268 +13,13 @@ import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
|||||||
import { $msg } from "../../lib/src/common/i18n.ts";
|
import { $msg } from "../../lib/src/common/i18n.ts";
|
||||||
// import { performDoctorConsultation, RebuildOptions } from "@/lib/src/common/configForDoc.ts";
|
// import { performDoctorConsultation, RebuildOptions } from "@/lib/src/common/configForDoc.ts";
|
||||||
import type { LiveSyncCore } from "../../main.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 {
|
import {
|
||||||
decodeSettingsFromQRCodeData,
|
|
||||||
encodeQR,
|
encodeQR,
|
||||||
encodeSettingsToQRCodeData,
|
encodeSettingsToQRCodeData,
|
||||||
encodeSettingsToSetupURI,
|
encodeSettingsToSetupURI,
|
||||||
OutputFormat,
|
OutputFormat,
|
||||||
} from "../../lib/src/API/processSetting.ts";
|
} from "../../lib/src/API/processSetting.ts";
|
||||||
// import type ObsidianLiveSyncPlugin from "../../main.ts";
|
import { SetupManager, UserMode } from "./SetupManager.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ModuleSetupObsidian extends AbstractObsidianModule {
|
export class ModuleSetupObsidian extends AbstractObsidianModule {
|
||||||
private _setupManager!: SetupManager;
|
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_OPEN_SETUP_URI, () => fireAndForget(() => this.command_openSetupURI()));
|
||||||
eventHub.onEvent(EVENT_REQUEST_COPY_SETUP_URI, () => fireAndForget(() => this.command_copySetupURI()));
|
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_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);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
async encodeQR() {
|
async encodeQR() {
|
||||||
@@ -380,6 +122,8 @@ export class ModuleSetupObsidian extends AbstractObsidianModule {
|
|||||||
await this._setupManager.onUseSetupURI(UserMode.Unknown);
|
await this._setupManager.onUseSetupURI(UserMode.Unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Where to implement these?
|
||||||
|
|
||||||
// async askSyncWithRemoteConfig(tryingSettings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
// async askSyncWithRemoteConfig(tryingSettings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
||||||
// const buttons = {
|
// const buttons = {
|
||||||
// fetch: $msg("Setup.FetchRemoteConf.Buttons.Fetch"),
|
// 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 {
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,77 +4,12 @@
|
|||||||
* Mostly used in the Setting Dialogue
|
* Mostly used in the Setting Dialogue
|
||||||
*/
|
*/
|
||||||
import { type SveltePanelProps } from "./SveltePanel";
|
import { type SveltePanelProps } from "./SveltePanel";
|
||||||
|
import InfoTable from "@lib/ui/components/InfoTable.svelte";
|
||||||
type Props = SveltePanelProps<{
|
type Props = SveltePanelProps<{
|
||||||
info: Record<string, any>;
|
info: Record<string, any>;
|
||||||
}>;
|
}>;
|
||||||
const { port }: Props = $props();
|
const { port }: Props = $props();
|
||||||
const info = $derived.by(() => $port?.info ?? {});
|
const info = $derived.by(() => $port?.info ?? {});
|
||||||
const infoEntries = $derived(Object.entries(info ?? {}));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="info-panel">
|
<InfoTable {info} />
|
||||||
<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>
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { $msg } from "../../../lib/src/common/i18n.ts";
|
|||||||
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
|
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
|
||||||
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
||||||
import type { PageFunctions } from "./SettingPane.ts";
|
import type { PageFunctions } from "./SettingPane.ts";
|
||||||
import { visibleOnly } from "./SettingPane.ts";
|
// import { visibleOnly } from "./SettingPane.ts";
|
||||||
import InfoPanel from "./InfoPanel.svelte";
|
import InfoPanel from "./InfoPanel.svelte";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import { SveltePanel } from "./SveltePanel.ts";
|
import { SveltePanel } from "./SveltePanel.ts";
|
||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
getE2EEConfigSummary,
|
getE2EEConfigSummary,
|
||||||
} from "./settingUtils.ts";
|
} from "./settingUtils.ts";
|
||||||
import { SETTING_KEY_P2P_DEVICE_NAME } from "../../../lib/src/common/types.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";
|
import { OnDialogSettingsDefault, type AllSettings } from "./settingConstants.ts";
|
||||||
|
|
||||||
function getSettingsFromEditingSettings(editingSettings: AllSettings): ObsidianLiveSyncSettings {
|
function getSettingsFromEditingSettings(editingSettings: AllSettings): ObsidianLiveSyncSettings {
|
||||||
@@ -30,6 +30,14 @@ function getSettingsFromEditingSettings(editingSettings: AllSettings): ObsidianL
|
|||||||
}
|
}
|
||||||
return workObj;
|
return workObj;
|
||||||
}
|
}
|
||||||
|
const toggleActiveSyncClass = (el: HTMLElement, isActive: () => boolean) => {
|
||||||
|
if (isActive()) {
|
||||||
|
el.addClass("active-pane");
|
||||||
|
} else {
|
||||||
|
el.removeClass("active-pane");
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
export function paneRemoteConfig(
|
export function paneRemoteConfig(
|
||||||
this: ObsidianLiveSyncSettingTab,
|
this: ObsidianLiveSyncSettingTab,
|
||||||
@@ -56,39 +64,46 @@ export function paneRemoteConfig(
|
|||||||
void addPanel(paneEl, "E2EE Configuration", () => {}).then((paneEl) => {
|
void addPanel(paneEl, "E2EE Configuration", () => {}).then((paneEl) => {
|
||||||
new SveltePanel(InfoPanel, paneEl, E2EESummaryWritable);
|
new SveltePanel(InfoPanel, paneEl, E2EESummaryWritable);
|
||||||
const setupButton = new Setting(paneEl).setName("Configure E2EE");
|
const setupButton = new Setting(paneEl).setName("Configure E2EE");
|
||||||
setupButton.addButton((button) =>
|
setupButton
|
||||||
button
|
.addButton((button) =>
|
||||||
.onClick(async () => {
|
button
|
||||||
const setupManager = this.plugin.getModule(SetupManager);
|
.onClick(async () => {
|
||||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
const setupManager = this.plugin.getModule(SetupManager);
|
||||||
await setupManager.onlyE2EEConfiguration(UserMode.Update, originalSettings);
|
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||||
updateE2EESummary();
|
await setupManager.onlyE2EEConfiguration(UserMode.Update, originalSettings);
|
||||||
})
|
updateE2EESummary();
|
||||||
.setButtonText("Configure")
|
})
|
||||||
.setWarning()
|
.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();
|
updateE2EESummary();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
void addPanel(
|
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleRemoteServer"), () => {}).then((paneEl) => {
|
||||||
paneEl,
|
const setting = new Setting(paneEl).setName("Active Remote Configuration");
|
||||||
$msg("obsidianLiveSyncSettingTab.titleRemoteServer"),
|
|
||||||
() => {},
|
|
||||||
() => ({ classes: this.editingSettings.remoteType === REMOTE_COUCHDB ? ["active-sync"] : [] })
|
|
||||||
).then((paneEl) => {
|
|
||||||
const nSetting = new Setting(paneEl).setName("Active Remote Configuration");
|
|
||||||
|
|
||||||
const el = nSetting.controlEl.createDiv({});
|
const el = setting.controlEl.createDiv({});
|
||||||
el.setText(`${remoteNameMap[this.editingSettings.remoteType] || " - "}`);
|
el.setText(`${remoteNameMap[this.editingSettings.remoteType] || " - "}`);
|
||||||
nSetting.addButton((button) =>
|
setting.addButton((button) =>
|
||||||
button
|
button
|
||||||
.setButtonText("Change Remote and Setup")
|
.setButtonText("Change Remote and Setup")
|
||||||
.setCta()
|
.setCta()
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
const setupManager = this.plugin.getModule(SetupManager);
|
const setupManager = this.plugin.getModule(SetupManager);
|
||||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
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),
|
info: getCouchDBConfigSummary(this.editingSettings),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
void addPanel(
|
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleCouchDB"), () => {}).then((paneEl) => {
|
||||||
paneEl,
|
|
||||||
$msg("obsidianLiveSyncSettingTab.titleCouchDB"),
|
|
||||||
() => {},
|
|
||||||
() => ({ classes: this.editingSettings.remoteType === REMOTE_COUCHDB ? ["active-sync"] : [] })
|
|
||||||
).then((paneEl) => {
|
|
||||||
new SveltePanel(InfoPanel, paneEl, summaryWritable);
|
new SveltePanel(InfoPanel, paneEl, summaryWritable);
|
||||||
const setupButton = new Setting(paneEl).setName("Configure Remote");
|
const setupButton = new Setting(paneEl).setName("Configure Remote");
|
||||||
setupButton.addButton((button) =>
|
setupButton
|
||||||
button
|
.addButton((button) =>
|
||||||
.setButtonText("Configure")
|
button
|
||||||
.setCta()
|
.setButtonText("Configure")
|
||||||
.onClick(async () => {
|
.setCta()
|
||||||
const setupManager = this.plugin.getModule(SetupManager);
|
.onClick(async () => {
|
||||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
const setupManager = this.plugin.getModule(SetupManager);
|
||||||
await setupManager.onCouchDBManualSetup(
|
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||||
UserMode.Update,
|
await setupManager.onCouchDBManualSetup(
|
||||||
originalSettings,
|
UserMode.Update,
|
||||||
this.editingSettings.remoteType === REMOTE_COUCHDB
|
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),
|
info: getBucketConfigSummary(this.editingSettings),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
void addPanel(
|
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleMinioS3R2"), () => {}).then((paneEl) => {
|
||||||
paneEl,
|
|
||||||
$msg("obsidianLiveSyncSettingTab.titleMinioS3R2"),
|
|
||||||
() => {},
|
|
||||||
() => ({ classes: this.editingSettings.remoteType === REMOTE_MINIO ? ["active-sync"] : [] })
|
|
||||||
).then((paneEl) => {
|
|
||||||
new SveltePanel(InfoPanel, paneEl, summaryWritable);
|
new SveltePanel(InfoPanel, paneEl, summaryWritable);
|
||||||
const setupButton = new Setting(paneEl).setName("Configure Remote");
|
const setupButton = new Setting(paneEl).setName("Configure Remote");
|
||||||
setupButton.addButton((button) =>
|
setupButton
|
||||||
button
|
.addButton((button) =>
|
||||||
.setButtonText("Configure")
|
button
|
||||||
.setCta()
|
.setButtonText("Configure")
|
||||||
.onClick(async () => {
|
.setCta()
|
||||||
const setupManager = this.plugin.getModule(SetupManager);
|
.onClick(async () => {
|
||||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
const setupManager = this.plugin.getModule(SetupManager);
|
||||||
await setupManager.onBucketManualSetup(
|
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||||
UserMode.Update,
|
await setupManager.onBucketManualSetup(
|
||||||
originalSettings,
|
UserMode.Update,
|
||||||
this.editingSettings.remoteType === REMOTE_MINIO
|
originalSettings,
|
||||||
);
|
this.editingSettings.remoteType === REMOTE_MINIO
|
||||||
//TODO
|
);
|
||||||
updateSummary();
|
//TODO
|
||||||
})
|
updateSummary();
|
||||||
);
|
})
|
||||||
|
)
|
||||||
|
.addOnUpdate(() =>
|
||||||
|
toggleActiveSyncClass(paneEl, () => this.editingSettings.remoteType === REMOTE_MINIO)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -180,54 +193,35 @@ export function paneRemoteConfig(
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
void addPanel(
|
void addPanel(paneEl, "Peer-to-Peer Synchronisation", () => {}).then((paneEl) => {
|
||||||
paneEl,
|
|
||||||
"Peer-to-Peer Synchronisation",
|
|
||||||
() => {},
|
|
||||||
() => ({ classes: this.editingSettings.remoteType === REMOTE_P2P ? ["active-sync"] : [] })
|
|
||||||
).then((paneEl) => {
|
|
||||||
new SveltePanel(InfoPanel, paneEl, summaryWritable);
|
new SveltePanel(InfoPanel, paneEl, summaryWritable);
|
||||||
const setupButton = new Setting(paneEl).setName("Configure Remote");
|
const setupButton = new Setting(paneEl).setName("Configure Remote");
|
||||||
setupButton.addButton((button) =>
|
setupButton
|
||||||
button
|
.addButton((button) =>
|
||||||
.setButtonText("Configure")
|
button
|
||||||
.setCta()
|
.setButtonText("Configure")
|
||||||
.onClick(async () => {
|
.setCta()
|
||||||
const setupManager = this.plugin.getModule(SetupManager);
|
.onClick(async () => {
|
||||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
const setupManager = this.plugin.getModule(SetupManager);
|
||||||
await setupManager.onP2PManualSetup(
|
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
||||||
UserMode.Update,
|
await setupManager.onP2PManualSetup(
|
||||||
originalSettings,
|
UserMode.Update,
|
||||||
this.editingSettings.remoteType === REMOTE_P2P
|
originalSettings,
|
||||||
);
|
this.editingSettings.remoteType === REMOTE_P2P
|
||||||
//TODO
|
);
|
||||||
updateSummary();
|
//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)
|
// new Setting(paneEl)
|
||||||
// .setDesc("Generate ES256 Keypair for testing")
|
// .setDesc("Generate ES256 Keypair for testing")
|
||||||
// .addButton((button) =>
|
// .addButton((button) =>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import type { PageFunctions } from "./SettingPane.ts";
|
|||||||
import { visibleOnly } from "./SettingPane.ts";
|
import { visibleOnly } from "./SettingPane.ts";
|
||||||
import { DEFAULT_SETTINGS } from "../../../lib/src/common/types.ts";
|
import { DEFAULT_SETTINGS } from "../../../lib/src/common/types.ts";
|
||||||
import { request } from "obsidian";
|
import { request } from "obsidian";
|
||||||
import { SetupManager, UserMode } from "../ModuleSetupObsidian.ts";
|
import { SetupManager, UserMode } from "../SetupManager.ts";
|
||||||
export function paneSetup(
|
export function paneSetup(
|
||||||
this: ObsidianLiveSyncSettingTab,
|
this: ObsidianLiveSyncSettingTab,
|
||||||
paneEl: HTMLElement,
|
paneEl: HTMLElement,
|
||||||
@@ -36,7 +36,7 @@ export function paneSetup(
|
|||||||
.addButton((text) => {
|
.addButton((text) => {
|
||||||
text.setButtonText("Rerun Wizard").onClick(async () => {
|
text.setButtonText("Rerun Wizard").onClick(async () => {
|
||||||
const setupManager = this.plugin.getModule(SetupManager);
|
const setupManager = this.plugin.getModule(SetupManager);
|
||||||
await setupManager.onBoard(UserMode.ExistingUser);
|
await setupManager.onOnboard(UserMode.ExistingUser);
|
||||||
// await this.plugin.moduleSetupObsidian.onBoardingWizard(true);
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,12 +32,8 @@
|
|||||||
|
|
||||||
const context = getObsidianDialogContext();
|
const context = getObsidianDialogContext();
|
||||||
let error = $state("");
|
let error = $state("");
|
||||||
let devicePeerId = $state("");
|
|
||||||
const TYPE_CANCELLED = "cancelled";
|
const TYPE_CANCELLED = "cancelled";
|
||||||
type SettingInfo = {
|
type SettingInfo = P2PConnectionInfo;
|
||||||
info: P2PConnectionInfo;
|
|
||||||
devicePeerId: string;
|
|
||||||
};
|
|
||||||
type ResultType = typeof TYPE_CANCELLED | SettingInfo;
|
type ResultType = typeof TYPE_CANCELLED | SettingInfo;
|
||||||
type Props = GuestDialogProps<ResultType, P2PSyncSetting>;
|
type Props = GuestDialogProps<ResultType, P2PSyncSetting>;
|
||||||
|
|
||||||
@@ -49,7 +45,11 @@
|
|||||||
copyTo(initialData, syncSetting);
|
copyTo(initialData, syncSetting);
|
||||||
}
|
}
|
||||||
if (context.services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME)) {
|
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 +103,7 @@
|
|||||||
confirm: context.plugin.confirm,
|
confirm: context.plugin.confirm,
|
||||||
db: dummyPouch,
|
db: dummyPouch,
|
||||||
simpleStore: store,
|
simpleStore: store,
|
||||||
deviceName: devicePeerId || "unnamed-device",
|
deviceName: syncSetting.P2P_DevicePeerName || "unnamed-device",
|
||||||
platform: "setup-wizard",
|
platform: "setup-wizard",
|
||||||
};
|
};
|
||||||
const replicator = new TrysteroReplicator(env);
|
const replicator = new TrysteroReplicator(env);
|
||||||
@@ -164,10 +164,7 @@
|
|||||||
error = (await checkConnection()) || "";
|
error = (await checkConnection()) || "";
|
||||||
if (!error) {
|
if (!error) {
|
||||||
const setting = generateSetting();
|
const setting = generateSetting();
|
||||||
setResult({
|
setResult(pickP2PSyncSettings(setting));
|
||||||
info: pickP2PSyncSettings(setting),
|
|
||||||
devicePeerId: devicePeerId,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -177,10 +174,7 @@
|
|||||||
}
|
}
|
||||||
function commit() {
|
function commit() {
|
||||||
const setting = pickP2PSyncSettings(generateSetting());
|
const setting = pickP2PSyncSettings(generateSetting());
|
||||||
setResult({
|
setResult(setting);
|
||||||
info: setting,
|
|
||||||
devicePeerId: devicePeerId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
function cancel() {
|
function cancel() {
|
||||||
setResult(TYPE_CANCELLED);
|
setResult(TYPE_CANCELLED);
|
||||||
@@ -190,13 +184,16 @@
|
|||||||
syncSetting.P2P_relays.trim() !== "" &&
|
syncSetting.P2P_relays.trim() !== "" &&
|
||||||
syncSetting.P2P_roomID.trim() !== "" &&
|
syncSetting.P2P_roomID.trim() !== "" &&
|
||||||
syncSetting.P2P_passphrase.trim() !== "" &&
|
syncSetting.P2P_passphrase.trim() !== "" &&
|
||||||
devicePeerId.trim() !== ""
|
(syncSetting.P2P_DevicePeerName ?? "").trim() !== ""
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DialogHeader title="P2P Configuration" />
|
<DialogHeader title="P2P Configuration" />
|
||||||
<Guidance>Please enter the Peer-to-Peer Synchronisation information below.</Guidance>
|
<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">
|
<InputRow label="Relay URL">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -237,10 +234,23 @@
|
|||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="off"
|
autocapitalize="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
bind:value={devicePeerId}
|
bind:value={syncSetting.P2P_DevicePeerName}
|
||||||
/>
|
/>
|
||||||
</InputRow>
|
</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>
|
||||||
<InfoNote error visible={error !== ""}>
|
<InfoNote error visible={error !== ""}>
|
||||||
{error}
|
{error}
|
||||||
</InfoNote>
|
</InfoNote>
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ import { ObsidianUIService } from "./ObsidianUIService.ts";
|
|||||||
// All Services will be migrated to be based on Plain Services, not Injectable Services.
|
// All Services will be migrated to be based on Plain Services, not Injectable Services.
|
||||||
// This is a migration step.
|
// 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 ObsidianPathService extends InjectablePathService {}
|
||||||
export class ObsidianDatabaseService extends InjectableDatabaseService {}
|
export class ObsidianDatabaseService extends InjectableDatabaseService {}
|
||||||
|
|
||||||
|
|||||||
@@ -393,13 +393,13 @@ span.ls-mark-cr::after {
|
|||||||
div.workspace-leaf-content[data-type=bases] .livesync-status {
|
div.workspace-leaf-content[data-type=bases] .livesync-status {
|
||||||
top: calc(var(--bases-header-height) + var(--header-height));
|
top: calc(var(--bases-header-height) + var(--header-height));
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
padding-right:18px;
|
padding-right: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-mobile div.workspace-leaf-content[data-type=bases] .livesync-status {
|
.is-mobile div.workspace-leaf-content[data-type=bases] .livesync-status {
|
||||||
top: calc(var(--bases-header-height) + var(--view-header-height));
|
top: calc(var(--bases-header-height) + var(--view-header-height));
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
padding-right:18px;
|
padding-right: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.livesync-status div {
|
.livesync-status div {
|
||||||
@@ -444,6 +444,10 @@ div.workspace-leaf-content[data-type=bases] .livesync-status {
|
|||||||
padding: 0.5em 1.0em;
|
padding: 0.5em 1.0em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.active-pane .sls-setting-panel-title {
|
||||||
|
border: 1px solid var(--interactive-accent);
|
||||||
|
}
|
||||||
|
|
||||||
.sls-dialogue-note-wrapper {
|
.sls-dialogue-note-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"importHelpers": false,
|
"importHelpers": false,
|
||||||
"alwaysStrict": true,
|
"alwaysStrict": true,
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"lib": ["es2018", "DOM", "ES5", "ES6", "ES7", "es2019.array", "ES2021.WeakRef", "ES2020.BigInt", "ESNext.Intl"],
|
"lib": ["es2018", "DOM", "ES5", "ES6", "ES7", "es2019.array", "ES2021.WeakRef", "ES2020.BigInt", "ESNext.Intl"],
|
||||||
"strictBindCallApply": true,
|
"strictBindCallApply": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user