Complete Commonlib rc.6 integration and setup workflows

This commit is contained in:
vorotamoroz
2026-07-21 15:00:28 +00:00
parent e005f9eb13
commit 1ef8c88139
150 changed files with 31041 additions and 260 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ import { type EntryDoc, type RemoteType } from "@vrtmrz/livesync-commonlib/compa
import { scheduleTask } from "octagonal-wheels/concurrency/task";
import { EVENT_FILE_SAVED, EVENT_SETTING_SAVED, eventHub } from "@/common/events";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import type { LiveSyncCore } from "@/main";
import { ReplicateResultProcessor } from "./ReplicateResultProcessor";
import { UnresolvedErrorManager } from "@vrtmrz/livesync-commonlib/compat/services/base/UnresolvedErrorManager";
@@ -13,7 +13,7 @@ import {
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import { escapeMarkdownValue } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import { AbstractModule } from "@/modules/AbstractModule.ts";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import type { InjectableServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableServiceHub";
import type { LiveSyncCore } from "@/main.ts";
import { REMOTE_P2P } from "@vrtmrz/livesync-commonlib/compat/common/models/setting.const";
+1 -1
View File
@@ -13,7 +13,7 @@ import {
eventHub,
} from "@/common/events.ts";
import { AbstractModule } from "@/modules/AbstractModule.ts";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import { performDoctorConsultation, RebuildOptions } from "@vrtmrz/livesync-commonlib/compat/common/configForDoc";
import { isValidPath } from "@/common/utils.ts";
import { isMetaEntry } from "@vrtmrz/livesync-commonlib/compat/common/types";
@@ -1,7 +1,7 @@
import { type Editor, type MarkdownFileInfo, type MarkdownView } from "@/deps.ts";
import { addIcon } from "@/deps.ts";
import { type FilePathWithPrefix } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import type { LiveSyncCore } from "@/main.ts";
import { AbstractModule } from "@/modules/AbstractModule.ts";
// Obsidian specific menu commands.
+1 -1
View File
@@ -1,5 +1,5 @@
import { delay } from "octagonal-wheels/promises";
import { __onMissingTranslation } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { __onMissingTranslation } from "@/common/translation";
import { AbstractObsidianModule } from "@/modules/AbstractObsidianModule.ts";
import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
// import { enableTestFunction } from "./devUtil/testUtils.ts";
+1 -1
View File
@@ -3,7 +3,7 @@
import { logMessages } from "@vrtmrz/livesync-commonlib/compat/mock_and_interop/stores";
import { reactive, type ReactiveInstance } from "octagonal-wheels/dataobject/reactive";
import { Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
import { $msg as msg, currentLang as lang } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg as msg, currentLang as lang } from "@/common/translation";
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
let unsubscribe: () => void;
+1 -1
View File
@@ -2,7 +2,7 @@ import { WorkspaceLeaf } from "@/deps.ts";
import LogPaneComponent from "./LogPane.svelte";
import type ObsidianLiveSyncPlugin from "@/main.ts";
import { SvelteItemView } from "@/common/SvelteItemView.ts";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import { mount } from "svelte";
export const VIEW_TYPE_LOG = "log-log";
//Log view
+1 -1
View File
@@ -29,7 +29,7 @@ import { addIcon, debounce, normalizePath, Notice, stringifyYaml, type Workspace
import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger";
import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts";
import { serialized } from "octagonal-wheels/concurrency/lock";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import { P2PLogCollector } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PLogCollector";
import {
REMOTE_REQUEST_ACTIVITY_MINIMUM_VISIBLE_MS,
@@ -18,7 +18,7 @@ import {
type AllNumericItemKey,
type AllBooleanItemKey,
} from "./settingConstants.ts";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import { wrapMemo, type AutoWireOption, type OnUpdateResult } from "./SettingPane.ts";
export class LiveSyncSetting extends Setting {
@@ -30,7 +30,7 @@ import {
type OnDialogSettings,
getConfName,
} from "./settingConstants.ts";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
import { fireAndForget, yieldNextAnimationFrame } from "octagonal-wheels/promises";
import { EVENT_REQUEST_RELOAD_SETTING_TAB, eventHub } from "@/common/events.ts";
@@ -1,5 +1,5 @@
import { $msg, $t } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@vrtmrz/livesync-commonlib/compat/common/rosetta";
import { $msg, $t } from "@/common/translation";
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@/common/rosetta";
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
import type { PageFunctions } from "./SettingPane.ts";
@@ -11,7 +11,7 @@ import {
import { createBlob, getFileRegExp, isDocContentSame, readAsBlob } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import { Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
import { addPrefix, shouldBeIgnored, stripAllPrefixes } from "@vrtmrz/livesync-commonlib/compat/string_and_binary/path";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
import {
@@ -8,7 +8,7 @@ import {
LOG_LEVEL_VERBOSE,
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import { Menu, type ButtonComponent } from "@/deps.ts";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
import type { PageFunctions } from "./SettingPane.ts";
@@ -1,5 +1,5 @@
import { MarkdownRenderer } from "@/deps.ts";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
import { fireAndForget } from "octagonal-wheels/promises";
import {
@@ -1,6 +1,6 @@
import { type ObsidianLiveSyncSettings, LOG_LEVEL_NOTICE, REMOTE_COUCHDB, LEVEL_ADVANCED } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
import { EVENT_REQUEST_COPY_SETUP_URI, eventHub } from "@/common/events.ts";
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
@@ -1,4 +1,4 @@
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import { LEVEL_ADVANCED, LEVEL_EDGE_CASE, LEVEL_POWER_USER, type ConfigLevel } from "@vrtmrz/livesync-commonlib/compat/common/types";
import type { AllSettingItemKey, AllSettings } from "./settingConstants";
@@ -1,5 +1,5 @@
import { requestToCouchDBWithCredentials } from "@/common/utils";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import {
LOG_LEVEL_INFO,
LOG_LEVEL_NOTICE,
+5 -6
View File
@@ -43,6 +43,7 @@ import {
applySettingsAndFetchOnActivation,
applySettingsWithScheduledInitialisation,
} from "@/serviceFeatures/setupObsidian/setupActivationLifecycle.ts";
import { isP2PMainRemote } from "@/common/remoteConfiguration.ts";
function copySettingsForRemoteProfileUpdate(settings: ObsidianLiveSyncSettings): ObsidianLiveSyncSettings {
return {
@@ -368,10 +369,7 @@ export class SetupManager extends AbstractModule {
userMode = UserMode.ExistingUser;
} else if (userModeResult === "compatible-existing-user") {
extra();
const applied = await this.applySettingAndScheduleFetchOnActivation(
newConf,
UserMode.ExistingUser
);
const applied = await this.applySettingAndScheduleFetchOnActivation(newConf, UserMode.ExistingUser);
if (applied) this._log("Settings from wizard applied.", LOG_LEVEL_NOTICE);
return applied;
} else if (userModeResult === "cancelled") {
@@ -382,8 +380,9 @@ export class SetupManager extends AbstractModule {
}
const component = userMode === UserMode.NewUser ? OutroNewUser : OutroExistingUser;
const confirm = await this.dialogManager.openWithExplicitCancel<
OutroNewUserResultType | OutroExistingUserResultType
>(component);
OutroNewUserResultType | OutroExistingUserResultType,
{ isP2P: boolean }
>(component, { isP2P: isP2PMainRemote(newConf) });
if (confirm === "cancelled") {
this._log("User cancelled applying settings from wizard..", LOG_LEVEL_NOTICE);
return false;
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import {
DEFAULT_SETTINGS,
REMOTE_COUCHDB,
REMOTE_P2P,
type ObsidianLiveSyncSettings,
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import { SettingService } from "@vrtmrz/livesync-commonlib/compat/services/base/SettingService";
@@ -194,6 +195,37 @@ describe("SetupManager", () => {
expect(setting.currentSettings().isConfigured).toBe(true);
});
it("identifies P2P when opening the new-user initialisation confirmation", async () => {
const { manager, setting, dialogManager } = createSetupManager();
setting.settings = { ...setting.currentSettings(), isConfigured: false };
dialogManager.openWithExplicitCancel.mockResolvedValueOnce(true);
const p2pProfileId = "p2p-profile";
await manager.onConfirmApplySettingsFromWizard(
{
...setting.currentSettings(),
isConfigured: true,
// Imported profile settings can still carry the previous compatibility field
// until the selected profile is projected by the setting lifecycle.
remoteType: REMOTE_COUCHDB,
activeConfigurationId: p2pProfileId,
remoteConfigurations: {
[p2pProfileId]: {
id: p2pProfileId,
name: "P2P room",
uri: "sls+p2p://:secret@team-room?relays=wss%3A%2F%2Frelay.example",
isEncrypted: false,
},
},
},
UserMode.NewUser
);
expect(dialogManager.openWithExplicitCancel).toHaveBeenCalledWith(expect.anything(), {
isP2P: true,
});
});
it("reserves Fetch when compatible imported settings activate an unconfigured device", async () => {
const { manager, setting, dialogManager, core } = createSetupManager();
setting.settings = { ...setting.currentSettings(), isConfigured: false };
@@ -5,33 +5,59 @@
import Question from "@/modules/services/LiveSyncUI/components/Question.svelte";
import Instruction from "@/modules/services/LiveSyncUI/components/Instruction.svelte";
import UserDecisions from "@/modules/services/LiveSyncUI/components/UserDecisions.svelte";
import { $msg as msg } from "@/common/translation";
import { TYPE_APPLY, TYPE_CANCELLED, type OutroNewUserResultType } from "./setupDialogTypes";
type Props = {
setResult: (result: OutroNewUserResultType) => void;
getInitialData?: () => { isP2P?: boolean } | undefined;
};
const { setResult }: Props = $props();
const { setResult, getInitialData }: Props = $props();
const isP2P = $derived(getInitialData?.()?.isP2P === true);
// let userType = $state<OutroNewUserResultType>(TYPE_CANCELLED);
</script>
<DialogHeader title="Setup Complete: Preparing to Initialise Server" />
<Guidance>
<p>
The connection to the server has been configured successfully. As the next step, <strong
>the synchronisation data on the server will be built based on the current data on this device.</strong
>
</p>
<p>
<strong>IMPORTANT</strong>
<br />
After restarting, the data on this device will be uploaded to the server as the 'master copy'. Please be aware that
any unintended data currently on the server will be completely overwritten.
</p>
</Guidance>
<Instruction>
<Question>Please select the button below to restart and proceed to the final confirmation.</Question>
</Instruction>
<UserDecisions>
<Decision title="Restart and Initialise Server" important={true} commit={() => setResult(TYPE_APPLY)} />
<Decision title="No, please take me back" commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>
{#if isP2P}
<DialogHeader title={msg("Ui.SetupWizard.OutroNewP2PUser.Title")} />
<Guidance>
<p>{msg("Ui.SetupWizard.OutroNewP2PUser.GuidancePrimary")}</p>
<p>
<strong>{msg("Ui.SetupWizard.OutroNewP2PUser.Important")}</strong>
<br />
{msg("Ui.SetupWizard.OutroNewP2PUser.GuidanceNotice")}
</p>
</Guidance>
<Instruction>
<Question>{msg("Ui.SetupWizard.OutroNewP2PUser.Question")}</Question>
</Instruction>
<UserDecisions>
<Decision
title={msg("Ui.SetupWizard.OutroNewP2PUser.Proceed")}
important={true}
commit={() => setResult(TYPE_APPLY)}
/>
<Decision title="No, please take me back" commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>
{:else}
<DialogHeader title="Setup Complete: Preparing to Initialise Server" />
<Guidance>
<p>
The connection to the server has been configured successfully. As the next step, <strong
>the synchronisation data on the server will be built based on the current data on this device.</strong
>
</p>
<p>
<strong>IMPORTANT</strong>
<br />
After restarting, the data on this device will be uploaded to the server as the 'master copy'. Please be aware
that any unintended data currently on the server will be completely overwritten.
</p>
</Guidance>
<Instruction>
<Question>Please select the button below to restart and proceed to the final confirmation.</Question>
</Instruction>
<UserDecisions>
<Decision title="Restart and Initialise Server" important={true} commit={() => setResult(TYPE_APPLY)} />
<Decision title="No, please take me back" commit={() => setResult(TYPE_CANCELLED)} />
</UserDecisions>
{/if}
@@ -10,6 +10,7 @@
import InfoNote from "@/modules/services/LiveSyncUI/components/InfoNote.svelte";
import ExtraItems from "@/modules/services/LiveSyncUI/components/ExtraItems.svelte";
import Check from "@/modules/services/LiveSyncUI/components/Check.svelte";
import { $msg as msg } from "@/common/translation";
import {
TYPE_CANCEL,
TYPE_BACKUP_DONE,
@@ -21,20 +22,19 @@
type Props = {
setResult: (result: RebuildEverythingResult) => void;
getInitialData?: () => { isP2P?: boolean } | undefined;
};
const { setResult }: Props = $props();
const { setResult, getInitialData }: Props = $props();
const isP2P = $derived(getInitialData?.()?.isP2P === true);
let backupType = $state<ResultTypeBackup>(TYPE_CANCEL);
let confirmationCheck1 = $state(false);
let confirmationCheck2 = $state(false);
let confirmationCheck3 = $state(false);
const canProceed = $derived.by(() => {
return (
(backupType === TYPE_BACKUP_DONE || backupType === TYPE_BACKUP_SKIPPED) &&
confirmationCheck1 &&
confirmationCheck2 &&
confirmationCheck3
);
const backupConfirmed = backupType === TYPE_BACKUP_DONE || backupType === TYPE_BACKUP_SKIPPED;
if (isP2P) return backupConfirmed && confirmationCheck1;
return backupConfirmed && confirmationCheck1 && confirmationCheck2 && confirmationCheck3;
});
let preventFetchingConfig = $state(false);
@@ -48,33 +48,44 @@
}
</script>
<DialogHeader title="Final Confirmation: Overwrite Server Data with This Device's Files" />
<Guidance
>This procedure will first delete all existing synchronisation data from the server. Following this, the server data
will be completely rebuilt, using the current state of your Vault on this device (including its local database) as
<strong>the single, authoritative master copy</strong>.</Guidance
>
<InfoNote>
You should perform this operation only in exceptional circumstances, such as when the server data is completely
corrupted, when changes on all other devices are no longer needed, or when the database size has become unusually
large in comparison to the Vault size.
</InfoNote>
<Guidance important title="⚠️ Please Confirm the Following">
<Check
title="I understand that all changes made on other smartphones or computers possibly could be lost."
bind:value={confirmationCheck1}
{#if isP2P}
<DialogHeader title={msg("Ui.SetupWizard.RebuildEverythingP2P.Title")} />
<Guidance>{msg("Ui.SetupWizard.RebuildEverythingP2P.Guidance")}</Guidance>
<InfoNote>{msg("Ui.SetupWizard.RebuildEverythingP2P.Note")}</InfoNote>
<Guidance important title={msg("Ui.SetupWizard.RebuildEverythingP2P.ConfirmTitle")}>
<Check title={msg("Ui.SetupWizard.RebuildEverythingP2P.ConfirmLocalReset")} bind:value={confirmationCheck1}>
<InfoNote>{msg("Ui.SetupWizard.RebuildEverythingP2P.ConfirmLocalResetNote")}</InfoNote>
</Check>
</Guidance>
{:else}
<DialogHeader title="Final Confirmation: Overwrite Server Data with This Device's Files" />
<Guidance
>This procedure will first delete all existing synchronisation data from the server. Following this, the server
data will be completely rebuilt, using the current state of your Vault on this device (including its local
database) as <strong>the single, authoritative master copy</strong>.</Guidance
>
<InfoNote>There is a way to resolve this on other devices.</InfoNote>
<InfoNote>Of course, we can back up the data before proceeding.</InfoNote>
</Check>
<Check
title="I understand that other devices will no longer be able to synchronise, and will need to be reset the synchronisation information."
bind:value={confirmationCheck2}
>
<InfoNote>by resetting the remote, you will be informed on other devices.</InfoNote>
</Check>
<Check title="I understand that this action is irreversible once performed." bind:value={confirmationCheck3} />
</Guidance>
<InfoNote>
You should perform this operation only in exceptional circumstances, such as when the server data is completely
corrupted, when changes on all other devices are no longer needed, or when the database size has become unusually
large in comparison to the Vault size.
</InfoNote>
<Guidance important title="⚠️ Please Confirm the Following">
<Check
title="I understand that all changes made on other smartphones or computers possibly could be lost."
bind:value={confirmationCheck1}
>
<InfoNote>There is a way to resolve this on other devices.</InfoNote>
<InfoNote>Of course, we can back up the data before proceeding.</InfoNote>
</Check>
<Check
title="I understand that other devices will no longer be able to synchronise, and will need to be reset the synchronisation information."
bind:value={confirmationCheck2}
>
<InfoNote>by resetting the remote, you will be informed on other devices.</InfoNote>
</Check>
<Check title="I understand that this action is irreversible once performed." bind:value={confirmationCheck3} />
</Guidance>
{/if}
<hr />
<Instruction>
<Question>Have you created a backup before proceeding?</Question>
@@ -103,12 +114,19 @@
</Option>
</Options>
</Instruction>
<Instruction>
<ExtraItems title="Advanced">
<Check title="Prevent fetching configuration from server" bind:value={preventFetchingConfig} />
</ExtraItems>
</Instruction>
{#if !isP2P}
<Instruction>
<ExtraItems title="Advanced">
<Check title="Prevent fetching configuration from server" bind:value={preventFetchingConfig} />
</ExtraItems>
</Instruction>
{/if}
<UserDecisions>
<Decision title="I Understand, Overwrite Server" important disabled={!canProceed} commit={() => commit()} />
<Decision
title={isP2P ? msg("Ui.SetupWizard.RebuildEverythingP2P.Proceed") : "I Understand, Overwrite Server"}
important
disabled={!canProceed}
commit={() => commit()}
/>
<Decision title="Cancel" commit={() => setResult(TYPE_CANCEL)} />
</UserDecisions>
@@ -1,5 +1,5 @@
import { requestToCouchDBWithCredentials } from "@/common/utils";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import { Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { parseHeaderValues } from "@vrtmrz/livesync-commonlib/compat/common/utils";
+1 -1
View File
@@ -11,7 +11,7 @@ import {
EVENT_SETTING_SAVED,
eventHub,
} from "@/common/events.ts";
import { $msg, setLang } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg, setLang } from "@/common/translation";
import { AbstractModule } from "@/modules/AbstractModule.ts";
import type { InjectableServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableServiceHub";
import type { LiveSyncCore } from "@/main.ts";
@@ -1,5 +1,5 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { translateIfAvailable as translate } from "@/common/translation";
type Props = {
title: string;
@@ -1,5 +1,5 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { translateIfAvailable as translate } from "@/common/translation";
import { fireAndForget } from "octagonal-wheels/promises";
type Props = {
@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount, tick } from "svelte";
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { translateIfAvailable as translate } from "@/common/translation";
import { getDialogContext } from "@/modules/services/LiveSyncUI/svelteDialog";
import { _activeDocument } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
@@ -1,5 +1,5 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { translateIfAvailable as translate } from "@/common/translation";
type Props = {
title?: string;
@@ -1,5 +1,5 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { translateIfAvailable as translate } from "@/common/translation";
type Props = {
children?: () => any;
@@ -1,5 +1,5 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { translateIfAvailable as translate } from "@/common/translation";
type SignalWord = "danger" | "warning" | "caution" | "notice";
@@ -1,5 +1,5 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { translateIfAvailable as translate } from "@/common/translation";
type Props = {
label: string;
@@ -1,6 +1,6 @@
<script lang="ts">
import { getContext } from "svelte";
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { translateIfAvailable as translate } from "@/common/translation";
type Props = {
title: string;
@@ -1,5 +1,5 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { translateIfAvailable as translate } from "@/common/translation";
type Props = {
value: string;
+7 -7
View File
@@ -1,14 +1,11 @@
import { type App, type Plugin, Notice } from "@/deps";
import { scheduleTask, memoIfNotExist, memoObject, retrieveMemoObject, disposeMemoObject } from "@/common/utils";
import { EVENT_PLUGIN_UNLOADED } from "@/common/events";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { $msg } from "@/common/translation";
import type { Confirm, ConfirmActionLayout } from "@vrtmrz/livesync-commonlib/compat/interfaces/Confirm";
import { confirmAction, pickOne, promptPassword, promptText } from "@vrtmrz/obsidian-plugin-kit";
import type { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext";
import {
confirmWithMessageWithWideButton,
confirmWithMessage as confirmWithLegacyMessage,
} from "@/modules/coreObsidian/UILib/dialogs";
import { confirmWithMessageWithWideButton } from "@/modules/coreObsidian/UILib/dialogs";
export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceContext> implements Confirm {
private _context: T;
@@ -49,6 +46,7 @@ export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceC
yes: $msg("moduleInputUIObsidian.optionYes"),
no: $msg("moduleInputUIObsidian.optionNo"),
},
actionLayout: "vertical",
defaultAction: "no",
sourcePath: "/",
},
@@ -92,6 +90,7 @@ export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceC
message,
actions: ["Yes", "No"] as const,
labels: { Yes: yesLabel, No: noLabel },
actionLayout: "vertical",
defaultAction: opt.defaultOption === "Yes" ? "Yes" : "No",
sourcePath: "/",
},
@@ -136,6 +135,7 @@ export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceC
title: opt.title || defaultTitle,
message,
actions: buttons,
actionLayout: "vertical",
defaultAction: opt.defaultAction,
sourcePath: "/",
},
@@ -210,7 +210,7 @@ export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceC
actionLayout?: ConfirmActionLayout
): Promise<(typeof buttons)[number] | false> {
if (this.hasCountdown(timeout)) {
return confirmWithLegacyMessage(this._plugin, title, contentMd, buttons, defaultAction, timeout);
return confirmWithMessageWithWideButton(this._plugin, title, contentMd, buttons, defaultAction, timeout);
}
const result = await confirmAction(
this._app,
@@ -220,7 +220,7 @@ export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceC
actions: buttons,
defaultAction,
sourcePath: "/",
...(actionLayout === undefined ? {} : { actionLayout }),
actionLayout: actionLayout ?? "vertical",
},
this.dialogueLifecycle
);
@@ -93,6 +93,7 @@ describe("ObsidianConfirm Fancy Kit adapter", () => {
expect.objectContaining({
message: "Continue?",
actions: ["yes", "no"],
actionLayout: "vertical",
defaultAction: "no",
}),
{ signal: expect.any(AbortSignal) }
@@ -109,15 +110,14 @@ describe("ObsidianConfirm Fancy Kit adapter", () => {
expect(mocks.legacyAskSelectString).not.toHaveBeenCalled();
});
it("uses Kit for untimed action dialogues and retains the legacy countdown path", async () => {
it("keeps untimed and countdown action dialogues vertically stacked", async () => {
const { confirm, app, plugin } = createConfirm();
mocks.confirmAction.mockResolvedValueOnce("Apply").mockResolvedValueOnce("Yes");
mocks.legacyConfirm.mockResolvedValueOnce("Cancel");
mocks.legacyWideConfirm.mockResolvedValueOnce("No");
mocks.legacyWideConfirm.mockResolvedValueOnce("Cancel").mockResolvedValueOnce("No");
await expect(
confirm.confirmWithMessage("Review", "**Apply?**", ["Apply", "Cancel"], "Cancel", undefined, "vertical")
).resolves.toBe("Apply");
await expect(confirm.confirmWithMessage("Review", "**Apply?**", ["Apply", "Cancel"], "Cancel")).resolves.toBe(
"Apply"
);
await expect(confirm.askYesNoDialog("Continue?", { title: "Question", defaultOption: "Yes" })).resolves.toBe(
"yes"
);
@@ -139,8 +139,29 @@ describe("ObsidianConfirm Fancy Kit adapter", () => {
},
{ signal: expect.any(AbortSignal) }
);
expect(mocks.legacyConfirm).toHaveBeenCalledWith(plugin, "Timed", "Wait", ["Apply", "Cancel"], "Cancel", 30);
expect(mocks.legacyWideConfirm).toHaveBeenCalledWith(
expect(mocks.confirmAction).toHaveBeenNthCalledWith(
2,
app,
expect.objectContaining({
title: "Question",
message: "Continue?",
actions: ["Yes", "No"],
actionLayout: "vertical",
defaultAction: "Yes",
}),
{ signal: expect.any(AbortSignal) }
);
expect(mocks.legacyWideConfirm).toHaveBeenNthCalledWith(
1,
plugin,
"Timed",
"Wait",
["Apply", "Cancel"],
"Cancel",
30
);
expect(mocks.legacyWideConfirm).toHaveBeenNthCalledWith(
2,
plugin,
expect.any(String),
"Timed?",
@@ -148,6 +169,7 @@ describe("ObsidianConfirm Fancy Kit adapter", () => {
expect.any(String),
10
);
expect(mocks.legacyConfirm).not.toHaveBeenCalled();
});
it("uses Kit for untimed multi-action selection and keeps timed wide actions on the countdown dialogue", async () => {
@@ -176,6 +198,7 @@ describe("ObsidianConfirm Fancy Kit adapter", () => {
title: "Next step",
message: "Choose the next step",
actions,
actionLayout: "vertical",
defaultAction: "Review later",
sourcePath: "/",
},
@@ -26,6 +26,7 @@ import { createScreenWakeLockManager } from "octagonal-wheels/browser/wakeLock";
import { PouchDB } from "@vrtmrz/livesync-commonlib/compat/pouchdb/pouchdb-browser";
import { OpenKeyValueDatabase } from "@/common/KeyValueDB";
import { ObsidianNoticeGroupManager } from "./ObsidianNoticeGroups";
import { setLang } from "@/common/translation";
// InjectableServiceHub
@@ -42,6 +43,7 @@ export class ObsidianServiceHub extends InjectableServiceHub<ObsidianServiceCont
const setting = new ObsidianSettingService(context, {
APIService: API,
onDisplayLanguageChanged: setLang,
});
const appLifecycle = new ObsidianAppLifecycleService(context, {
settingService: setting,