mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 17:30:31 +00:00
Group settings rows with Obsidian setting groups
This commit is contained in:
parent
3c7861fcd1
commit
76c0a5a907
7 changed files with 178 additions and 119 deletions
|
|
@ -4,31 +4,47 @@ import type { Thread } from "../domain/threads/model";
|
|||
import { shortThreadId } from "../utils";
|
||||
import { archivedThreadDisplayTitle } from "./archived-thread-title";
|
||||
import type { ArchivedThreadSectionState } from "./section-state";
|
||||
import { SettingRow, SettingsHeading, SettingsIconButton, TextControl, ToggleControl } from "./setting-components";
|
||||
import {
|
||||
SettingRow,
|
||||
SettingsGroup,
|
||||
SettingsHeading,
|
||||
SettingsIconButton,
|
||||
SettingsItems,
|
||||
SettingsStatusRow,
|
||||
TextControl,
|
||||
ToggleControl,
|
||||
} from "./setting-components";
|
||||
|
||||
export function ArchivedThreadSection({ state }: { state: ArchivedThreadSectionState }): UiNode {
|
||||
return (
|
||||
<section className="codex-panel-settings__dynamic-section codex-panel-settings__archived-section">
|
||||
<SettingsHeading
|
||||
dynamic
|
||||
name="Thread archiving"
|
||||
desc="Choose the default archive behavior and configure saved thread notes. Thread lists offer both archive choices; slash commands use the default."
|
||||
/>
|
||||
<ArchiveExportSettings state={state} />
|
||||
{state.contentAvailable && state.threads.length === 0 ? (
|
||||
<p className="setting-item-description codex-panel-settings__dynamic-section-status">No archived threads.</p>
|
||||
) : state.contentAvailable ? (
|
||||
<ArchivedThreadList state={state} />
|
||||
) : !state.loading && state.status ? (
|
||||
<p className="setting-item-description codex-panel-settings__dynamic-section-status">{state.status}</p>
|
||||
) : null}
|
||||
</section>
|
||||
<>
|
||||
<SettingsGroup className="codex-panel-settings__dynamic-section codex-panel-settings__archived-section">
|
||||
<SettingsHeading
|
||||
dynamic
|
||||
name="Thread archiving"
|
||||
desc="Choose the default archive behavior and configure saved thread notes. Thread lists offer both archive choices; slash commands use the default."
|
||||
/>
|
||||
<ArchiveExportSettings state={state} />
|
||||
</SettingsGroup>
|
||||
<SettingsGroup className="codex-panel-settings__dynamic-section codex-panel-settings__archived-threads-section">
|
||||
<SettingsHeading
|
||||
dynamic
|
||||
name="Archived threads"
|
||||
desc="Restore archived threads, or permanently delete archived threads you no longer need."
|
||||
/>
|
||||
{state.contentAvailable ? (
|
||||
<ArchivedThreadList state={state} />
|
||||
) : !state.loading && state.status ? (
|
||||
<p className="setting-item-description codex-panel-settings__dynamic-section-status">{state.status}</p>
|
||||
) : null}
|
||||
</SettingsGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ArchiveExportSettings({ state }: { state: ArchivedThreadSectionState }): UiNode {
|
||||
return (
|
||||
<>
|
||||
<SettingsItems>
|
||||
<SettingRow
|
||||
name="Save note by default"
|
||||
desc="When on, the default archive action saves a markdown note before archiving. When off, the default archives without saving. If saving fails, the thread stays active. Frontmatter includes title, thread_id, created, and optional tags."
|
||||
|
|
@ -70,22 +86,19 @@ function ArchiveExportSettings({ state }: { state: ArchivedThreadSectionState })
|
|||
}}
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
</SettingsItems>
|
||||
);
|
||||
}
|
||||
|
||||
function ArchivedThreadList({ state }: { state: ArchivedThreadSectionState }): UiNode {
|
||||
return (
|
||||
<>
|
||||
<p className="setting-item-description codex-panel-settings__dynamic-list-summary">
|
||||
Restore archived threads, or permanently delete archived threads you no longer need.
|
||||
</p>
|
||||
<div className="setting-items codex-panel-settings__dynamic-list codex-panel-settings__archived-list">
|
||||
{state.threads.map((thread) => (
|
||||
<ArchivedThreadRow key={thread.id} thread={thread} state={state} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
<SettingsItems className="codex-panel-settings__dynamic-list codex-panel-settings__archived-list">
|
||||
{state.threads.length === 0 ? (
|
||||
<SettingsStatusRow>No archived threads.</SettingsStatusRow>
|
||||
) : (
|
||||
state.threads.map((thread) => <ArchivedThreadRow key={thread.id} thread={thread} state={state} />)
|
||||
)}
|
||||
</SettingsItems>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,34 +3,36 @@ import type { ComponentChild as UiNode } from "preact";
|
|||
import type { ModelMetadata, ReasoningEffort } from "../domain/catalog/metadata";
|
||||
import { findModelMetadataByIdOrName, supportedEffortsForModelMetadata } from "../domain/catalog/metadata";
|
||||
import type { HelperSettingsState } from "./section-state";
|
||||
import { SelectControl, SettingRow, SettingsHeading } from "./setting-components";
|
||||
import { SelectControl, SettingRow, SettingsGroup, SettingsHeading, SettingsItems } from "./setting-components";
|
||||
|
||||
const CODEX_DEFAULT_VALUE = "__codex-default__";
|
||||
|
||||
export function HelperSettingsSection({ state }: { state: HelperSettingsState }): UiNode {
|
||||
return (
|
||||
<section className="codex-panel-settings__section codex-panel-settings__helper-section">
|
||||
<SettingsGroup className="codex-panel-settings__section codex-panel-settings__helper-section">
|
||||
<SettingsHeading name="Codex helpers" />
|
||||
<ModelEffortSetting
|
||||
name="Automatic thread naming"
|
||||
desc="Choose the model and reasoning effort used to suggest thread names."
|
||||
modelValue={state.threadNamingModel}
|
||||
effortValue={state.threadNamingEffort}
|
||||
models={state.models}
|
||||
onModelChange={state.onThreadNamingModelChange}
|
||||
onEffortChange={state.onThreadNamingEffortChange}
|
||||
/>
|
||||
<ModelEffortSetting
|
||||
name="Selection rewrite"
|
||||
desc="Choose the model and reasoning effort used by rewrite selection."
|
||||
modelValue={state.rewriteSelectionModel}
|
||||
effortValue={state.rewriteSelectionEffort}
|
||||
models={state.models}
|
||||
onModelChange={state.onRewriteSelectionModelChange}
|
||||
onEffortChange={state.onRewriteSelectionEffortChange}
|
||||
/>
|
||||
<SettingsItems>
|
||||
<ModelEffortSetting
|
||||
name="Automatic thread naming"
|
||||
desc="Choose the model and reasoning effort used to suggest thread names."
|
||||
modelValue={state.threadNamingModel}
|
||||
effortValue={state.threadNamingEffort}
|
||||
models={state.models}
|
||||
onModelChange={state.onThreadNamingModelChange}
|
||||
onEffortChange={state.onThreadNamingEffortChange}
|
||||
/>
|
||||
<ModelEffortSetting
|
||||
name="Selection rewrite"
|
||||
desc="Choose the model and reasoning effort used by rewrite selection."
|
||||
modelValue={state.rewriteSelectionModel}
|
||||
effortValue={state.rewriteSelectionEffort}
|
||||
models={state.models}
|
||||
onModelChange={state.onRewriteSelectionModelChange}
|
||||
onEffortChange={state.onRewriteSelectionEffortChange}
|
||||
/>
|
||||
</SettingsItems>
|
||||
{state.modelLoadFailed ? <p className="setting-item-description codex-panel-settings__section-status">{state.modelStatus}</p> : null}
|
||||
</section>
|
||||
</SettingsGroup>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,33 +3,31 @@ import type { ComponentChild as UiNode } from "preact";
|
|||
import type { HookItem } from "../domain/catalog/metadata";
|
||||
import { ObsidianButton } from "../shared/ui/components";
|
||||
import type { HookSectionState } from "./section-state";
|
||||
import { SettingRow, SettingsHeading } from "./setting-components";
|
||||
import { SettingRow, SettingsGroup, SettingsHeading, SettingsItems, SettingsStatusRow } from "./setting-components";
|
||||
|
||||
export function HookSection({ state }: { state: HookSectionState }): UiNode {
|
||||
return (
|
||||
<section className="codex-panel-settings__dynamic-section codex-panel-settings__hook-section">
|
||||
<SettingsGroup className="codex-panel-settings__dynamic-section codex-panel-settings__hook-section">
|
||||
<SettingsHeading dynamic name="Hook status" desc="Review discovered hooks, trust changes, and turn hooks on or off." />
|
||||
{state.contentAvailable ? (
|
||||
<Hooks state={state} />
|
||||
) : !state.loading && state.status ? (
|
||||
<p className="setting-item-description codex-panel-settings__dynamic-section-status">{state.status}</p>
|
||||
) : null}
|
||||
</section>
|
||||
</SettingsGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function Hooks({ state }: { state: HookSectionState }): UiNode {
|
||||
return (
|
||||
<>
|
||||
{state.hooks.length === 0 ? (
|
||||
<p className="setting-item-description">No hooks found for this vault root.</p>
|
||||
) : (
|
||||
<div className="setting-items codex-panel-settings__dynamic-list codex-panel-settings__hook-list">
|
||||
{state.hooks.map((hook) => (
|
||||
<HookRow key={hook.key} hook={hook} state={state} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<SettingsItems className="codex-panel-settings__dynamic-list codex-panel-settings__hook-list">
|
||||
{state.hooks.length === 0 ? (
|
||||
<SettingsStatusRow>No hooks found for this vault root.</SettingsStatusRow>
|
||||
) : (
|
||||
state.hooks.map((hook) => <HookRow key={hook.key} hook={hook} state={state} />)
|
||||
)}
|
||||
</SettingsItems>
|
||||
{state.warnings.map((warning) => (
|
||||
<p key={`warning:${warning}`} className="setting-item-description codex-panel-settings__hook-warning">
|
||||
{warning}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,25 @@ import {
|
|||
|
||||
export type SelectControlOption = ObsidianDropdownOption;
|
||||
|
||||
export function SettingsGroup({ className, children }: { className: string; children: UiNode }): UiNode {
|
||||
return <section className={`setting-group ${className}`}>{children}</section>;
|
||||
}
|
||||
|
||||
export function SettingsItems({ className = "", children }: { className?: string; children: UiNode }): UiNode {
|
||||
return <div className={`setting-items ${className}`.trim()}>{children}</div>;
|
||||
}
|
||||
|
||||
export function SettingsStatusRow({ children }: { children: UiNode }): UiNode {
|
||||
return (
|
||||
<div className="setting-item codex-panel-settings__status-row">
|
||||
<div className="setting-item-info">
|
||||
<div className="setting-item-description">{children}</div>
|
||||
</div>
|
||||
<div className="setting-item-control" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SettingsHeading({ name, desc, dynamic = false }: { name: string; desc?: string; dynamic?: boolean }): UiNode {
|
||||
return (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -28,6 +28,14 @@ function renderSettingsHeading(containerEl: HTMLElement, name: string): void {
|
|||
new Setting(containerEl).setClass("codex-panel-settings__section-heading").setHeading().setName(name);
|
||||
}
|
||||
|
||||
function createSettingsGroup(containerEl: HTMLElement, className: string): HTMLElement {
|
||||
return containerEl.createDiv({ cls: `setting-group ${className}` });
|
||||
}
|
||||
|
||||
function createSettingsItems(containerEl: HTMLElement): HTMLElement {
|
||||
return containerEl.createDiv({ cls: "setting-items" });
|
||||
}
|
||||
|
||||
export class CodexPanelSettingTab extends PluginSettingTab {
|
||||
private readonly dynamicData: SettingsDynamicDataController;
|
||||
private archivedDeleteConfirmThreadId: string | null = null;
|
||||
|
|
@ -90,9 +98,10 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
private renderPanelPreferenceSections(containerEl: HTMLElement): void {
|
||||
const configSection = containerEl.createDiv({ cls: "codex-panel-settings__section codex-panel-settings__general-section" });
|
||||
const configSection = createSettingsGroup(containerEl, "codex-panel-settings__section codex-panel-settings__general-section");
|
||||
const configItems = createSettingsItems(configSection);
|
||||
|
||||
new Setting(configSection)
|
||||
new Setting(configItems)
|
||||
.setName("Codex executable")
|
||||
.setDesc("Path used to start `codex app-server`. Use an absolute path if Obsidian cannot find `codex`.")
|
||||
.addText((text) => {
|
||||
|
|
@ -108,7 +117,7 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
text.inputEl.blur();
|
||||
});
|
||||
});
|
||||
new Setting(configSection)
|
||||
new Setting(configItems)
|
||||
.setName("Show chat toolbar")
|
||||
.setDesc("Show the chat panel toolbar. Slash commands, composer status controls, and the threads view remain available when hidden.")
|
||||
.addToggle((toggle) => {
|
||||
|
|
@ -119,9 +128,10 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
});
|
||||
});
|
||||
|
||||
const composerSection = containerEl.createDiv({ cls: "codex-panel-settings__section codex-panel-settings__composer-section" });
|
||||
const composerSection = createSettingsGroup(containerEl, "codex-panel-settings__section codex-panel-settings__composer-section");
|
||||
renderSettingsHeading(composerSection, "Composer");
|
||||
new Setting(composerSection)
|
||||
const composerItems = createSettingsItems(composerSection);
|
||||
new Setting(composerItems)
|
||||
.setName("Send shortcut")
|
||||
.setDesc(
|
||||
"Choose how the composer sends messages. Shift+Enter inserts a newline when Enter sends. Obsidian hotkeys may intercept Cmd/Ctrl+Enter.",
|
||||
|
|
@ -135,7 +145,7 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
this.display();
|
||||
});
|
||||
});
|
||||
new Setting(composerSection)
|
||||
new Setting(composerItems)
|
||||
.setName("Scroll thread from composer edges")
|
||||
.setDesc("When enabled, Up/Ctrl+P on the first composer line and Down/Ctrl+N on the last line scroll the thread.")
|
||||
.addToggle((toggle) => {
|
||||
|
|
@ -244,12 +254,14 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
private renderHeaderActions(containerEl: HTMLElement, introText: string): void {
|
||||
const header = containerEl.createDiv({ cls: "codex-panel-settings__header" });
|
||||
header.createEl("span", {
|
||||
const header = containerEl.createDiv({ cls: "setting-item setting-item-heading codex-panel-settings__header" });
|
||||
const info = header.createDiv({ cls: "setting-item-info" });
|
||||
info.createDiv({
|
||||
cls: "setting-item-description codex-panel-settings__section-intro",
|
||||
text: introText,
|
||||
});
|
||||
const button = header.createEl("button", {
|
||||
const control = header.createDiv({ cls: "setting-item-control" });
|
||||
const button = control.createEl("button", {
|
||||
cls: "clickable-icon codex-panel-settings__refresh-button",
|
||||
});
|
||||
button.type = "button";
|
||||
|
|
|
|||
|
|
@ -3,15 +3,6 @@
|
|||
margin-top: var(--size-4-6);
|
||||
}
|
||||
|
||||
.codex-panel-settings__header {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, max-content) auto;
|
||||
align-items: start;
|
||||
justify-content: start;
|
||||
gap: var(--size-4-2);
|
||||
min-height: var(--input-height);
|
||||
}
|
||||
|
||||
.codex-panel-settings__refresh-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -21,41 +12,24 @@
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.codex-panel-settings__section-heading.setting-item-heading,
|
||||
.codex-panel-settings__dynamic-section-heading.setting-item-heading {
|
||||
margin-bottom: var(--size-4-2);
|
||||
}
|
||||
|
||||
.codex-panel-settings__dynamic-section-status {
|
||||
padding: 0 var(--size-4-4);
|
||||
}
|
||||
|
||||
.codex-panel-settings__section-status {
|
||||
padding: 0 var(--size-4-4);
|
||||
}
|
||||
|
||||
.codex-panel-settings__section-intro,
|
||||
.codex-panel-settings__dynamic-list-summary {
|
||||
.codex-panel-settings__section-intro {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-ui-small);
|
||||
line-height: var(--line-height-normal);
|
||||
}
|
||||
|
||||
.codex-panel-settings__section-intro {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.codex-panel-settings__dynamic-list-summary {
|
||||
padding: 0 var(--size-4-4);
|
||||
margin: 0 0 var(--size-2-3);
|
||||
.codex-panel-settings__dynamic-list {
|
||||
margin: var(--size-4-4) 0 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.codex-panel-settings__dynamic-list {
|
||||
margin: var(--size-2-3) 0 0;
|
||||
overflow: auto;
|
||||
border: var(--codex-panel-border);
|
||||
border-radius: var(--codex-panel-card-radius);
|
||||
background: var(--codex-panel-surface);
|
||||
.codex-panel-settings__dynamic-section-heading + .codex-panel-settings__dynamic-list {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.codex-panel-settings__archived-list {
|
||||
|
|
@ -67,16 +41,7 @@
|
|||
}
|
||||
|
||||
.codex-panel-settings__dynamic-row.setting-item {
|
||||
margin: 0;
|
||||
padding: var(--size-4-2) var(--size-4-3);
|
||||
border: 0;
|
||||
border-top: var(--codex-panel-border);
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.codex-panel-settings__dynamic-row.setting-item:first-child {
|
||||
border-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.codex-panel-settings__archived-row--delete-confirming {
|
||||
|
|
|
|||
|
|
@ -70,8 +70,34 @@ describe("settings tab", () => {
|
|||
expect(buttonTexts(tab)).not.toContain("Load models");
|
||||
expect(buttonTexts(tab)).not.toContain("Load hooks");
|
||||
expect(buttonTexts(tab)).not.toContain("Load archive list");
|
||||
expect(
|
||||
tab.containerEl.querySelector(".codex-panel-settings__header.setting-item-heading .setting-item-description")?.textContent,
|
||||
).toContain("Codex Panel stores only panel preferences");
|
||||
expect(tab.containerEl.querySelector(".codex-panel-settings__header button")?.getAttribute("data-icon")).toBe("refresh-cw");
|
||||
expect(tab.containerEl.querySelector("h2")).toBeNull();
|
||||
expect(tab.containerEl.querySelector(".codex-panel-settings__general-section.setting-group > .setting-items")?.children).toHaveLength(
|
||||
2,
|
||||
);
|
||||
expect(tab.containerEl.querySelector(".codex-panel-settings__composer-section.setting-group > .setting-items")?.children).toHaveLength(
|
||||
2,
|
||||
);
|
||||
expect(tab.containerEl.querySelector(".codex-panel-settings__helper-section.setting-group > .setting-items")?.children).toHaveLength(2);
|
||||
expect(
|
||||
tab.containerEl.querySelector(".codex-panel-settings__archived-section > .setting-item-heading .setting-item-description")
|
||||
?.textContent,
|
||||
).toContain("Choose the default archive behavior");
|
||||
expect(
|
||||
tab.containerEl.querySelector(".codex-panel-settings__archived-threads-section > .setting-item-heading .setting-item-description")
|
||||
?.textContent,
|
||||
).toContain("Restore archived threads");
|
||||
expect(
|
||||
tab.containerEl.querySelector(".codex-panel-settings__hook-section > .setting-item-heading .setting-item-description")?.textContent,
|
||||
).toContain("Review discovered hooks");
|
||||
expect(
|
||||
tab.containerEl.querySelector(
|
||||
".codex-panel-settings__archived-section.setting-group > .setting-items:not(.codex-panel-settings__dynamic-list)",
|
||||
)?.children,
|
||||
).toHaveLength(4);
|
||||
expect(settingNames(tab)).toEqual([
|
||||
"Codex executable",
|
||||
"Show chat toolbar",
|
||||
|
|
@ -82,6 +108,11 @@ describe("settings tab", () => {
|
|||
"Automatic thread naming",
|
||||
"Selection rewrite",
|
||||
"Thread archiving",
|
||||
"Save note by default",
|
||||
"Saved note folder",
|
||||
"Saved note filename",
|
||||
"Saved note tags",
|
||||
"Archived threads",
|
||||
"Hook status",
|
||||
]);
|
||||
});
|
||||
|
|
@ -611,8 +642,11 @@ describe("settings tab", () => {
|
|||
expect(tab.containerEl.querySelector(".codex-panel-settings__archived-section .setting-item-heading")?.textContent).toContain(
|
||||
"Thread archiving",
|
||||
);
|
||||
expect(tab.containerEl.querySelectorAll(".codex-panel-settings__hook-list .setting-item")).toHaveLength(1);
|
||||
expect(tab.containerEl.querySelectorAll(".codex-panel-settings__archived-list .setting-item")).toHaveLength(1);
|
||||
expect(tab.containerEl.querySelector(".codex-panel-settings__archived-threads-section .setting-item-heading")?.textContent).toContain(
|
||||
"Archived threads",
|
||||
);
|
||||
expect(tab.containerEl.querySelectorAll(".codex-panel-settings__hook-list .codex-panel-settings__hook-row")).toHaveLength(1);
|
||||
expect(tab.containerEl.querySelectorAll(".codex-panel-settings__archived-list .codex-panel-settings__archived-row")).toHaveLength(1);
|
||||
expect(tab.containerEl.querySelector(".codex-panel-settings__hook-list")?.textContent).toContain("abc123");
|
||||
expect(tab.containerEl.querySelector(".codex-panel-settings__archived-list")?.textContent).toContain("Archived thread");
|
||||
});
|
||||
|
|
@ -749,7 +783,10 @@ describe("settings tab", () => {
|
|||
await flushPromises();
|
||||
|
||||
expect(tab.containerEl.textContent).toContain("No archived threads.");
|
||||
expect(tab.containerEl.querySelectorAll(".codex-panel-settings__archived-list .setting-item")).toHaveLength(0);
|
||||
expect(tab.containerEl.querySelector(".codex-panel-settings__archived-list .codex-panel-settings__status-row")?.textContent).toContain(
|
||||
"No archived threads.",
|
||||
);
|
||||
expect(tab.containerEl.querySelectorAll(".codex-panel-settings__archived-list .codex-panel-settings__archived-row")).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -942,21 +979,34 @@ async function flushPromises(): Promise<void> {
|
|||
|
||||
function settingNames(tab: CodexPanelSettingTab): string[] {
|
||||
return Array.from(settingsSectionRoots(tab)).flatMap((element) => {
|
||||
if (element.classList.contains("codex-panel-settings__header")) return [];
|
||||
if (element.classList.contains("setting-item")) {
|
||||
return [element.querySelector(".setting-item-name")?.textContent ?? ""];
|
||||
}
|
||||
if (element.classList.contains("codex-panel-settings__section")) {
|
||||
return Array.from(element.querySelectorAll(":scope > .setting-item")).map((setting) => {
|
||||
return setting.querySelector(".setting-item-name")?.textContent ?? "";
|
||||
});
|
||||
return settingsGroupNames(element);
|
||||
}
|
||||
if (element.classList.contains("codex-panel-settings__dynamic-section")) {
|
||||
return [element.querySelector(":scope > .setting-item-heading .setting-item-name")?.textContent ?? ""];
|
||||
return settingsGroupNames(element);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
function settingsGroupNames(element: Element): string[] {
|
||||
const names = Array.from(element.querySelectorAll(":scope > .setting-item-heading")).map((setting) => {
|
||||
return setting.querySelector(".setting-item-name")?.textContent ?? "";
|
||||
});
|
||||
names.push(
|
||||
...Array.from(element.querySelectorAll(":scope > .setting-items:not(.codex-panel-settings__dynamic-list) > .setting-item")).flatMap(
|
||||
(setting) => {
|
||||
return [setting.querySelector(".setting-item-name")?.textContent ?? ""];
|
||||
},
|
||||
),
|
||||
);
|
||||
return names;
|
||||
}
|
||||
|
||||
function settingsSectionRoots(tab: CodexPanelSettingTab): Element[] {
|
||||
return Array.from(tab.containerEl.children).flatMap((element) => {
|
||||
if (element.classList.contains("codex-panel-settings__preact-sections")) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue