Align host integrations with current API recommendations

This commit is contained in:
vorotamoroz
2026-08-24 16:39:32 +00:00
parent 47759f6205
commit dd11a753d5
19 changed files with 146 additions and 129 deletions
+4 -4
View File
@@ -24,7 +24,7 @@
"@smithy/util-retry": "^4.4.5", "@smithy/util-retry": "^4.4.5",
"@vrtmrz/browser-ui-kit": "0.1.0", "@vrtmrz/browser-ui-kit": "0.1.0",
"@vrtmrz/livesync-commonlib": "0.1.19", "@vrtmrz/livesync-commonlib": "0.1.19",
"@vrtmrz/obsidian-plugin-kit": "0.1.3", "@vrtmrz/obsidian-plugin-kit": "0.1.4",
"@vrtmrz/ui-interactions": "0.1.2", "@vrtmrz/ui-interactions": "0.1.2",
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"fflate": "^0.8.2", "fflate": "^0.8.2",
@@ -4834,9 +4834,9 @@
} }
}, },
"node_modules/@vrtmrz/obsidian-plugin-kit": { "node_modules/@vrtmrz/obsidian-plugin-kit": {
"version": "0.1.3", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/@vrtmrz/obsidian-plugin-kit/-/obsidian-plugin-kit-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@vrtmrz/obsidian-plugin-kit/-/obsidian-plugin-kit-0.1.4.tgz",
"integrity": "sha512-6fsKdhFZtBv6FXlZHtSmpqwROohFzDmres6q08nr2xYGVeh2ooBGU3zJS94WN/tOjDT+wa/Vr3yE42wmI0pIZA==", "integrity": "sha512-MxZgd7UOr8DXk0e+JsAXJmYj/8bfIoEliI4ruvp1Cu4U+mmVI/p63nZ5we3dDW+EaCgRAgF+ztuVDkcMUk1DwA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vrtmrz/ui-interactions": "0.1.2" "@vrtmrz/ui-interactions": "0.1.2"
+1 -1
View File
@@ -179,7 +179,7 @@
"@smithy/util-retry": "^4.4.5", "@smithy/util-retry": "^4.4.5",
"@vrtmrz/browser-ui-kit": "0.1.0", "@vrtmrz/browser-ui-kit": "0.1.0",
"@vrtmrz/livesync-commonlib": "0.1.19", "@vrtmrz/livesync-commonlib": "0.1.19",
"@vrtmrz/obsidian-plugin-kit": "0.1.3", "@vrtmrz/obsidian-plugin-kit": "0.1.4",
"@vrtmrz/ui-interactions": "0.1.2", "@vrtmrz/ui-interactions": "0.1.2",
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"fflate": "^0.8.2", "fflate": "^0.8.2",
@@ -4,7 +4,6 @@ vi.mock("@/deps.ts", () => ({
addIcon: vi.fn(), addIcon: vi.fn(),
diff_match_patch: class DiffMatchPatch {}, diff_match_patch: class DiffMatchPatch {},
normalizePath: vi.fn((path: string) => path), normalizePath: vi.fn((path: string) => path),
Notice: class Notice {},
parseYaml: vi.fn(), parseYaml: vi.fn(),
Platform: {}, Platform: {},
})); }));
@@ -30,13 +29,10 @@ vi.mock("@/common/types.ts", () => ({
PERIODIC_PLUGIN_SWEEP: 60, PERIODIC_PLUGIN_SWEEP: 60,
})); }));
vi.mock("@/common/utils.ts", () => ({ vi.mock("@/common/utils.ts", () => ({
cancelTask: vi.fn(),
EVEN: Symbol("even"), EVEN: Symbol("even"),
disposeMemoObject: vi.fn(),
isCustomisationSyncMetadata: vi.fn(), isCustomisationSyncMetadata: vi.fn(),
isPluginMetadata: vi.fn(), isPluginMetadata: vi.fn(),
memoIfNotExist: vi.fn(),
memoObject: vi.fn(),
retrieveMemoObject: vi.fn(),
scheduleTask: vi.fn(), scheduleTask: vi.fn(),
})); }));
vi.mock("@/common/PeriodicProcessor.ts", () => ({ vi.mock("@/common/PeriodicProcessor.ts", () => ({
@@ -55,6 +51,7 @@ vi.mock("@/common/obsidianCommunityPlugins.ts", () => ({
getObsidianCommunityPluginManager: vi.fn(), getObsidianCommunityPluginManager: vi.fn(),
})); }));
import { cancelTask } from "@/common/utils.ts";
import { ConfigSync } from "./CmdConfigSync"; import { ConfigSync } from "./CmdConfigSync";
describe("ConfigSync commands", () => { describe("ConfigSync commands", () => {
@@ -93,4 +90,24 @@ describe("ConfigSync commands", () => {
expect(command?.checkCallback?.(false)).toBe(true); expect(command?.checkCallback?.(false)).toBe(true);
expect(showPluginSyncModal).toHaveBeenCalledOnce(); expect(showPluginSyncModal).toHaveBeenCalledOnce();
}); });
it("cancels the pending configuration Notice before releasing its owned UI", () => {
const notices = { hide: vi.fn() };
const periodicPluginSweepProcessor = { disable: vi.fn() };
const configSync = Object.create(ConfigSync.prototype) as ConfigSync;
Object.assign(configSync, {
core: {
services: {
context: { notices },
},
},
periodicPluginSweepProcessor,
});
configSync.onunload();
expect(cancelTask).toHaveBeenCalledWith("config-sync:updated-configuration");
expect(notices.hide).toHaveBeenCalledWith("config-sync:updated-configuration");
expect(periodicPluginSweepProcessor.disable).toHaveBeenCalledOnce();
});
}); });
+10 -36
View File
@@ -1,7 +1,6 @@
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import type PouchDB from "pouchdb-core"; import type PouchDB from "pouchdb-core";
import { import {
Notice,
type PluginManifest, type PluginManifest,
parseYaml, parseYaml,
normalizePath, normalizePath,
@@ -53,21 +52,11 @@ import {
import { serialized, shareRunningResult } from "octagonal-wheels/concurrency/lock"; import { serialized, shareRunningResult } from "octagonal-wheels/concurrency/lock";
import { LiveSyncCommands } from "@/features/LiveSyncCommands.ts"; import { LiveSyncCommands } from "@/features/LiveSyncCommands.ts";
import { stripAllPrefixes } from "@vrtmrz/livesync-commonlib/compat/string_and_binary/path"; import { stripAllPrefixes } from "@vrtmrz/livesync-commonlib/compat/string_and_binary/path";
import { import { cancelTask, EVEN, isCustomisationSyncMetadata, isPluginMetadata, scheduleTask } from "@/common/utils.ts";
EVEN,
disposeMemoObject,
isCustomisationSyncMetadata,
isPluginMetadata,
memoIfNotExist,
memoObject,
retrieveMemoObject,
scheduleTask,
} from "@/common/utils.ts";
import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts"; import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts";
import { JsonResolveModal } from "@/features/HiddenFileCommon/JsonResolveModal.ts"; import { JsonResolveModal } from "@/features/HiddenFileCommon/JsonResolveModal.ts";
import { QueueProcessor } from "octagonal-wheels/concurrency/processor"; import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
import { pluginScanningCount } from "@vrtmrz/livesync-commonlib/compat/mock_and_interop/stores"; import { pluginScanningCount } from "@vrtmrz/livesync-commonlib/compat/mock_and_interop/stores";
import type ObsidianLiveSyncPlugin from "@/main.ts";
import { base64ToArrayBuffer, base64ToString } from "octagonal-wheels/binary/base64"; import { base64ToArrayBuffer, base64ToString } from "octagonal-wheels/binary/base64";
import { ConflictResolveModal } from "@/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts"; import { ConflictResolveModal } from "@/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts";
import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
@@ -82,6 +71,7 @@ import { getObsidianCommunityPluginManager } from "@/common/obsidianCommunityPlu
const d = "\u200b"; const d = "\u200b";
const d2 = "\n"; const d2 = "\n";
const UPDATED_CONFIGURATION_NOTICE_KEY = "config-sync:updated-configuration";
function serialize(data: PluginDataEx): string { function serialize(data: PluginDataEx): string {
// For higher performance, create custom plug-in data strings. // For higher performance, create custom plug-in data strings.
@@ -393,8 +383,8 @@ export type PluginDataEx = {
}; };
export class ConfigSync extends LiveSyncCommands { export class ConfigSync extends LiveSyncCommands {
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) { constructor(core: LiveSyncCore) {
super(plugin, core); super(core);
pluginScanningCount.onChanged((e) => { pluginScanningCount.onChanged((e) => {
const total = e.value; const total = e.value;
pluginIsEnumerating.set(total != 0); pluginIsEnumerating.set(total != 0);
@@ -428,7 +418,7 @@ export class ConfigSync extends LiveSyncCommands {
if (this.pluginDialog) { if (this.pluginDialog) {
this.pluginDialog.open(); this.pluginDialog.open();
} else { } else {
this.pluginDialog = new PluginDialogModal(this.app, this.plugin); this.pluginDialog = new PluginDialogModal(this.app, this.services.context.liveSyncPlugin);
this.pluginDialog.open(); this.pluginDialog.open();
} }
} }
@@ -440,8 +430,10 @@ export class ConfigSync extends LiveSyncCommands {
} }
} }
onunload() { onunload() {
cancelTask(UPDATED_CONFIGURATION_NOTICE_KEY);
this.hidePluginSyncModal(); this.hidePluginSyncModal();
this.periodicPluginSweepProcessor?.disable(); this.periodicPluginSweepProcessor?.disable();
this.services.context.notices.hide(UPDATED_CONFIGURATION_NOTICE_KEY);
} }
addRibbonIcon = this.services.API.addRibbonIcon.bind(this.services.API); addRibbonIcon = this.services.API.addRibbonIcon.bind(this.services.API);
onload() { onload() {
@@ -1196,22 +1188,9 @@ export class ConfigSync extends LiveSyncCommands {
}); });
}); });
const updatedPluginKey = "popupUpdated-plugins"; scheduleTask(UPDATED_CONFIGURATION_NOTICE_KEY, 1000, () => {
scheduleTask(updatedPluginKey, 1000, async () => { this.services.context.notices.show(UPDATED_CONFIGURATION_NOTICE_KEY, fragment, {
const popup = await memoIfNotExist(updatedPluginKey, () => new Notice(fragment, 0)); durationMs: 20_000,
//@ts-ignore -- retained for compatibility with Obsidian versions before Notice.messageEl.
const isShown = popup?.noticeEl?.isShown();
if (!isShown) {
memoObject(updatedPluginKey, new Notice(fragment, 0));
}
scheduleTask(updatedPluginKey + "-close", 20000, () => {
const popup = retrieveMemoObject<Notice>(updatedPluginKey);
if (!popup) return;
//@ts-ignore -- retained for compatibility with Obsidian versions before Notice.messageEl.
if (popup?.noticeEl?.isShown()) {
popup.hide();
}
disposeMemoObject(updatedPluginKey);
}); });
}); });
} }
@@ -1716,8 +1695,6 @@ export class ConfigSync extends LiveSyncCommands {
} }
async configureHiddenFileSync(mode: OptionalSyncFeatureMode) { async configureHiddenFileSync(mode: OptionalSyncFeatureMode) {
if (mode == "DISABLE") { if (mode == "DISABLE") {
// this.plugin.settings.usePluginSync = false;
// await this.plugin.saveSettings();
await this.core.services.setting.applyPartial( await this.core.services.setting.applyPartial(
{ {
usePluginSync: false, usePluginSync: false,
@@ -1758,9 +1735,6 @@ export class ConfigSync extends LiveSyncCommands {
} }
this.services.setting.setDeviceAndVaultName(name); this.services.setting.setDeviceAndVaultName(name);
} }
// this.core.settings.usePluginSync = true;
// this.core.settings.useAdvancedMode = true;
// await this.core.saveSettings();
await this.core.services.setting.applyPartial( await this.core.services.setting.applyPartial(
{ {
usePluginSync: true, usePluginSync: true,
+2 -11
View File
@@ -8,7 +8,6 @@ import {
type FilePathWithPrefix, type FilePathWithPrefix,
type LOG_LEVEL, type LOG_LEVEL,
} from "@vrtmrz/livesync-commonlib/compat/common/types"; } from "@vrtmrz/livesync-commonlib/compat/common/types";
import type ObsidianLiveSyncPlugin from "@/main.ts";
import { MARK_DONE } from "@/modules/features/ModuleLog.ts"; import { MARK_DONE } from "@/modules/features/ModuleLog.ts";
import type { LiveSyncCore } from "@/main.ts"; import type { LiveSyncCore } from "@/main.ts";
// import { __$checkInstanceBinding } from "@vrtmrz/livesync-commonlib/compat/dev/checks"; // import { __$checkInstanceBinding } from "@vrtmrz/livesync-commonlib/compat/dev/checks";
@@ -16,13 +15,9 @@ import { createInstanceLogFunction } from "@vrtmrz/livesync-commonlib/compat/ser
let noticeIndex = 0; let noticeIndex = 0;
export abstract class LiveSyncCommands { export abstract class LiveSyncCommands {
/**
* @deprecated This class is deprecated. Please use core
*/
plugin: ObsidianLiveSyncPlugin;
core: LiveSyncCore; core: LiveSyncCore;
get app() { get app() {
return this.plugin.app; return this.services.context.app;
} }
get settings() { get settings() {
return this.core.settings; return this.core.settings;
@@ -34,9 +29,6 @@ export abstract class LiveSyncCommands {
return this.core.services; return this.core.services;
} }
// id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
// return this.plugin.$$id2path(id, entry, stripPrefix);
// }
async path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> { async path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
return await this.services.path.path2id(filename, prefix); return await this.services.path.path2id(filename, prefix);
} }
@@ -45,8 +37,7 @@ export abstract class LiveSyncCommands {
return this.services.path.getPath(entry); return this.services.path.getPath(entry);
} }
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) { constructor(core: LiveSyncCore) {
this.plugin = plugin;
this.core = core; this.core = core;
this.onBindFunction(this.core, this.core.services); this.onBindFunction(this.core, this.core.services);
this._log = createInstanceLogFunction(this.constructor.name, this.services.API); this._log = createInstanceLogFunction(this.constructor.name, this.services.API);
@@ -35,7 +35,7 @@ export class LocalDatabaseMaintenance extends LiveSyncCommands {
} }
onload(): void | Promise<void> { onload(): void | Promise<void> {
// NO OP. // NO OP.
this.plugin.addCommand({ this.services.API.addCommand({
id: "analyse-database", id: "analyse-database",
name: "Analyse Database Usage (advanced)", name: "Analyse Database Usage (advanced)",
icon: "database-search", icon: "database-search",
@@ -47,7 +47,7 @@ export class LocalDatabaseMaintenance extends LiveSyncCommands {
return true; return true;
}, },
}); });
this.plugin.addCommand({ this.services.API.addCommand({
id: "gc-v3", id: "gc-v3",
name: "Garbage Collection V3 (advanced, beta)", name: "Garbage Collection V3 (advanced, beta)",
icon: "trash-2", icon: "trash-2",
@@ -18,10 +18,13 @@ vi.mock("@vrtmrz/livesync-commonlib/compat/common/utils", async (importOriginal)
}); });
vi.mock("@/features/LiveSyncCommands", () => ({ vi.mock("@/features/LiveSyncCommands", () => ({
LiveSyncCommands: class LiveSyncCommands { LiveSyncCommands: class LiveSyncCommands {
core!: { settings: unknown }; core!: { settings: unknown; services: unknown };
get settings() { get settings() {
return this.core.settings; return this.core.settings;
} }
get services() {
return this.core.services;
}
}, },
})); }));
vi.mock("@/common/events", () => ({ vi.mock("@/common/events", () => ({
@@ -76,11 +79,13 @@ describe("LocalDatabaseMaintenance prerequisites", () => {
}; };
const maintenance = Object.create(LocalDatabaseMaintenance.prototype) as LocalDatabaseMaintenance; const maintenance = Object.create(LocalDatabaseMaintenance.prototype) as LocalDatabaseMaintenance;
Object.assign(maintenance, { Object.assign(maintenance, {
plugin: {
addCommand: vi.fn((command) => commands.push(command)),
},
core: { core: {
settings, settings,
services: {
API: {
addCommand: vi.fn((command) => commands.push(command)),
},
},
}, },
_isDatabaseReady: vi.fn(() => true), _isDatabaseReady: vi.fn(() => true),
}); });
+1 -5
View File
@@ -170,11 +170,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
return extraModules; return extraModules;
}, },
(core) => { (core) => {
const addOns = [ const addOns = [new ConfigSync(core), new HiddenFileSync(core), new LocalDatabaseMaintenance(core)];
new ConfigSync(this, core),
new HiddenFileSync(this, core),
new LocalDatabaseMaintenance(this, core),
];
return addOns; return addOns;
}, },
(core) => { (core) => {
+5 -1
View File
@@ -2,7 +2,11 @@ import type { LiveSyncCore } from "@/main";
import type ObsidianLiveSyncPlugin from "@/main"; import type ObsidianLiveSyncPlugin from "@/main";
import { AbstractModule } from "./AbstractModule.ts"; import { AbstractModule } from "./AbstractModule.ts";
export abstract class AbstractObsidianModule extends AbstractModule { export abstract class AbstractObsidianModule extends AbstractModule<LiveSyncCore> {
override get services() {
return this.core.services;
}
get app() { get app() {
return this.plugin.app; return this.plugin.app;
} }
@@ -4,7 +4,8 @@
// Original Implementation is here: https://github.com/remotely-save/remotely-save/blob/28b99557a864ef59c19d2ad96101196e401718f0/src/remoteForS3.ts // Original Implementation is here: https://github.com/remotely-save/remotely-save/blob/28b99557a864ef59c19d2ad96101196e401718f0/src/remoteForS3.ts
import { FetchHttpHandler, type FetchHttpHandlerOptions } from "@smithy/fetch-http-handler"; import { FetchHttpHandler, type FetchHttpHandlerOptions } from "@smithy/fetch-http-handler";
import { HttpRequest, HttpResponse, type HttpHandlerOptions } from "@smithy/protocol-http"; import { HttpRequest, HttpResponse } from "@smithy/protocol-http";
import type { HttpHandlerOptions } from "@smithy/types";
import { buildQueryString } from "@smithy/querystring-builder"; import { buildQueryString } from "@smithy/querystring-builder";
import { requestUrl, type RequestUrlParam } from "@/deps.ts"; import { requestUrl, type RequestUrlParam } from "@/deps.ts";
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions"; import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
+15 -20
View File
@@ -7,7 +7,7 @@ import {
type DatabaseConnectingStatus, type DatabaseConnectingStatus,
type LOG_LEVEL, type LOG_LEVEL,
} from "@vrtmrz/livesync-commonlib/compat/common/types"; } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { cancelTask, scheduleTask } from "octagonal-wheels/concurrency/task"; import { scheduleTask } from "octagonal-wheels/concurrency/task";
import { fireAndForget, isDirty, throttle } from "@vrtmrz/livesync-commonlib/compat/common/utils"; import { fireAndForget, isDirty, throttle } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import { import {
collectingChunks, collectingChunks,
@@ -119,7 +119,7 @@ export class ModuleLog extends AbstractObsidianModule {
statusBarLabels!: ReactiveValue<{ message: string; status: string }>; statusBarLabels!: ReactiveValue<{ message: string; status: string }>;
statusLog = reactiveSource(""); statusLog = reactiveSource("");
activeFileStatus = reactiveSource(""); activeFileStatus = reactiveSource("");
notifies: { [key: string]: { notice: Notice; count: number } } = {}; notifies: { [key: string]: { count: number } } = {};
p2pLogCollector = new P2PLogCollector(this.services.context.events); p2pLogCollector = new P2PLogCollector(this.services.context.events);
observeForLogs() { observeForLogs() {
@@ -407,6 +407,10 @@ export class ModuleLog extends AbstractObsidianModule {
} }
private _allStartOnUnload(): Promise<boolean> { private _allStartOnUnload(): Promise<boolean> {
for (const key of Object.keys(this.notifies)) {
this.services.context.notices.hide(`log:${key}`);
}
this.notifies = {};
if (this.statusDiv) { if (this.statusDiv) {
this.statusDiv.remove(); this.statusDiv.remove();
} }
@@ -559,35 +563,26 @@ ${stringifyYaml(info)}
if (level >= LOG_LEVEL_NOTICE) { if (level >= LOG_LEVEL_NOTICE) {
if (!key) key = messageContent; if (!key) key = messageContent;
if (key in this.notifies) { if (key in this.notifies) {
// @ts-ignore
const isShown = this.notifies[key].notice.noticeEl?.isShown();
if (!isShown) {
this.notifies[key].notice = new Notice(messageContent, 0);
}
cancelTask(`notify-${key}`);
if (key == messageContent) { if (key == messageContent) {
this.notifies[key].count++; this.notifies[key].count++;
this.notifies[key].notice.setMessage(`(${this.notifies[key].count}):${messageContent}`);
} else {
this.notifies[key].notice.setMessage(`${messageContent}`);
} }
} else { } else {
const notify = new Notice(messageContent, 0);
this.notifies[key] = { this.notifies[key] = {
count: 0, count: 0,
notice: notify,
}; };
} }
const timeout = 5000; const timeout = 5000;
if (!key.startsWith("keepalive-") || messageContent.indexOf(MARK_DONE) !== -1) { const shouldExpire = !key.startsWith("keepalive-") || messageContent.indexOf(MARK_DONE) !== -1;
const noticeMessage =
key == messageContent && this.notifies[key].count > 0
? `(${this.notifies[key].count}):${messageContent}`
: messageContent;
this.services.context.notices.show(`log:${key}`, noticeMessage, {
durationMs: shouldExpire ? timeout : false,
});
if (shouldExpire) {
scheduleTask(`notify-${key}`, timeout, () => { scheduleTask(`notify-${key}`, timeout, () => {
const notify = this.notifies[key].notice;
delete this.notifies[key]; delete this.notifies[key];
try {
notify.hide();
} catch {
// NO OP
}
}); });
} }
} }
@@ -3,8 +3,7 @@ import {
type ObsidianLiveSyncSettings, type ObsidianLiveSyncSettings,
type RemoteDBSettings, type RemoteDBSettings,
LOG_LEVEL_NOTICE, LOG_LEVEL_NOTICE,
FLAGMD_REDFLAG2_HR, FlagFilesHumanReadable,
FLAGMD_REDFLAG3_HR,
REMOTE_COUCHDB, REMOTE_COUCHDB,
REMOTE_MINIO, REMOTE_MINIO,
type ConfigLevel, type ConfigLevel,
@@ -874,12 +873,12 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.saveAllDirtySettings(); await this.saveAllDirtySettings();
await Promise.resolve(this.applyAllSettings()); await Promise.resolve(this.applyAllSettings());
if (result == OPTION_FETCH) { if (result == OPTION_FETCH) {
await this.core.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, ""); await this.core.storageAccess.writeFileAuto(FlagFilesHumanReadable.FETCH_ALL, "");
this.services.appLifecycle.scheduleRestart(); this.services.appLifecycle.scheduleRestart();
this.closeSetting(); this.closeSetting();
// await rebuildDB("localOnly"); // await rebuildDB("localOnly");
} else if (result == OPTION_REBUILD_BOTH) { } else if (result == OPTION_REBUILD_BOTH) {
await this.core.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, ""); await this.core.storageAccess.writeFileAuto(FlagFilesHumanReadable.REBUILD_ALL, "");
this.services.appLifecycle.scheduleRestart(); this.services.appLifecycle.scheduleRestart();
this.closeSetting(); this.closeSetting();
} else if (result == OPTION_ONLY_SETTING) { } else if (result == OPTION_ONLY_SETTING) {
@@ -1,6 +1,6 @@
import { EVENT_REQUEST_PERFORM_GC_V3, eventHub } from "@/common/events.ts"; import { EVENT_REQUEST_PERFORM_GC_V3, eventHub } from "@/common/events.ts";
import { LOG_LEVEL_NOTICE, Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger"; import { LOG_LEVEL_NOTICE, Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
import { FlagFilesHumanReadable, FLAGMD_REDFLAG } from "@vrtmrz/livesync-commonlib/compat/common/types"; import { FlagFilesHumanReadable, FlagFilesOriginal } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { fireAndForget } from "@vrtmrz/livesync-commonlib/compat/common/utils"; import { fireAndForget } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator"; import { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts"; import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
@@ -90,7 +90,7 @@ export function paneMaintenance(
.setButtonText("Flag and restart") .setButtonText("Flag and restart")
.setDisabled(false) .setDisabled(false)
.onClick(async () => { .onClick(async () => {
await this.core.storageAccess.writeFileAuto(FLAGMD_REDFLAG, ""); await this.core.storageAccess.writeFileAuto(FlagFilesOriginal.SUSPEND_ALL, "");
this.services.appLifecycle.performRestart(); this.services.appLifecycle.performRestart();
}) })
); );
+8 -18
View File
@@ -1,5 +1,5 @@
import { type App, type Plugin, Notice } from "@/deps"; import { type App, type Plugin } from "@/deps";
import { scheduleTask, memoIfNotExist, memoObject, retrieveMemoObject, disposeMemoObject } from "@/common/utils"; import { scheduleTask } from "@/common/utils";
import { EVENT_PLUGIN_UNLOADED } from "@/common/events"; import { EVENT_PLUGIN_UNLOADED } from "@/common/events";
import { $msg } from "@/common/translation"; import { $msg } from "@/common/translation";
import type { Confirm, ConfirmActionLayout } from "@vrtmrz/livesync-commonlib/compat/interfaces/Confirm"; import type { Confirm, ConfirmActionLayout } from "@vrtmrz/livesync-commonlib/compat/interfaces/Confirm";
@@ -180,30 +180,20 @@ export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceC
a.appendText(afterText); a.appendText(afterText);
}); });
}); });
scheduleTask(popupKey, 1000, async () => { scheduleTask(popupKey, 1000, () => {
if (this.dialogueController.signal.aborted) { if (this.dialogueController.signal.aborted) {
this.popupKeys.delete(popupKey); this.popupKeys.delete(popupKey);
return; return;
} }
const popup = await memoIfNotExist(popupKey, () => new Notice(fragment, 0)); this._context.notices.show(popupKey, fragment, { durationMs });
const isShown = popup?.noticeEl?.isShown(); scheduleTask(`${popupKey}-forget`, durationMs, () => {
if (!isShown) { this.popupKeys.delete(popupKey);
memoObject(popupKey, new Notice(fragment, 0)); });
}
scheduleTask(popupKey + "-close", durationMs, () => this.closePopup(popupKey));
}); });
} }
private closePopup(popupKey: string) { private closePopup(popupKey: string) {
const popup = retrieveMemoObject<Notice>(popupKey); this._context.notices.hide(popupKey);
if (!popup) {
this.popupKeys.delete(popupKey);
return;
}
if (popup.noticeEl?.isShown()) {
popup.hide();
}
disposeMemoObject(popupKey);
this.popupKeys.delete(popupKey); this.popupKeys.delete(popupKey);
} }
@@ -32,7 +32,6 @@ vi.mock("@/deps", () => ({
})); }));
import { EVENT_PLUGIN_UNLOADED } from "@/common/events"; import { EVENT_PLUGIN_UNLOADED } from "@/common/events";
import { memoObject, retrieveMemoObject } from "@/common/utils";
import { createLiveSyncEventHub } from "@vrtmrz/livesync-commonlib/context"; import { createLiveSyncEventHub } from "@vrtmrz/livesync-commonlib/context";
import { ObsidianConfirm } from "./ObsidianConfirm"; import { ObsidianConfirm } from "./ObsidianConfirm";
import type { ObsidianServiceContext } from "./ObsidianServiceContext"; import type { ObsidianServiceContext } from "./ObsidianServiceContext";
@@ -41,8 +40,12 @@ function createConfirm() {
const app = { id: "app" }; const app = { id: "app" };
const plugin = { app }; const plugin = { app };
const events = createLiveSyncEventHub(); const events = createLiveSyncEventHub();
const context = { app, plugin, events } as unknown as ObsidianServiceContext; const notices = {
return { confirm: new ObsidianConfirm(context), events, app, plugin }; show: vi.fn(),
hide: vi.fn(),
};
const context = { app, plugin, events, notices } as unknown as ObsidianServiceContext;
return { confirm: new ObsidianConfirm(context), events, app, plugin, notices };
} }
beforeEach(() => { beforeEach(() => {
@@ -259,19 +262,46 @@ describe("ObsidianConfirm Fancy Kit adapter", () => {
expect(observedSignal?.aborted).toBe(true); expect(observedSignal?.aborted).toBe(true);
}); });
it("closes an active Notice when the plug-in unload event is emitted", () => { it("routes popup display and expiry through the context-owned keyed Notice manager", async () => {
const { confirm, events } = createConfirm(); vi.useFakeTimers();
const popupKey = "popup-remote-size-exceeded"; const popupKey = "popup-remote-size-exceeded";
const popup = { const fragment = {} as DocumentFragment;
hide: vi.fn(), const anchor = { addEventListener: vi.fn() } as unknown as HTMLAnchorElement;
noticeEl: { isShown: vi.fn(() => true) }, const span = {
appendText: vi.fn(),
appendChild: vi.fn(),
createEl: vi.fn((_tag, _options, callback: (element: HTMLAnchorElement) => void) => {
callback(anchor);
return anchor;
}),
}; };
memoObject(popupKey, popup); vi.stubGlobal("createFragment", (callback: (document: unknown) => void) => {
callback({
createSpan: (_options: unknown, build: (element: typeof span) => void) => build(span),
});
return fragment;
});
const { confirm, notices } = createConfirm();
try {
confirm.askInPopup("remote-size-exceeded", "Review {HERE} details", vi.fn(), 20_000);
await vi.advanceTimersByTimeAsync(1_000);
expect(notices.show).toHaveBeenCalledWith(popupKey, fragment, { durationMs: 20_000 });
} finally {
await vi.runAllTimersAsync();
vi.useRealTimers();
vi.unstubAllGlobals();
}
});
it("closes an owned keyed Notice when the plug-in unload event is emitted", () => {
const { confirm, events, notices } = createConfirm();
const popupKey = "popup-remote-size-exceeded";
(confirm as unknown as { popupKeys: Set<string> }).popupKeys.add(popupKey); (confirm as unknown as { popupKeys: Set<string> }).popupKeys.add(popupKey);
events.emitEvent(EVENT_PLUGIN_UNLOADED); events.emitEvent(EVENT_PLUGIN_UNLOADED);
expect(popup.hide).toHaveBeenCalledOnce(); expect(notices.hide).toHaveBeenCalledWith(popupKey);
expect(retrieveMemoObject(popupKey)).toBe(false);
}); });
}); });
+10 -1
View File
@@ -4,19 +4,28 @@ import { ServiceContext } from "@vrtmrz/livesync-commonlib/context";
import { eventHub } from "@/common/events"; import { eventHub } from "@/common/events";
import { translateLiveSyncMessage } from "@/common/translation"; import { translateLiveSyncMessage } from "@/common/translation";
import type { ObsidianNoticeGroups } from "./ObsidianNoticeGroups"; import type { ObsidianNoticeGroups } from "./ObsidianNoticeGroups";
import type { KeyedNoticeManager } from "@vrtmrz/obsidian-plugin-kit/notice";
/** Host capabilities owned by one Self-hosted LiveSync plug-in instance. */ /** Host capabilities owned by one Self-hosted LiveSync plug-in instance. */
export class ObsidianServiceContext extends ServiceContext { export class ObsidianServiceContext extends ServiceContext {
app: App; app: App;
plugin: Plugin; plugin: Plugin;
liveSyncPlugin: ObsidianLiveSyncPlugin; liveSyncPlugin: ObsidianLiveSyncPlugin;
readonly notices: KeyedNoticeManager;
readonly noticeGroups: ObsidianNoticeGroups; readonly noticeGroups: ObsidianNoticeGroups;
constructor(app: App, plugin: Plugin, liveSyncPlugin: ObsidianLiveSyncPlugin, noticeGroups: ObsidianNoticeGroups) { constructor(
app: App,
plugin: Plugin,
liveSyncPlugin: ObsidianLiveSyncPlugin,
notices: KeyedNoticeManager,
noticeGroups: ObsidianNoticeGroups
) {
super({ events: eventHub, translate: translateLiveSyncMessage }); super({ events: eventHub, translate: translateLiveSyncMessage });
this.app = app; this.app = app;
this.plugin = plugin; this.plugin = plugin;
this.liveSyncPlugin = liveSyncPlugin; this.liveSyncPlugin = liveSyncPlugin;
this.notices = notices;
this.noticeGroups = noticeGroups; this.noticeGroups = noticeGroups;
} }
} }
@@ -13,8 +13,9 @@ describe("ObsidianServiceContext contract", () => {
const app = {} as Parameters[0]; const app = {} as Parameters[0];
const plugin = {} as Parameters[1]; const plugin = {} as Parameters[1];
const liveSyncPlugin = {} as Parameters[2]; const liveSyncPlugin = {} as Parameters[2];
const noticeGroups = {} as Parameters[3]; const notices = {} as Parameters[3];
const context = new ObsidianServiceContext(app, plugin, liveSyncPlugin, noticeGroups); const noticeGroups = {} as Parameters[4];
const context = new ObsidianServiceContext(app, plugin, liveSyncPlugin, notices, noticeGroups);
expect(observeServiceContext(context, TRANSLATION_KEY)).toEqual({ expect(observeServiceContext(context, TRANSLATION_KEY)).toEqual({
translation: translateLiveSyncMessage(TRANSLATION_KEY), translation: translateLiveSyncMessage(TRANSLATION_KEY),
@@ -24,6 +25,7 @@ describe("ObsidianServiceContext contract", () => {
expect(context.app).toBe(app); expect(context.app).toBe(app);
expect(context.plugin).toBe(plugin); expect(context.plugin).toBe(plugin);
expect(context.liveSyncPlugin).toBe(liveSyncPlugin); expect(context.liveSyncPlugin).toBe(liveSyncPlugin);
expect(context.notices).toBe(notices);
expect(context.noticeGroups).toBe(noticeGroups); expect(context.noticeGroups).toBe(noticeGroups);
}); });
}); });
+4 -1
View File
@@ -26,14 +26,16 @@ import { createScreenWakeLockManager } from "octagonal-wheels/browser/wakeLock";
import { PouchDB } from "@vrtmrz/livesync-commonlib/compat/pouchdb/pouchdb-browser"; import { PouchDB } from "@vrtmrz/livesync-commonlib/compat/pouchdb/pouchdb-browser";
import { OpenKeyValueDatabase } from "@/common/KeyValueDB"; import { OpenKeyValueDatabase } from "@/common/KeyValueDB";
import { ObsidianNoticeGroupManager } from "./ObsidianNoticeGroups"; import { ObsidianNoticeGroupManager } from "./ObsidianNoticeGroups";
import { KeyedNoticeManager } from "@vrtmrz/obsidian-plugin-kit/notice";
import { setLang } from "@/common/translation"; import { setLang } from "@/common/translation";
// InjectableServiceHub // InjectableServiceHub
export class ObsidianServiceHub extends InjectableServiceHub<ObsidianServiceContext> { export class ObsidianServiceHub extends InjectableServiceHub<ObsidianServiceContext> {
constructor(plugin: ObsidianLiveSyncPlugin) { constructor(plugin: ObsidianLiveSyncPlugin) {
const notices = new KeyedNoticeManager();
const noticeGroups = new ObsidianNoticeGroupManager(); const noticeGroups = new ObsidianNoticeGroupManager();
const context = new ObsidianServiceContext(plugin.app, plugin, plugin, noticeGroups); const context = new ObsidianServiceContext(plugin.app, plugin, plugin, notices, noticeGroups);
const API = new ObsidianAPIService(context); const API = new ObsidianAPIService(context);
const conflict = new ObsidianConflictService(context); const conflict = new ObsidianConflictService(context);
@@ -66,6 +68,7 @@ export class ObsidianServiceHub extends InjectableServiceHub<ObsidianServiceCont
const screenWakeLock = createScreenWakeLockManager(); const screenWakeLock = createScreenWakeLockManager();
appLifecycle.onUnload.addHandler(async () => { appLifecycle.onUnload.addHandler(async () => {
await screenWakeLock.dispose(); await screenWakeLock.dispose();
notices.dispose();
noticeGroups.dispose(); noticeGroups.dispose();
return true; return true;
}); });
+1
View File
@@ -1,4 +1,5 @@
import { $msg } from "@/common/translation"; import { $msg } from "@/common/translation";
import type PouchDB from "pouchdb-core";
import type { import type {
FilePath, FilePath,
FilePathWithPrefix, FilePathWithPrefix,