mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-01 08:17:07 +00:00
Separate declarative root group identity from display
This commit is contained in:
@@ -140,6 +140,12 @@ by the catalogue, while the imperative renderer continues to pass the same
|
|||||||
emoji to its existing menu button. This preserves the established visual
|
emoji to its existing menu button. This preserves the established visual
|
||||||
identity without adding host-DOM manipulation.
|
identity without adding host-DOM manipulation.
|
||||||
|
|
||||||
|
`SettingDefinitionGroup` likewise exposes only a string heading. Root groups
|
||||||
|
therefore have semantic identifiers whose catalogue entries keep their emoji
|
||||||
|
and late-translated names separate. The adapter combines those fields only
|
||||||
|
when constructing the Obsidian definition, so callers select a group by its
|
||||||
|
identifier instead of repeating presentation strings.
|
||||||
|
|
||||||
### Compose the native landing page around common tasks
|
### Compose the native landing page around common tasks
|
||||||
|
|
||||||
The declarative root is a composition of native groups and catalogue pages,
|
The declarative root is a composition of native groups and catalogue pages,
|
||||||
|
|||||||
@@ -64,7 +64,9 @@ import {
|
|||||||
createExtraMenuSettingDefinitions,
|
createExtraMenuSettingDefinitions,
|
||||||
createGeneralSettingDefinitionGroups,
|
createGeneralSettingDefinitionGroups,
|
||||||
createSettingsPageCatalogue,
|
createSettingsPageCatalogue,
|
||||||
|
getSettingsRootGroupEntry,
|
||||||
type SettingsPageEntry,
|
type SettingsPageEntry,
|
||||||
|
type SettingsRootGroupId,
|
||||||
} from "./SettingsPageCatalogue.ts";
|
} from "./SettingsPageCatalogue.ts";
|
||||||
import { createAdvancedSettingSpecGroups } from "./AdvancedSettingSpecs.ts";
|
import { createAdvancedSettingSpecGroups } from "./AdvancedSettingSpecs.ts";
|
||||||
import { isValidSettingSpecValue, type SettingSpec } from "./SettingSpec.ts";
|
import { isValidSettingSpecValue, type SettingSpec } from "./SettingSpec.ts";
|
||||||
@@ -73,6 +75,7 @@ import type {
|
|||||||
SettingDefinitionGroup,
|
SettingDefinitionGroup,
|
||||||
SettingDefinitionItem,
|
SettingDefinitionItem,
|
||||||
SettingDefinitionPage,
|
SettingDefinitionPage,
|
||||||
|
SettingGroupItem,
|
||||||
} from "obsidian";
|
} from "obsidian";
|
||||||
import { createExtraMenuSettingSpecGroup, createGeneralSettingSpecGroups } from "./GeneralSettingSpecs.ts";
|
import { createExtraMenuSettingSpecGroup, createGeneralSettingSpecGroups } from "./GeneralSettingSpecs.ts";
|
||||||
import { SetupManager } from "@/modules/features/SetupManager.ts";
|
import { SetupManager } from "@/modules/features/SetupManager.ts";
|
||||||
@@ -785,7 +788,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
if (!appearance || !logging) {
|
if (!appearance || !logging) {
|
||||||
throw new Error("General settings must define Appearance and Logging groups");
|
throw new Error("General settings must define Appearance and Logging groups");
|
||||||
}
|
}
|
||||||
return this.createPageGroup(`⚙️ ${$msg("obsidianLiveSyncSettingTab.panelGeneralSettings")}`, [
|
return this.createRootGroup("general-settings", [
|
||||||
{
|
{
|
||||||
type: "page",
|
type: "page",
|
||||||
name: `🎨 ${appearance.heading}`,
|
name: `🎨 ${appearance.heading}`,
|
||||||
@@ -809,53 +812,44 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createQuickSetupGroup(): SettingDefinitionGroup {
|
private createQuickSetupGroup(): SettingDefinitionGroup {
|
||||||
return {
|
return this.createRootGroup("quick-setup", [
|
||||||
type: "group",
|
{
|
||||||
heading: `🧙♂️ ${$msg("obsidianLiveSyncSettingTab.titleQuickSetup")}`,
|
name: $msg("obsidianLiveSyncSettingTab.nameConnectSetupURI"),
|
||||||
items: [
|
desc: $msg("obsidianLiveSyncSettingTab.descConnectSetupURI"),
|
||||||
{
|
action: () => this.requestOpenSetupURI(),
|
||||||
name: $msg("obsidianLiveSyncSettingTab.nameConnectSetupURI"),
|
},
|
||||||
desc: $msg("obsidianLiveSyncSettingTab.descConnectSetupURI"),
|
{
|
||||||
action: () => this.requestOpenSetupURI(),
|
name: $msg("Rerun Onboarding Wizard"),
|
||||||
},
|
desc: $msg("Rerun the onboarding wizard to set up Self-hosted LiveSync again."),
|
||||||
{
|
action: () => fireAndForget(async () => await this.rerunOnboardingWizard()),
|
||||||
name: $msg("Rerun Onboarding Wizard"),
|
},
|
||||||
desc: $msg("Rerun the onboarding wizard to set up Self-hosted LiveSync again."),
|
{
|
||||||
action: () => fireAndForget(async () => await this.rerunOnboardingWizard()),
|
name: $msg("obsidianLiveSyncSettingTab.nameEnableLiveSync"),
|
||||||
},
|
desc: $msg("obsidianLiveSyncSettingTab.descEnableLiveSync"),
|
||||||
{
|
visible: () => !this.isConfiguredAs("isConfigured", true),
|
||||||
name: $msg("obsidianLiveSyncSettingTab.nameEnableLiveSync"),
|
action: () => fireAndForget(async () => await this.enableLiveSyncFromSettings()),
|
||||||
desc: $msg("obsidianLiveSyncSettingTab.descEnableLiveSync"),
|
},
|
||||||
visible: () => !this.isConfiguredAs("isConfigured", true),
|
]);
|
||||||
action: () => fireAndForget(async () => await this.enableLiveSyncFromSettings()),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createSynchronisationGroup(pages: SettingDefinitionPage[]): SettingDefinitionGroup {
|
private createRootGroup(
|
||||||
return this.createPageGroup(`🔄 ${$msg("obsidianLiveSyncSettingTab.titleSynchronisation")}`, pages);
|
id: SettingsRootGroupId,
|
||||||
}
|
items: SettingGroupItem[],
|
||||||
|
|
||||||
private createPageGroup(
|
|
||||||
heading: string,
|
|
||||||
pages: SettingDefinitionPage[],
|
|
||||||
visible?: () => boolean
|
visible?: () => boolean
|
||||||
): SettingDefinitionGroup {
|
): SettingDefinitionGroup {
|
||||||
|
const { icon, name } = getSettingsRootGroupEntry(id);
|
||||||
return {
|
return {
|
||||||
type: "group",
|
type: "group",
|
||||||
heading,
|
heading: `${icon} ${name()}`,
|
||||||
items: pages,
|
items,
|
||||||
...(visible ? { visible } : {}),
|
...(visible ? { visible } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private createSetupOtherDevicesGroup(): SettingDefinitionGroup {
|
private createSetupOtherDevicesGroup(): SettingDefinitionGroup {
|
||||||
return {
|
return this.createRootGroup(
|
||||||
type: "group",
|
"setup-other-devices",
|
||||||
heading: `📲 ${$msg("obsidianLiveSyncSettingTab.titleSetupOtherDevices")}`,
|
[
|
||||||
visible: () => this.isConfiguredAs("isConfigured", true),
|
|
||||||
items: [
|
|
||||||
{
|
{
|
||||||
name: $msg("obsidianLiveSyncSettingTab.nameCopySetupURI"),
|
name: $msg("obsidianLiveSyncSettingTab.nameCopySetupURI"),
|
||||||
desc: $msg("obsidianLiveSyncSettingTab.descCopySetupURI"),
|
desc: $msg("obsidianLiveSyncSettingTab.descCopySetupURI"),
|
||||||
@@ -867,7 +861,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
action: () => this.requestShowSetupQRCode(),
|
action: () => this.requestShowSetupQRCode(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
() => this.isConfiguredAs("isConfigured", true)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getSettingDefinitions(): SettingDefinitionItem[] {
|
override getSettingDefinitions(): SettingDefinitionItem[] {
|
||||||
@@ -882,34 +877,34 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
}
|
}
|
||||||
return this.createDeclarativePage(entry);
|
return this.createDeclarativePage(entry);
|
||||||
};
|
};
|
||||||
const synchronisation = this.createSynchronisationGroup([
|
const synchronisation = this.createRootGroup("synchronisation", [
|
||||||
getPage("remote-configuration"),
|
getPage("remote-configuration"),
|
||||||
getPage("synchronisation"),
|
getPage("synchronisation"),
|
||||||
]);
|
]);
|
||||||
const generalSettings = this.createGeneralSettingsGroup();
|
const generalSettings = this.createGeneralSettingsGroup();
|
||||||
const quickSetup = this.createQuickSetupGroup();
|
const quickSetup = this.createQuickSetupGroup();
|
||||||
const setupOtherDevices = this.createSetupOtherDevicesGroup();
|
const setupOtherDevices = this.createSetupOtherDevicesGroup();
|
||||||
const maintenance = this.createPageGroup(
|
const maintenance = this.createRootGroup("maintenance-and-recovery", [
|
||||||
`🛠️ ${$msg("obsidianLiveSyncSettingTab.titleMaintenanceAndRecovery")}`,
|
getPage("maintenance"),
|
||||||
[getPage("maintenance"), getPage("hatch")]
|
getPage("hatch"),
|
||||||
);
|
]);
|
||||||
const extraFeatures = this.createPageGroup(
|
const extraFeatures = this.createRootGroup(
|
||||||
`🧩 ${$msg("obsidianLiveSyncSettingTab.titleExtraFeaturesGroup")}`,
|
"extra-features",
|
||||||
[getPage("selector"), getPage("customisation-sync")],
|
[getPage("selector"), getPage("customisation-sync")],
|
||||||
() => this.isPageVisible(LEVEL_ADVANCED)
|
() => this.isPageVisible(LEVEL_ADVANCED)
|
||||||
);
|
);
|
||||||
const advancedSettings = this.createPageGroup(
|
const advancedSettings = this.createRootGroup(
|
||||||
`🔧 ${$msg("obsidianLiveSyncSettingTab.titleAdvancedSettings")}`,
|
"advanced-settings",
|
||||||
[getPage("advanced"), getPage("power-users"), getPage("patches")],
|
[getPage("advanced"), getPage("power-users"), getPage("patches")],
|
||||||
() =>
|
() =>
|
||||||
this.isPageVisible(LEVEL_ADVANCED) ||
|
this.isPageVisible(LEVEL_ADVANCED) ||
|
||||||
this.isPageVisible(LEVEL_POWER_USER) ||
|
this.isPageVisible(LEVEL_POWER_USER) ||
|
||||||
this.isPageVisible(LEVEL_EDGE_CASE)
|
this.isPageVisible(LEVEL_EDGE_CASE)
|
||||||
);
|
);
|
||||||
const helpAndInformation = this.createPageGroup(
|
const helpAndInformation = this.createRootGroup("help-and-information", [
|
||||||
`ℹ️ ${$msg("obsidianLiveSyncSettingTab.titleHelpAndInformation")}`,
|
getPage("help"),
|
||||||
[getPage("help"), getPage("change-log")]
|
getPage("change-log"),
|
||||||
);
|
]);
|
||||||
const laterGroups = [setupOtherDevices, maintenance, extraFeatures, advancedSettings, helpAndInformation];
|
const laterGroups = [setupOtherDevices, maintenance, extraFeatures, advancedSettings, helpAndInformation];
|
||||||
|
|
||||||
if (this.isAnySyncEnabled()) {
|
if (this.isAnySyncEnabled()) {
|
||||||
|
|||||||
@@ -50,6 +50,54 @@ export type SettingsPageEntry = {
|
|||||||
legacy: SettingsPaneRenderer;
|
legacy: SettingsPaneRenderer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SettingsRootGroupEntry = {
|
||||||
|
icon: string;
|
||||||
|
name: () => string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Root groups used only by Obsidian's declarative settings landing page. */
|
||||||
|
const SETTINGS_ROOT_GROUP_CATALOGUE = {
|
||||||
|
"quick-setup": {
|
||||||
|
icon: "🧙♂️",
|
||||||
|
name: () => $msg("obsidianLiveSyncSettingTab.titleQuickSetup"),
|
||||||
|
},
|
||||||
|
synchronisation: {
|
||||||
|
icon: "🔄",
|
||||||
|
name: () => $msg("obsidianLiveSyncSettingTab.titleSynchronisation"),
|
||||||
|
},
|
||||||
|
"general-settings": {
|
||||||
|
icon: "⚙️",
|
||||||
|
name: () => $msg("obsidianLiveSyncSettingTab.panelGeneralSettings"),
|
||||||
|
},
|
||||||
|
"setup-other-devices": {
|
||||||
|
icon: "📲",
|
||||||
|
name: () => $msg("obsidianLiveSyncSettingTab.titleSetupOtherDevices"),
|
||||||
|
},
|
||||||
|
"maintenance-and-recovery": {
|
||||||
|
icon: "🛠️",
|
||||||
|
name: () => $msg("obsidianLiveSyncSettingTab.titleMaintenanceAndRecovery"),
|
||||||
|
},
|
||||||
|
"extra-features": {
|
||||||
|
icon: "🧩",
|
||||||
|
name: () => $msg("obsidianLiveSyncSettingTab.titleExtraFeaturesGroup"),
|
||||||
|
},
|
||||||
|
"advanced-settings": {
|
||||||
|
icon: "🔧",
|
||||||
|
name: () => $msg("obsidianLiveSyncSettingTab.titleAdvancedSettings"),
|
||||||
|
},
|
||||||
|
"help-and-information": {
|
||||||
|
icon: "ℹ️",
|
||||||
|
name: () => $msg("obsidianLiveSyncSettingTab.titleHelpAndInformation"),
|
||||||
|
},
|
||||||
|
} as const satisfies Record<string, SettingsRootGroupEntry>;
|
||||||
|
|
||||||
|
export type SettingsRootGroupId = keyof typeof SETTINGS_ROOT_GROUP_CATALOGUE;
|
||||||
|
|
||||||
|
/** Resolve a late-translated root-group label while keeping its icon separate until the Obsidian API boundary. */
|
||||||
|
export function getSettingsRootGroupEntry(id: SettingsRootGroupId): SettingsRootGroupEntry {
|
||||||
|
return SETTINGS_ROOT_GROUP_CATALOGUE[id];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the explicit page list in the same order as the existing settings tab.
|
* Build the explicit page list in the same order as the existing settings tab.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -18,9 +18,34 @@ vi.mock("./PanePowerUsers.ts", () => ({ panePowerUsers: vi.fn() }));
|
|||||||
vi.mock("./PanePatches.ts", () => ({ panePatches: vi.fn() }));
|
vi.mock("./PanePatches.ts", () => ({ panePatches: vi.fn() }));
|
||||||
vi.mock("./PaneMaintenance.ts", () => ({ paneMaintenance: vi.fn() }));
|
vi.mock("./PaneMaintenance.ts", () => ({ paneMaintenance: vi.fn() }));
|
||||||
|
|
||||||
import { createAdvancedSettingDefinitionGroups, createSettingsPageCatalogue } from "./SettingsPageCatalogue.ts";
|
import {
|
||||||
|
createAdvancedSettingDefinitionGroups,
|
||||||
|
createSettingsPageCatalogue,
|
||||||
|
getSettingsRootGroupEntry,
|
||||||
|
type SettingsRootGroupId,
|
||||||
|
} from "./SettingsPageCatalogue.ts";
|
||||||
|
|
||||||
|
const EXPECTED_ROOT_GROUPS = [
|
||||||
|
["quick-setup", "🧙♂️", "obsidianLiveSyncSettingTab.titleQuickSetup"],
|
||||||
|
["synchronisation", "🔄", "obsidianLiveSyncSettingTab.titleSynchronisation"],
|
||||||
|
["general-settings", "⚙️", "obsidianLiveSyncSettingTab.panelGeneralSettings"],
|
||||||
|
["setup-other-devices", "📲", "obsidianLiveSyncSettingTab.titleSetupOtherDevices"],
|
||||||
|
["maintenance-and-recovery", "🛠️", "obsidianLiveSyncSettingTab.titleMaintenanceAndRecovery"],
|
||||||
|
["extra-features", "🧩", "obsidianLiveSyncSettingTab.titleExtraFeaturesGroup"],
|
||||||
|
["advanced-settings", "🔧", "obsidianLiveSyncSettingTab.titleAdvancedSettings"],
|
||||||
|
["help-and-information", "ℹ️", "obsidianLiveSyncSettingTab.titleHelpAndInformation"],
|
||||||
|
] as const satisfies readonly (readonly [SettingsRootGroupId, string, string])[];
|
||||||
|
|
||||||
describe("settings page catalogue", () => {
|
describe("settings page catalogue", () => {
|
||||||
|
it("keeps each declarative root-group icon separate from its late-translated name", () => {
|
||||||
|
expect(
|
||||||
|
EXPECTED_ROOT_GROUPS.map(([id]) => {
|
||||||
|
const { icon, name } = getSettingsRootGroupEntry(id);
|
||||||
|
return [id, icon, name()];
|
||||||
|
})
|
||||||
|
).toEqual(EXPECTED_ROOT_GROUPS);
|
||||||
|
});
|
||||||
|
|
||||||
it("registers every existing page once and keeps only Advanced native", () => {
|
it("registers every existing page once and keeps only Advanced native", () => {
|
||||||
const catalogue = createSettingsPageCatalogue();
|
const catalogue = createSettingsPageCatalogue();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user