mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-09 02:42:30 +00:00
Merge reviewed Customisation Sync fixes for Russian catalogue validation
This commit is contained in:
@@ -52,7 +52,7 @@ vi.mock("@/common/obsidianCommunityPlugins.ts", () => ({
|
||||
}));
|
||||
|
||||
import { cancelTask } from "@/common/utils.ts";
|
||||
import { ConfigSync } from "./CmdConfigSync";
|
||||
import { ConfigSync, PluginDataExDisplayV2, type IPluginDataExDisplay } from "./CmdConfigSync";
|
||||
|
||||
describe("ConfigSync commands", () => {
|
||||
it("shows the Customisation Sync command only whilst the feature is enabled", () => {
|
||||
@@ -111,3 +111,25 @@ describe("ConfigSync commands", () => {
|
||||
expect(periodicPluginSweepProcessor.disable).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe("PluginDataExDisplayV2", () => {
|
||||
const entryWithModifiedTimes = (mtimes: number[]) =>
|
||||
new PluginDataExDisplayV2({
|
||||
documentPath: "ix:device/plugin_main/example",
|
||||
category: "PLUGIN_MAIN",
|
||||
name: "example",
|
||||
term: "device",
|
||||
files: mtimes.map((mtime, index) => ({ filename: `file-${index}`, mtime, data: [] })),
|
||||
} as unknown as IPluginDataExDisplay);
|
||||
|
||||
it("averages millisecond modification times without truncating them to 32 bits", () => {
|
||||
const mtime = Date.UTC(2026, 8, 15);
|
||||
expect(entryWithModifiedTimes([mtime, mtime, mtime]).mtime).toBe(mtime);
|
||||
});
|
||||
|
||||
it("keeps a newer copy newer than an older one", () => {
|
||||
const newer = entryWithModifiedTimes([Date.UTC(2026, 8, 15)]);
|
||||
const older = entryWithModifiedTimes([Date.UTC(2026, 8, 2)]);
|
||||
expect(newer.mtime).toBeGreaterThan(older.mtime);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -368,7 +368,8 @@ export class PluginDataExDisplayV2 {
|
||||
return this._version;
|
||||
}
|
||||
get mtime(): number {
|
||||
return ~~this.files.reduce((a, b) => a + b.mtime, 0) / this.files.length;
|
||||
// Millisecond timestamps exceed 32 bits, so `~~` would wrap them.
|
||||
return Math.floor(this.files.reduce((a, b) => a + b.mtime, 0) / this.files.length);
|
||||
}
|
||||
}
|
||||
export type PluginDataEx = {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// import { askString } from "../../common/utils";
|
||||
import { Menu } from "@/deps.ts";
|
||||
import { $msg as translateMessage } from "@/common/translation";
|
||||
import { selectSourceTerms } from "./PluginTerms.ts";
|
||||
|
||||
export let list: IPluginDataExDisplay[] = [];
|
||||
export let thisTerm = "";
|
||||
@@ -182,24 +183,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTerms(list: IPluginDataExDisplay[], selectNewest: boolean, isMaintenanceMode: boolean) {
|
||||
async function updateTerms(
|
||||
list: IPluginDataExDisplay[],
|
||||
selectNewest: boolean,
|
||||
isMaintenanceMode: boolean,
|
||||
hideNotApplicable: boolean
|
||||
) {
|
||||
const local = list.find((e) => e.term == thisTerm);
|
||||
// selected = "";
|
||||
if (isMaintenanceMode) {
|
||||
terms = [...new Set(list.map((e) => e.term))];
|
||||
} else if (hideNotApplicable) {
|
||||
const termsTmp = [];
|
||||
const wk = [...new Set(list.map((e) => e.term))];
|
||||
for (const termName of wk) {
|
||||
const remote = list.find((e) => e.term == termName);
|
||||
if ((await comparePlugin(local, remote)).canApply) {
|
||||
termsTmp.push(termName);
|
||||
}
|
||||
}
|
||||
terms = [...termsTmp];
|
||||
} else {
|
||||
terms = [...new Set(list.map((e) => e.term))].filter((e) => e != thisTerm);
|
||||
}
|
||||
terms = await selectSourceTerms(
|
||||
list,
|
||||
thisTerm,
|
||||
{ isMaintenanceMode, hideNotApplicable },
|
||||
async (local, remote) => (await comparePlugin(local, remote)).canApply
|
||||
);
|
||||
let newest: IPluginDataExDisplay | undefined = local;
|
||||
if (selectNewest) {
|
||||
for (const term of terms) {
|
||||
@@ -230,7 +227,7 @@
|
||||
}
|
||||
// currentSelectNewest = selectNewest;
|
||||
}
|
||||
updateTerms(list, doSelectNewest, isMaintenanceMode);
|
||||
updateTerms(list, doSelectNewest, isMaintenanceMode, hideNotApplicable);
|
||||
currentSelectNewest = selectNewest;
|
||||
}
|
||||
$: {
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
export let core :LiveSyncBaseCore;
|
||||
// $: core = plugin.core;
|
||||
|
||||
$: hideNotApplicable = false;
|
||||
// Comparing every copy is only worth it while the user asks to hide the unchanged ones.
|
||||
$: hideNotApplicable = hideEven;
|
||||
$: thisTerm = core.services.setting.getDeviceAndVaultName();
|
||||
|
||||
const addOn = core.getAddOn<ConfigSync>(ConfigSync.name)!;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { IPluginDataExDisplay } from "./CmdConfigSync.ts";
|
||||
|
||||
export type CanApplyFrom = (
|
||||
local: IPluginDataExDisplay | undefined,
|
||||
remote: IPluginDataExDisplay | undefined
|
||||
) => Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Devices offered as sources for one item in the Customisation Sync dialogue.
|
||||
*
|
||||
* Maintenance mode lists every device, including this one. Otherwise every other
|
||||
* device that has the item is listed; with `hideNotApplicable`, only devices whose
|
||||
* copy can actually be applied remain, so an item that is the same everywhere ends
|
||||
* up with no source and is shown as "All the same or non-existent".
|
||||
*/
|
||||
export async function selectSourceTerms(
|
||||
list: IPluginDataExDisplay[],
|
||||
thisTerm: string,
|
||||
options: { isMaintenanceMode: boolean; hideNotApplicable: boolean },
|
||||
canApplyFrom: CanApplyFrom
|
||||
): Promise<string[]> {
|
||||
const terms = [...new Set(list.map((e) => e.term))];
|
||||
if (options.isMaintenanceMode) return terms;
|
||||
if (!options.hideNotApplicable) return terms.filter((term) => term != thisTerm);
|
||||
const local = list.find((e) => e.term == thisTerm);
|
||||
const applicable: string[] = [];
|
||||
for (const term of terms) {
|
||||
const remote = list.find((e) => e.term == term);
|
||||
if (await canApplyFrom(local, remote)) applicable.push(term);
|
||||
}
|
||||
return applicable;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { IPluginDataExDisplay } from "./CmdConfigSync.ts";
|
||||
import { selectSourceTerms } from "./PluginTerms.ts";
|
||||
|
||||
const copyOn = (term: string) => ({ term, files: [] }) as unknown as IPluginDataExDisplay;
|
||||
const list = [copyOn("desktop"), copyOn("phone"), copyOn("tablet")];
|
||||
const differsOn =
|
||||
(...terms: string[]) =>
|
||||
(_local: IPluginDataExDisplay | undefined, remote: IPluginDataExDisplay | undefined) =>
|
||||
Promise.resolve(terms.includes(remote?.term ?? ""));
|
||||
|
||||
describe("selectSourceTerms", () => {
|
||||
it("offers every other device without comparing copies by default", async () => {
|
||||
const canApplyFrom = vi.fn(differsOn());
|
||||
const terms = await selectSourceTerms(
|
||||
list,
|
||||
"desktop",
|
||||
{ isMaintenanceMode: false, hideNotApplicable: false },
|
||||
canApplyFrom
|
||||
);
|
||||
expect(terms).toEqual(["phone", "tablet"]);
|
||||
expect(canApplyFrom).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("offers every device, including this one, in maintenance mode", async () => {
|
||||
const terms = await selectSourceTerms(
|
||||
list,
|
||||
"desktop",
|
||||
{ isMaintenanceMode: true, hideNotApplicable: true },
|
||||
differsOn()
|
||||
);
|
||||
expect(terms).toEqual(["desktop", "phone", "tablet"]);
|
||||
});
|
||||
|
||||
it("leaves out devices whose copy is the same when hiding items that are not applicable", async () => {
|
||||
const terms = await selectSourceTerms(
|
||||
list,
|
||||
"desktop",
|
||||
{ isMaintenanceMode: false, hideNotApplicable: true },
|
||||
differsOn("tablet")
|
||||
);
|
||||
expect(terms).toEqual(["tablet"]);
|
||||
});
|
||||
|
||||
it("offers no source for an item that is the same on every device", async () => {
|
||||
const terms = await selectSourceTerms(
|
||||
list,
|
||||
"desktop",
|
||||
{ isMaintenanceMode: false, hideNotApplicable: true },
|
||||
differsOn()
|
||||
);
|
||||
expect(terms).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user