refactor: consume Commonlib as a package

This commit is contained in:
vorotamoroz
2026-07-17 11:13:16 +00:00
parent 6475d32769
commit a721d3b602
665 changed files with 3438 additions and 31592 deletions
@@ -0,0 +1,162 @@
<script lang="ts">
import type { DialogHostProps } from "@vrtmrz/livesync-commonlib/compat/services/implements/base/SvelteDialog";
import { type DialogSvelteComponentBaseProps } from "./svelteDialog";
// type Props = DialogSvelteComponentBaseProps & {
// /**
// * The Svelte component to mount inside the dialog host
// */
// mountComponent: ComponentHasResult<any>;
// /**
// * Callback function to setup the dialog context
// * @param props
// */
// onSetupContext?(props: DialogSvelteComponentBaseProps): void;
// };
const { setTitle, closeDialog, setResult, mountComponent, getInitialData, onSetupContext }: DialogHostProps =
$props();
const contextProps = {
setTitle,
closeDialog,
setResult,
getInitialData,
} satisfies DialogSvelteComponentBaseProps<any,any>
// Call the onSetupContext function to setup the dialog context
onSetupContext?.(contextProps);
/**
* Wrapper around setResult to also close the dialog
* @param result
*/
const setResultWrapper = (result: any) => {
setResult(result);
closeDialog();
};
const Component = mountComponent;
let thisElement: HTMLElement;
</script>
<div class="dialog-host" bind:this={thisElement}>
<Component setResult={setResultWrapper} {getInitialData}></Component>
</div>
<style>
.dialog-host {
padding: 20px;
gap: 0.5em;
display: flex;
flex-direction: column;
padding-bottom: var(--keyboard-height, 0px);
}
.dialog-host :global(button) {
margin-left: 10px;
}
.dialog-host :global(.button-group) {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 20px;
}
.dialog-host :global(.row) {
display: flex;
flex-direction: row;
justify-items: center;
align-items: center;
flex-wrap: wrap;
}
.dialog-host :global(.row > input[type="text"]),
.dialog-host :global(.row > input[type="password"]),
.dialog-host :global(.row > textarea),
.dialog-host :global(.row > select) {
flex: 1;
margin-left: 10px;
min-width: 10em;
}
.dialog-host :global(.row > input[type="password"]) {
-webkit-text-security: disc;
}
.dialog-host :global(.row > input[type="checkbox"]) {
margin-left: 10px;
margin-right: 10px;
}
.dialog-host :global(label > span) {
display: block;
width: 8em;
}
.dialog-host :global(.note),
.dialog-host :global(.note-important),
.dialog-host :global(.note-error) {
padding: 10px;
margin-top: 4px;
margin-bottom: 0.5lh;
border-left: 4px solid;
}
.dialog-host :global(.note) {
background-color: var(--interactive-hover);
border-left-color: var(--interactive-accent);
}
.dialog-host :global(.note-important) {
background-color: var(--interactive-hover);
border-left-color: var(--text-warning);
}
.dialog-host :global(.note-error) {
background-color: var(--interactive-hover);
border-left-color: var(--text-error);
}
.dialog-host :global(hr) {
margin: 0.7lh 0;
}
.dialog-host :global(details) {
gap: 0.5em;
padding-left: 0.5em;
border-left: 2px solid var(--interactive-accent);
}
.dialog-host :global(summary::marker) {
display: none;
content: "";
}
.dialog-host :global(summary) {
border-left: 4px solid var(--interactive-accent);
padding-left: 0.5em;
cursor: pointer;
outline: none;
}
.dialog-host :global(details > summary::after) {
content: "⏷";
float: right;
margin-right: 0.5em;
}
.dialog-host :global(details[open] > summary::after) {
content: "⏶";
float: right;
margin-right: 0.5em;
}
.dialog-host :global(input:invalid),
.dialog-host :global(textarea:invalid) {
border-color: var(--background-modifier-error);
}
.dialog-host :global(.sub-section) {
margin-left: 1em;
display: flex;
flex-direction: column;
gap: 0.5em;
}
.dialog-host :global(.row > input[type="text"]:disabled),
.dialog-host :global(.row > input[type="password"]:disabled),
.dialog-host :global(.row > textarea:disabled),
.dialog-host :global(.row > select:disabled) {
background-color: var(--background-secondary);
}
</style>
@@ -0,0 +1,53 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
type Props = {
title: string;
value: boolean;
noteOnSelected?: () => any;
noteOnUnselected?: () => any;
children?: () => any;
};
let { title, value = $bindable(), noteOnSelected, noteOnUnselected, children }: Props = $props();
const translatedTitle = $derived.by(() => translate(title));
</script>
<label class="choice-row">
<input type="checkbox" bind:checked={value} />
<span class="choice-title">{translatedTitle}</span>
</label>
<div class="choice-notes">
<!-- TODO Highlight selected option -->
{#if value && noteOnSelected}
{@render noteOnSelected()}
{:else if !value && noteOnUnselected}
{@render noteOnUnselected()}
{/if}
{@render children?.()}
</div>
<style>
.choice-row {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: 1rem;
cursor: pointer;
}
.choice-row span.choice-title {
width: auto;
}
.choice-row input[type="checkbox"] {
/* width: 1.2rem;
height: 1.2rem; */
cursor: pointer;
}
.choice-notes {
margin-left: 2rem;
margin-top: 0.25rem;
color: var(--text-muted);
font-size: 0.9rem;
}
</style>
@@ -0,0 +1,24 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { fireAndForget } from "octagonal-wheels/promises";
type Props = {
title: string;
commit: () => Promise<void> | void;
important?: boolean;
destructive?: boolean;
additionalClasses?: string;
disabled?: boolean;
};
let { title, commit, additionalClasses, important, disabled = $bindable(), destructive }: Props = $props();
const translatedTitle = $derived.by(() => translate(title));
function onclick() {
fireAndForget(async () => commit());
}
</script>
<button
class="button {additionalClasses} {important ? 'mod-cta' : ''} {destructive ? 'mod-destructive' : ''}"
{onclick}
{disabled}>{translatedTitle}</button
>
@@ -0,0 +1,41 @@
<script lang="ts">
import { onMount, tick } from "svelte";
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { getDialogContext } from "@/modules/services/LiveSyncUI/svelteDialog";
import { _activeDocument } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
type Props = {
title: string;
subtitle?: string;
children?: () => unknown;
};
let { title = $bindable(), subtitle }: Props = $props();
const context = getDialogContext();
const translatedTitle = $derived.by(() => translate(title));
const translatedSubtitle = $derived.by(() => (subtitle ? translate(subtitle) : ""));
const modalTitle = $derived.by(() => `${translatedTitle}${translatedSubtitle ? ` - ${translatedSubtitle}` : ""}`);
$effect(() => {
if (translatedTitle) {
context.setTitle(modalTitle);
}
});
onMount(async () => {
context.setTitle(modalTitle);
await tick();
_activeDocument.querySelector(".modal")?.scrollTo(0, 0);
});
</script>
<div class="dialog-header">
<h2>{translatedTitle}</h2>
{#if translatedSubtitle}
<h4>{translatedSubtitle}</h4>
{/if}
</div>
<style>
.dialog-header {
display: none;
}
</style>
@@ -0,0 +1,17 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
type Props = {
title?: string;
children?: () => any;
};
const { children, title }: Props = $props();
const translatedTitle = $derived.by(() => (title ? translate(title) : ""));
</script>
<details>
<summary>{translatedTitle}</summary>
<div class="sub-section">
{@render children?.()}
</div>
</details>
@@ -0,0 +1,21 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
type Props = {
children?: () => any;
important?: boolean;
title?: string;
};
const { children, important, title }: Props = $props();
const cssClass = $derived.by(() => {
return important ? "guidance important" : "guidance";
});
const translatedTitle = $derived.by(() => (title ? translate(title) : ""));
</script>
<div class={cssClass}>
{#if translatedTitle}
<h3>{translatedTitle}</h3>
{/if}
{@render children?.()}
</div>
@@ -0,0 +1,87 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
type SignalWord = "danger" | "warning" | "caution" | "notice";
type Props = {
title?: string;
message?: string;
children?: () => any;
cssClass?: string;
warning?: boolean;
caution?: boolean;
error?: boolean;
notice?: boolean;
info?: boolean;
signalWord?: string | false;
visible?: boolean;
};
const {
title,
message,
children,
cssClass,
warning: isWarning,
caution: isCaution,
error: isError,
notice: isNotice,
info: isInfo = true,
signalWord,
visible,
}: Props = $props();
const derivedCssClass = $derived.by(() => {
if (isError) {
return "note-error sls-info-note sls-info-note-danger";
} else if (isWarning) {
return "note-important sls-info-note sls-info-note-warning";
} else if (isCaution) {
return "note-important sls-info-note sls-info-note-caution";
} else if (isNotice) {
return "note sls-info-note sls-info-note-notice";
} else if (isInfo) {
return "note sls-info-note";
} else {
return "sls-info-note";
}
});
const signalWordKind = $derived.by((): SignalWord | undefined => {
if (isError) return "danger";
if (isWarning) return "warning";
if (isCaution) return "caution";
if (isNotice) return "notice";
return undefined;
});
const defaultSignalWord = $derived.by(() => {
switch (signalWordKind) {
case "danger":
return "Ui.Common.Signal.Danger";
case "warning":
return "Ui.Common.Signal.Warning";
case "caution":
return "Ui.Common.Signal.Caution";
case "notice":
return "Ui.Common.Signal.Notice";
default:
return "";
}
});
const signalWordText = $derived.by(() => {
if (signalWord === false) return "";
return signalWord ? translate(signalWord) : defaultSignalWord ? translate(defaultSignalWord) : "";
});
const signalWordCssKind = $derived.by(() => signalWordKind ?? "custom");
const translatedTitle = $derived.by(() => (title ? translate(title) : ""));
const translatedMessage = $derived.by(() => (message ? translate(message) : ""));
</script>
{#if visible === undefined || visible === true}
<div class={(cssClass ?? "") + " " + derivedCssClass}>
{#if signalWordText}
<div class="sls-signal-word sls-signal-word-{signalWordCssKind}">{signalWordText}</div>
{/if}
{#if translatedTitle}<h3>{translatedTitle}</h3>{/if}
{#if translatedMessage}<p>{translatedMessage}</p>{/if}
{@render children?.()}
</div>
{/if}
@@ -0,0 +1,74 @@
<script lang="ts">
type Props = {
info: Record<string, any>;
};
const { info }: Props = $props();
const infoEntries = $derived.by(() => Object.entries(info ?? {}));
</script>
<div class="info-panel">
<div class="info-grid" role="list">
{#each infoEntries as [key, value]}
<div class="info-entry info-key" role="listitem" aria-label={key}>
<div class="key">{key}</div>
</div>
<div class="info-entry info-item" role="listitem" aria-label={key}>
<div class="value">{value}</div>
</div>
{/each}
</div>
</div>
<style>
.info-panel {
padding: 0.6rem;
flex-grow: 1;
}
/* Main Grid (Info Items) 120px to 1fr, repeat */
.info-grid {
display: grid;
grid-template-columns: minmax(120px, 1fr) 1fr;
column-count: 2;
gap: 0.6rem;
margin-top: 0.5rem;
grid-area: "info-key" "info-value";
}
.info-entry {
display: grid;
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);
grid-area: "info-key";
}
.info-item {
align-items: start;
padding: 0.5rem;
background: var(--background-modifier-hover, rgba(0, 0, 0, 0.03));
grid-area: "info-value";
}
.value {
white-space: pre-wrap;
word-break: break-word;
color: var(--text-normal, #e6e6e6);
min-height: 1em;
}
@container (max-width: 340px) {
.info-grid {
grid-template-columns: 1fr;
}
.info-item {
grid-template-columns: 1fr;
}
}
</style>
@@ -0,0 +1,15 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
type Props = {
label: string;
children?: () => any;
};
const { label, children }: Props = $props();
const translatedLabel = $derived.by(() => translate(label));
</script>
<label class="row"
><span>{translatedLabel}</span>
{@render children?.()}
</label>
@@ -0,0 +1,10 @@
<script lang="ts">
type Props = {
children?: () => any;
};
const { children }: Props = $props();
</script>
<div class="question-container">
{@render children?.()}
</div>
@@ -0,0 +1,81 @@
<script lang="ts">
import { getContext } from "svelte";
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
type Props = {
title: string;
value: string;
selectedValue: string;
noteOnSelected?: () => any;
noteOnUnselected?: () => any;
group?: string;
children?: () => any;
};
const definedGroupContext = getContext<string>("radioGroup");
let {
title,
value = $bindable(),
noteOnSelected,
noteOnUnselected,
selectedValue,
group,
children,
}: Props = $props();
const actualGroup = group ?? definedGroupContext;
const translatedTitle = $derived.by(() => translate(title));
</script>
<div class="option-container {value === selectedValue ? 'selected' : ''}">
<label>
<div class="choice-row">
<input type="radio" bind:group={value} name={actualGroup} value={selectedValue} />
<span class="choice-title">{translatedTitle}</span>
</div>
<div class="choice-notes">
{#if value === selectedValue && noteOnSelected}
{@render noteOnSelected()}
{:else if value !== selectedValue && noteOnUnselected}
{@render noteOnUnselected()}
{/if}
{@render children?.()}
</div>
</label>
</div>
<style>
.option-container {
border: 1px solid transparent;
border-radius: 0.25lh;
padding: 0.5rem;
}
.option-container.selected {
border-color: var(--interactive-accent);
}
.choice-row {
display: flex;
align-items: center;
gap: 0.5rem;
/* margin-top: 1rem; */
cursor: pointer;
}
.choice-row span.choice-title {
width: auto;
}
.choice-row input[type="radio"] {
/* width: 1.2rem;
height: 1.2rem; */
cursor: pointer;
}
.choice-notes {
margin-left: 2rem;
margin-top: 0.25rem;
color: var(--text-muted);
font-size: 0.9rem;
}
.option-container.selected .choice-notes {
color: var(--text-normal);
}
</style>
@@ -0,0 +1,14 @@
<script lang="ts">
import { setContext } from "svelte";
type Props = {
children?: () => any;
};
const { children }: Props = $props();
const questionGroupID = Math.random().toString(36).substring(2, 15);
setContext("radioGroup", questionGroupID);
</script>
<div class="options-container">
{@render children?.()}
</div>
@@ -0,0 +1,34 @@
<script lang="ts">
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
type Props = {
value: string;
name?: string;
placeholder?: string;
disabled?: boolean;
required?: boolean;
};
let {
value = $bindable(),
name = "password",
placeholder = "Enter your password",
disabled = false,
required = false,
}: Props = $props();
let showPassword = $state(false);
const type = $derived.by(() => (showPassword ? "text" : "password"));
const translatedPlaceholder = $derived.by(() => translate(placeholder));
</script>
<input
{type}
{name}
placeholder={translatedPlaceholder}
bind:value
{disabled}
{required}
spellcheck="false"
autocorrect="off"
autocapitalize="off"
/>
<input type="checkbox" bind:checked={showPassword} />
@@ -0,0 +1,26 @@
<script lang="ts">
import { setContext } from "svelte";
type Props = {
children?: () => any;
question?: () => any;
};
const { children, question }: Props = $props();
const questionGroupID = Math.random().toString(36).substring(2, 15);
setContext("radioGroup", questionGroupID);
</script>
<div class="question-container">
{#if question}<h3>{@render question?.()}</h3>{/if}
<div class="question-content">
{@render children?.()}
</div>
</div>
<style>
.question-container {
border-bottom: 2px solid var(--interactive-accent);
margin-bottom: 0.5lh;
padding-bottom: 0.5lh;
}
</style>
@@ -0,0 +1,12 @@
<script lang="ts">
export type Props = {
children?: () => any;
};
const { children }: Props = $props();
</script>
<div class="button-group">
{#if children}
{@render children()}
{/if}
</div>
@@ -0,0 +1,59 @@
<script lang="ts">
import { onMount } from "svelte";
import type { GuestDialogProps } from "@/modules/services/LiveSyncUI/svelteDialog";
import DialogHeader from "@/modules/services/LiveSyncUI/components/DialogHeader.svelte";
import Instruction from "@/modules/services/LiveSyncUI/components/Instruction.svelte";
import InputRow from "@/modules/services/LiveSyncUI/components/InputRow.svelte";
import Decision from "@/modules/services/LiveSyncUI/components/Decision.svelte";
import UserDecisions from "@/modules/services/LiveSyncUI/components/UserDecisions.svelte";
import InfoNote from "@/modules/services/LiveSyncUI/components/InfoNote.svelte";
const TYPE_OK = "ok";
type ResultType = typeof TYPE_OK;
type Options = {
title?: string;
dataToCopy: string;
};
type Props = GuestDialogProps<ResultType, Options>;
const { setResult, getInitialData }: Props = $props();
let dataToCopy = $state("");
let title = $state<string | undefined>(undefined);
let copied = $state(false);
onMount(() => {
if (getInitialData) {
const initialData = getInitialData();
if (initialData) {
dataToCopy = initialData.dataToCopy;
title = initialData.title;
}
}
});
function commit() {
setResult(TYPE_OK);
}
async function copyToClipboard() {
await navigator.clipboard.writeText(dataToCopy);
copied = true;
}
</script>
<DialogHeader title="Your {title || 'Data'} is ready to be copied" />
<Instruction>
<InputRow label={title || "Data to Copy"}>
<textarea readonly rows="4">{dataToCopy}</textarea>
<button onclick={() => copyToClipboard()}
>{#if !copied}📋{:else}✔️{/if}
</button>
</InputRow>
</Instruction>
<InfoNote visible={copied}>
Your {title || "data"} has been copied to the clipboard.
</InfoNote>
<UserDecisions>
<Decision title="OK" important={true} {commit} />
</UserDecisions>
<style>
textarea {
resize: none;
}
</style>
@@ -0,0 +1,14 @@
export type {
HasSetResult,
HasGetInitialData,
ComponentHasResult,
GuestDialogProps,
DialogSvelteComponentBaseProps,
DialogControlBase,
} from "@vrtmrz/livesync-commonlib/compat/services/implements/base/SvelteDialog";
export {
CONTEXT_DIALOG_CONTROLS,
setupDialogContext,
getDialogContext,
SvelteDialogManagerBase,
} from "@vrtmrz/livesync-commonlib/compat/services/implements/base/SvelteDialog";
+4 -4
View File
@@ -1,11 +1,11 @@
import { InjectableAPIService } from "@lib/services/implements/injectable/InjectableAPIService";
import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext";
import { InjectableAPIService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableAPIService";
import type { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext";
import { Platform, type Command, type ViewCreator } from "@/deps.ts";
import { ObsHttpHandler } from "@/modules/essentialObsidian/APILib/ObsHttpHandler";
import { ObsidianConfirm } from "./ObsidianConfirm";
import type { Confirm } from "@lib/interfaces/Confirm";
import type { Confirm } from "@vrtmrz/livesync-commonlib/compat/interfaces/Confirm";
import { requestUrl, type RequestUrlParam } from "@/deps";
import { compatGlobal } from "@lib/common/coreEnvFunctions";
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
// All Services will be migrated to be based on Plain Services, not Injectable Services.
// This is a migration step.
@@ -1,5 +1,5 @@
import { AppLifecycleServiceBase } from "@lib/services/implements/injectable/InjectableAppLifecycleService";
import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext";
import { AppLifecycleServiceBase } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableAppLifecycleService";
import type { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext";
declare module "obsidian" {
interface App {
commands: {
+3 -3
View File
@@ -1,8 +1,8 @@
import { type App, type Plugin, Notice } from "@/deps";
import { scheduleTask, memoIfNotExist, memoObject, retrieveMemoObject, disposeMemoObject } from "@/common/utils";
import { $msg } from "@lib/common/i18n";
import type { Confirm } from "@lib/interfaces/Confirm";
import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext";
import { $msg } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import type { Confirm } from "@vrtmrz/livesync-commonlib/compat/interfaces/Confirm";
import type { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext";
import {
askYesNo,
askString,
@@ -1,8 +1,8 @@
import { initializeStores } from "@/common/stores";
// import { InjectableDatabaseService } from "@/lib/src/services/implements/injectable/InjectableDatabaseService";
import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext";
import { DatabaseService, type DatabaseServiceDependencies } from "@lib/services/base/DatabaseService.ts";
import type { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext";
import { DatabaseService, type DatabaseServiceDependencies } from "@vrtmrz/livesync-commonlib/compat/services/base/DatabaseService";
export class ObsidianDatabaseService<T extends ObsidianServiceContext> extends DatabaseService<T> {
private __onOpenDatabase(vaultName: string) {
+3 -3
View File
@@ -1,6 +1,6 @@
import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext";
import type { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext";
import { normalizePath } from "@/deps";
import { PathService } from "@lib/services/base/PathService";
import { PathService } from "@vrtmrz/livesync-commonlib/compat/services/base/PathService";
import {
type BASE_IS_NEW,
@@ -11,7 +11,7 @@ import {
compareFileFreshness,
isMarkedAsSameChanges,
} from "@/common/utils";
import type { UXFileInfo, AnyEntry, UXFileInfoStub, FilePathWithPrefix } from "@lib/common/types";
import type { UXFileInfo, AnyEntry, UXFileInfoStub, FilePathWithPrefix } from "@vrtmrz/livesync-commonlib/compat/common/types";
export class ObsidianPathService extends PathService<ObsidianServiceContext> {
override markChangesAreSame(
old: UXFileInfo | AnyEntry | FilePathWithPrefix,
@@ -0,0 +1,19 @@
import type ObsidianLiveSyncPlugin from "@/main";
import type { App, Plugin } from "@/deps";
import { ServiceContext } from "@vrtmrz/livesync-commonlib/context";
import { eventHub } from "@/common/events";
import { translateLiveSyncMessage } from "@/common/translation";
/** Host capabilities owned by one Self-hosted LiveSync plug-in instance. */
export class ObsidianServiceContext extends ServiceContext {
app: App;
plugin: Plugin;
liveSyncPlugin: ObsidianLiveSyncPlugin;
constructor(app: App, plugin: Plugin, liveSyncPlugin: ObsidianLiveSyncPlugin) {
super({ events: eventHub, translate: translateLiveSyncMessage });
this.app = app;
this.plugin = plugin;
this.liveSyncPlugin = liveSyncPlugin;
}
}
@@ -0,0 +1,27 @@
import { describe, expect, it } from "vitest";
import { eventHub } from "@/common/events";
import { translateLiveSyncMessage } from "@/common/translation";
import { observeServiceContext } from "../../../test/contracts/serviceContext";
import { ObsidianServiceContext } from "./ObsidianServiceContext";
const TRANSLATION_KEY = "Replicator.Message.InitialiseFatalError";
describe("ObsidianServiceContext contract", () => {
it("preserves the plug-in capabilities and host-neutral API results", () => {
type Parameters = ConstructorParameters<typeof ObsidianServiceContext>;
const app = {} as Parameters[0];
const plugin = {} as Parameters[1];
const liveSyncPlugin = {} as Parameters[2];
const context = new ObsidianServiceContext(app, plugin, liveSyncPlugin);
expect(observeServiceContext(context, TRANSLATION_KEY)).toEqual({
translation: translateLiveSyncMessage(TRANSLATION_KEY),
receivedEvents: ["context-contract-event"],
});
expect(context.events).toBe(eventHub);
expect(context.app).toBe(app);
expect(context.plugin).toBe(plugin);
expect(context.liveSyncPlugin).toBe(liveSyncPlugin);
});
});
+8 -3
View File
@@ -1,6 +1,6 @@
import { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub";
import { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext";
import type { ServiceInstances } from "@lib/services/ServiceHub";
import { InjectableServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableServiceHub";
import { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext";
import type { ServiceInstances } from "@vrtmrz/livesync-commonlib/compat/services/ServiceHub";
import type ObsidianLiveSyncPlugin from "@/main";
import {
ObsidianConflictService,
@@ -23,6 +23,8 @@ import { ObsidianPathService } from "./ObsidianPathService";
import { ObsidianVaultService } from "./ObsidianVaultService";
import { ObsidianUIService } from "./ObsidianUIService";
import { createScreenWakeLockManager } from "octagonal-wheels/browser/wakeLock";
import { PouchDB } from "@vrtmrz/livesync-commonlib/compat/pouchdb/pouchdb-browser";
import { OpenKeyValueDatabase } from "@/common/KeyValueDB";
// InjectableServiceHub
@@ -43,6 +45,7 @@ export class ObsidianServiceHub extends InjectableServiceHub<ObsidianServiceCont
settingService: setting,
});
const remote = new ObsidianRemoteService(context, {
pouchDB: PouchDB,
APIService: API,
appLifecycle: appLifecycle,
setting: setting,
@@ -62,12 +65,14 @@ export class ObsidianServiceHub extends InjectableServiceHub<ObsidianServiceCont
return true;
});
const database = new ObsidianDatabaseService(context, {
pouchDB: PouchDB,
path: path,
vault: vault,
setting: setting,
API: API,
});
const keyValueDB = new ObsidianKeyValueDBService(context, {
openKeyValueDatabase: OpenKeyValueDatabase,
appLifecycle: appLifecycle,
databaseEvents: databaseEvents,
vault: vault,
+12 -12
View File
@@ -1,15 +1,15 @@
import { InjectableConflictService } from "@lib/services/implements/injectable/InjectableConflictService";
import { InjectableDatabaseEventService } from "@lib/services/implements/injectable/InjectableDatabaseEventService";
import { InjectableFileProcessingService } from "@lib/services/implements/injectable/InjectableFileProcessingService";
import { InjectableRemoteService } from "@lib/services/implements/injectable/InjectableRemoteService";
import { InjectableReplicationService } from "@lib/services/implements/injectable/InjectableReplicationService";
import { InjectableReplicatorService } from "@lib/services/implements/injectable/InjectableReplicatorService";
import { InjectableTestService } from "@lib/services/implements/injectable/InjectableTestService";
import { InjectableTweakValueService } from "@lib/services/implements/injectable/InjectableTweakValueService";
import { ConfigServiceBrowserCompat } from "@lib/services/implements/browser/ConfigServiceBrowserCompat";
import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext.ts";
import { KeyValueDBService } from "@lib/services/base/KeyValueDBService";
import { ControlService } from "@lib/services/base/ControlService";
import { InjectableConflictService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableConflictService";
import { InjectableDatabaseEventService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableDatabaseEventService";
import { InjectableFileProcessingService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableFileProcessingService";
import { InjectableRemoteService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableRemoteService";
import { InjectableReplicationService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableReplicationService";
import { InjectableReplicatorService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableReplicatorService";
import { InjectableTestService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableTestService";
import { InjectableTweakValueService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableTweakValueService";
import { ConfigServiceBrowserCompat } from "@vrtmrz/livesync-commonlib/compat/services/implements/browser/ConfigServiceBrowserCompat";
import type { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext";
import { KeyValueDBService } from "@vrtmrz/livesync-commonlib/compat/services/base/KeyValueDBService";
import { ControlService } from "@vrtmrz/livesync-commonlib/compat/services/base/ControlService";
export class ObsidianDatabaseEventService extends InjectableDatabaseEventService<ObsidianServiceContext> {}
@@ -1,19 +1,18 @@
import { compatGlobal } from "@lib/common/coreEnvFunctions";
import { type ObsidianLiveSyncSettings } from "@lib/common/types";
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED } from "@lib/events/coreEvents";
import { eventHub } from "@lib/hub/hub";
import { SettingService, type SettingServiceDependencies } from "@lib/services/base/SettingService";
import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext";
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
import { type ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
import { SettingService, type SettingServiceDependencies } from "@vrtmrz/livesync-commonlib/compat/services/base/SettingService";
import type { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext";
export class ObsidianSettingService<T extends ObsidianServiceContext> extends SettingService<T> {
constructor(context: T, dependencies: SettingServiceDependencies) {
super(context, dependencies);
this.onSettingSaved.addHandler((settings) => {
eventHub.emitEvent(EVENT_SETTING_SAVED, settings);
this.context.events.emitEvent(EVENT_SETTING_SAVED, settings);
return Promise.resolve(true);
});
this.onSettingLoaded.addHandler((settings) => {
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
this.context.events.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
return Promise.resolve(true);
});
}
+7 -8
View File
@@ -1,11 +1,11 @@
import type { ConfigService } from "@lib/services/base/ConfigService";
import type { AppLifecycleService } from "@lib/services/base/AppLifecycleService";
import type { ReplicatorService } from "@lib/services/base/ReplicatorService";
import { UIService } from "@lib/services/implements/base/UIService";
import { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext";
import type { ConfigService } from "@vrtmrz/livesync-commonlib/compat/services/base/ConfigService";
import type { AppLifecycleService } from "@vrtmrz/livesync-commonlib/compat/services/base/AppLifecycleService";
import type { ReplicatorService } from "@vrtmrz/livesync-commonlib/compat/services/base/ReplicatorService";
import { UIService } from "@vrtmrz/livesync-commonlib/compat/services/implements/base/UIService";
import { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext";
import { ObsidianSvelteDialogManager } from "./SvelteDialogObsidian";
import DialogToCopy from "@lib/UI/dialogues/DialogueToCopy.svelte";
import type { IAPIService, IControlService } from "@lib/services/base/IService";
import DialogToCopy from "@/modules/services/LiveSyncUI/dialogues/DialogueToCopy.svelte";
import type { IAPIService, IControlService } from "@vrtmrz/livesync-commonlib/compat/services/base/IService";
export type ObsidianUIServiceDependencies<T extends ObsidianServiceContext = ObsidianServiceContext> = {
appLifecycle: AppLifecycleService<T>;
config: ConfigService<T>;
@@ -28,7 +28,6 @@ export class ObsidianUIService extends UIService<ObsidianServiceContext> {
control: dependents.control,
});
super(context, {
appLifecycle: dependents.appLifecycle,
dialogManager: obsidianSvelteDialogManager,
APIService: dependents.APIService,
});
+3 -3
View File
@@ -1,7 +1,7 @@
import { getPathFromTFile, isValidPath } from "@/common/utils";
import { InjectableVaultService } from "@lib/services/implements/injectable/InjectableVaultService";
import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext";
import type { FilePath } from "@lib/common/types";
import { InjectableVaultService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableVaultService";
import type { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext";
import type { FilePath } from "@vrtmrz/livesync-commonlib/compat/common/types";
declare module "obsidian" {
interface DataAdapter {
+3 -3
View File
@@ -5,9 +5,9 @@ import {
SvelteDialogMixIn,
type ComponentHasResult,
type SvelteDialogManagerDependencies,
} from "@lib/services/implements/base/SvelteDialog";
import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext";
import DialogHost from "@lib/UI/DialogHost.svelte";
} from "@vrtmrz/livesync-commonlib/compat/services/implements/base/SvelteDialog";
import type { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext";
import DialogHost from "@/modules/services/LiveSyncUI/DialogHost.svelte";
export const SvelteDialogBase = SvelteDialogMixIn(Modal, DialogHost);
export class SvelteDialogObsidian<
T,