chore(client): address requested changes

This commit is contained in:
Elian Doran
2026-04-11 00:16:10 +03:00
parent 5f68958aa7
commit 18aec84be5
6 changed files with 75 additions and 96 deletions

View File

@@ -185,13 +185,18 @@ export async function buildConfig(opts: BuildEditorOptions): Promise<EditorConfi
const suggestion = item as Suggestion;
const itemElement = document.createElement("button");
const iconElement = document.createElement("span");
// Choose appropriate icon based on action
let iconClass = suggestion.icon ?? "bx bx-note";
if (suggestion.action === "create-note") {
iconClass = "bx bx-plus";
}
iconElement.className = iconClass;
itemElement.innerHTML = `<span class="${iconClass}"></span> ${suggestion.highlightedNotePathTitle} `;
itemElement.append(iconElement, document.createTextNode(" "));
const titleContainer = document.createElement("span");
titleContainer.innerHTML = suggestion.highlightedNotePathTitle ?? "";
itemElement.append(...titleContainer.childNodes, document.createTextNode(" "));
return itemElement;
},

View File

@@ -2,29 +2,26 @@ import { describe, expect, it } from "vitest";
import { migrateSyncTimeoutFromMilliseconds } from "./options_init.js";
describe("migrateSyncTimeoutFromMilliseconds", () => {
it("returns null when no migration is needed", () => {
// Values < 1000 are already in seconds/minutes format
it("returns null when no migration is needed (values < 1000 are already in seconds)", () => {
expect(migrateSyncTimeoutFromMilliseconds(120)).toBeNull();
expect(migrateSyncTimeoutFromMilliseconds(500)).toBeNull();
expect(migrateSyncTimeoutFromMilliseconds(999)).toBeNull();
expect(migrateSyncTimeoutFromMilliseconds(NaN)).toBeNull();
});
it("migrates to minutes when divisible by 60", () => {
expect(migrateSyncTimeoutFromMilliseconds(60000)).toEqual({ value: 1, scale: 60 }); // 1 minute
expect(migrateSyncTimeoutFromMilliseconds(120000)).toEqual({ value: 2, scale: 60 }); // 2 minutes
expect(migrateSyncTimeoutFromMilliseconds(300000)).toEqual({ value: 5, scale: 60 }); // 5 minutes
expect(migrateSyncTimeoutFromMilliseconds(3600000)).toEqual({ value: 60, scale: 60 }); // 60 minutes
});
it("converts milliseconds to seconds and sets display scale", () => {
// Value is always stored in seconds; scale is for display only
// Divisible by 60 → display as minutes
expect(migrateSyncTimeoutFromMilliseconds(60000)).toEqual({ value: 60, scale: 60 }); // 60s, display as 1 min
expect(migrateSyncTimeoutFromMilliseconds(120000)).toEqual({ value: 120, scale: 60 }); // 120s, display as 2 min
expect(migrateSyncTimeoutFromMilliseconds(3600000)).toEqual({ value: 3600, scale: 60 }); // 3600s, display as 60 min
it("migrates to seconds when not divisible by 60", () => {
expect(migrateSyncTimeoutFromMilliseconds(1000)).toEqual({ value: 1, scale: 1 }); // 1 second
expect(migrateSyncTimeoutFromMilliseconds(45000)).toEqual({ value: 45, scale: 1 }); // 45 seconds
expect(migrateSyncTimeoutFromMilliseconds(90000)).toEqual({ value: 90, scale: 1 }); // 90 seconds
expect(migrateSyncTimeoutFromMilliseconds(150000)).toEqual({ value: 150, scale: 1 }); // 150 seconds
});
// Not divisible by 60 → display as seconds
expect(migrateSyncTimeoutFromMilliseconds(1000)).toEqual({ value: 1, scale: 1 });
expect(migrateSyncTimeoutFromMilliseconds(45000)).toEqual({ value: 45, scale: 1 });
expect(migrateSyncTimeoutFromMilliseconds(90000)).toEqual({ value: 90, scale: 1 });
it("rounds milliseconds to nearest second", () => {
// Rounds to nearest second
expect(migrateSyncTimeoutFromMilliseconds(120500)).toEqual({ value: 121, scale: 1 });
});
});

View File

@@ -66,15 +66,49 @@ async function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts =
optionService.createOption("textNoteEditorType", "ckeditor-classic", true);
optionService.createOption("syncServerHost", opts.syncServerHost || "", false);
optionService.createOption("syncServerTimeout", "2", false); // 2 minutes (with default scale of 60)
optionService.createOption("syncServerTimeout", "120", false); // 120 seconds (2 minutes)
optionService.createOption("syncProxy", opts.syncProxy || "", false);
}
/**
* Migrates a sync timeout value from milliseconds to seconds.
* Values >= 1000 are assumed to be in milliseconds (since 1000+ seconds = 16+ minutes is unlikely).
* TimeSelector stores values in seconds; the scale is only used for display.
*
* @returns The value in seconds and preferred display scale, or null if no migration is needed.
*/
export function migrateSyncTimeoutFromMilliseconds(milliseconds: number): { value: number; scale: number } | null {
if (isNaN(milliseconds) || milliseconds < 1000) {
return null;
}
const seconds = Math.round(milliseconds / 1000);
// Value is always stored in seconds; scale determines display unit
if (seconds >= 60 && seconds % 60 === 0) {
return { value: seconds, scale: 60 }; // display as minutes
}
return { value: seconds, scale: 1 }; // display as seconds
}
/**
* Contains all the default options that must be initialized on new and existing databases (at startup). The value can also be determined based on other options, provided they have already been initialized.
*/
const defaultOptions: DefaultOption[] = [
{ name: "syncServerTimeoutTimeScale", value: "60", isSynced: false }, // default to Minutes
{
name: "syncServerTimeoutTimeScale",
value: (optionsMap) => {
const timeout = parseInt(optionsMap.syncServerTimeout || "120", 10);
const migrated = migrateSyncTimeoutFromMilliseconds(timeout);
if (migrated) {
optionService.setOption("syncServerTimeout", String(migrated.value));
log.info(`Migrated syncServerTimeout from ${timeout}ms to ${migrated.value}s`);
return String(migrated.scale);
}
return "60"; // default to minutes
},
isSynced: false
},
{ name: "revisionSnapshotTimeInterval", value: "600", isSynced: true },
{ name: "revisionSnapshotTimeIntervalTimeScale", value: "60", isSynced: true }, // default to Minutes
{ name: "revisionSnapshotNumberLimit", value: "-1", isSynced: true },
@@ -256,36 +290,6 @@ function initStartupOptions() {
])
);
}
// Migrate syncServerTimeout from milliseconds to seconds/minutes (for existing installations)
const syncTimeout = parseInt(optionsMap.syncServerTimeout, 10);
const migrated = migrateSyncTimeoutFromMilliseconds(syncTimeout);
if (migrated) {
optionService.setOption("syncServerTimeout", String(migrated.value));
optionService.setOption("syncServerTimeoutTimeScale", String(migrated.scale));
const unit = migrated.scale === 60 ? "minutes" : "seconds";
log.info(`Migrated syncServerTimeout from ${syncTimeout}ms to ${migrated.value} ${unit}`);
}
}
/**
* Migrates a sync timeout value from milliseconds to a value/scale pair.
* Values >= 1000 are assumed to be in milliseconds (since 1000+ seconds = 16+ minutes is unlikely).
*
* @returns The migrated value and scale, or null if no migration is needed.
*/
export function migrateSyncTimeoutFromMilliseconds(milliseconds: number): { value: number; scale: number } | null {
if (isNaN(milliseconds) || milliseconds < 1000) {
return null;
}
const seconds = Math.round(milliseconds / 1000);
// If divisible by 60, store as minutes; otherwise store as seconds
if (seconds >= 60 && seconds % 60 === 0) {
return { value: seconds / 60, scale: 60 };
}
return { value: seconds, scale: 1 };
}
function getKeyboardDefaultOptions() {

View File

@@ -10,7 +10,6 @@ import syncOptions from "./sync_options.js";
describe("syncOptions.getSyncTimeout", () => {
beforeEach(() => {
// Reset config to empty
(config as any).Sync = {};
});
@@ -18,57 +17,32 @@ describe("syncOptions.getSyncTimeout", () => {
vi.clearAllMocks();
});
it("uses database value × scale when no config override", () => {
vi.mocked(optionService.getOption).mockImplementation((name: string) => {
if (name === "syncServerTimeout") return "2";
if (name === "syncServerTimeoutTimeScale") return "60";
return "";
});
it("converts database value from seconds to milliseconds", () => {
// TimeSelector stores value in seconds (displayed value × scale)
// Scale is UI-only, not used in backend calculation
vi.mocked(optionService.getOption).mockReturnValue("120"); // 120 seconds = 2 minutes
expect(syncOptions.getSyncTimeout()).toBe(120000);
expect(syncOptions.getSyncTimeout()).toBe(120000); // 2 × 60 × 1000 = 2 minutes
});
it("supports different time scales from database", () => {
// 30 seconds
vi.mocked(optionService.getOption).mockImplementation((name: string) => {
if (name === "syncServerTimeout") return "30";
if (name === "syncServerTimeoutTimeScale") return "1";
return "";
});
vi.mocked(optionService.getOption).mockReturnValue("30"); // 30 seconds
expect(syncOptions.getSyncTimeout()).toBe(30000);
// 1 hour
vi.mocked(optionService.getOption).mockImplementation((name: string) => {
if (name === "syncServerTimeout") return "1";
if (name === "syncServerTimeoutTimeScale") return "3600";
return "";
});
vi.mocked(optionService.getOption).mockReturnValue("3600"); // 3600 seconds = 1 hour
expect(syncOptions.getSyncTimeout()).toBe(3600000);
});
it("treats config override as raw milliseconds (ignores db scale)", () => {
(config as any).Sync = { syncServerTimeout: "60000" };
it("treats config override as raw milliseconds for backward compatibility", () => {
(config as any).Sync = { syncServerTimeout: "60000" }; // 60 seconds in ms
// Even if db has a different scale, config value is treated as raw ms
vi.mocked(optionService.getOption).mockImplementation((name: string) => {
if (name === "syncServerTimeout") return "5";
if (name === "syncServerTimeoutTimeScale") return "3600"; // hours
return "";
});
expect(syncOptions.getSyncTimeout()).toBe(60000); // 60 seconds, not 5 hours
// Config value takes precedence, db value is ignored
vi.mocked(optionService.getOption).mockReturnValue("9999");
expect(syncOptions.getSyncTimeout()).toBe(60000);
});
it("uses safe defaults for invalid database values", () => {
vi.mocked(optionService.getOption).mockImplementation(() => "");
it("uses safe defaults for invalid values", () => {
vi.mocked(optionService.getOption).mockReturnValue("");
expect(syncOptions.getSyncTimeout()).toBe(120000); // default 120 seconds
// Defaults: value=2, scale=60 → 120000ms
expect(syncOptions.getSyncTimeout()).toBe(120000);
});
it("uses safe default for invalid config override", () => {
(config as any).Sync = { syncServerTimeout: "invalid" };
expect(syncOptions.getSyncTimeout()).toBe(120000); // fallback to 120000
expect(syncOptions.getSyncTimeout()).toBe(120000); // fallback for invalid config
});
});

View File

@@ -27,7 +27,7 @@ export default {
// and we need to override it with config from config.ini
return !!syncServerHost && syncServerHost !== "disabled";
},
// Value is stored with a time scale, convert to milliseconds for use.
// Value is stored in seconds (TimeSelector saves displayed value × scale).
// Config file overrides are treated as raw milliseconds for backward compatibility.
getSyncTimeout: () => {
const configValue = config["Sync"]?.syncServerTimeout;
@@ -35,10 +35,9 @@ export default {
// Config override: treat as raw milliseconds (backward compatible)
return parseInt(configValue, 10) || 120000;
}
// Database option: use value × scale (with safe defaults)
const value = parseInt(optionService.getOption("syncServerTimeout"), 10) || 2;
const scale = parseInt(optionService.getOption("syncServerTimeoutTimeScale"), 10) || 60;
return value * scale * 1000;
// Database option: stored in seconds, convert to milliseconds
const seconds = parseInt(optionService.getOption("syncServerTimeout"), 10) || 120;
return seconds * 1000;
},
getSyncProxy: () => get("syncProxy")
};

View File

@@ -14,7 +14,7 @@ Note that <a class="reference-link" href="Synchronization.md">Synchronization</
## Downloading backup
You can download a existing backup by going to Settings > Backup > Existing backups > Download
You can download an existing backup by going to Settings > Backup > Existing backups > Download
## Restoring backup