Skip to content

Commit a8da39d

Browse files
committed
refactor(config-editor): replace ce-* DOM ids with semantic namespaces
Use editor-* for chrome, field-* for field controls, and section-* for sections; update focus/navigation helpers, unit test fixtures, and the browser regressions. Rename the Svelte context keys accordingly and keep the #app mount point. Assisted-by: opencode:deepseek-flash
1 parent 8f9caca commit a8da39d

18 files changed

Lines changed: 134 additions & 132 deletions

‎config-editor/index.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<script type="module" src="/ui/main.ts"></script>
1010
</head>
1111
<body>
12-
<p id="ce-loading" role="status">正在加载本地配置编辑器,需要支持 WebAssembly 的现代浏览器。</p>
12+
<p id="editor-loading" role="status">正在加载本地配置编辑器,需要支持 WebAssembly 的现代浏览器。</p>
1313
<div id="app"></div>
1414
<noscript>请启用 JavaScript 和 WebAssembly 以使用本地配置编辑器。</noscript>
1515
</body>

‎config-editor/ui/App.svelte‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
<svelte:head><title>{view.ui.brand} {view.ui.title}</title></svelte:head>
2424

2525
<div class="shell" data-theme={theme.current}>
26-
<a class="skip-link" href="#ce-editor" onclick={(event) => { event.preventDefault(); workbench.skipToEditor(); }}>跳转到配置表单</a>
26+
<a class="skip-link" href="#editor-form" onclick={(event) => { event.preventDefault(); workbench.skipToEditor(); }}>跳转到配置表单</a>
2727
<AppHeader {theme} />
28-
<main id="config-editor" data-ready="true">
28+
<main id="editor" data-ready="true">
2929
<div class="page-heading">
3030
<div><p class="page-heading__eyebrow">{view.ui.eyebrow}</p><h1>{view.ui.title}</h1><p class="page-heading__intro">{view.ui.description}</p></div>
3131
<p class="privacy-note"><span class="privacy-note__dot"></span>本地处理<span>不保存输入 · 不上传凭据</span></p>
@@ -45,7 +45,7 @@
4545
</div>
4646
<div class="workspace" data-pane={workbench.pane}>
4747
<SectionNav />
48-
<form id="ce-editor" class="editor-form" autocomplete="off" onsubmit={(event) => event.preventDefault()}>
48+
<form id="editor-form" class="editor-form" autocomplete="off" onsubmit={(event) => event.preventDefault()}>
4949
<div class="region-heading"><h2>选择配置</h2><span>修改后实时更新预览</span></div>
5050
{#key session.schema}
5151
<FieldSearch />
@@ -84,7 +84,7 @@
8484
box-shadow: var(--shadow-pop);
8585
}
8686
.skip-link:focus { top: 10px; }
87-
#config-editor {
87+
#editor {
8888
max-width: 1616px;
8989
padding: 26px 28px 22px;
9090
margin: auto;
@@ -204,7 +204,7 @@
204204
[data-pane="preview"] > :global(.section-nav) { display: none; }
205205
}
206206
@media (max-width: 480px) {
207-
#config-editor { padding: 18px 16px; }
207+
#editor { padding: 18px 16px; }
208208
.page-heading { margin-bottom: 12px; }
209209
.region-heading { flex-wrap: wrap; }
210210
}

‎config-editor/ui/components/AppHeader.svelte‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919
</a>
2020
<div class="app-header__controls">
2121
{#if session.schemas.length}
22-
<label class="app-header__schema" for="ce-schema"><span>配置方案</span>
23-
<select id="ce-schema" value={session.schema} onchange={(event) => selectSchema(event.currentTarget.value)}>
22+
<label class="app-header__schema" for="editor-schema"><span>配置方案</span>
23+
<select id="editor-schema" value={session.schema} onchange={(event) => selectSchema(event.currentTarget.value)}>
2424
{#each session.schemas as item (item.name)}<option value={item.name}>{item.label}</option>{/each}
2525
</select>
2626
</label>
2727
{/if}
28-
<label class="app-header__theme" for="ce-theme"><span>主题</span>
29-
<select id="ce-theme" value={theme.current} onchange={(event) => theme.select(event.currentTarget.value)}>
28+
<label class="app-header__theme" for="editor-theme"><span>主题</span>
29+
<select id="editor-theme" value={theme.current} onchange={(event) => theme.select(event.currentTarget.value)}>
3030
{#each THEMES as option (option.id)}<option value={option.id}>{option.label}</option>{/each}
3131
</select>
3232
</label>

‎config-editor/ui/components/FieldControl.svelte‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
let { field, row }: { field: FieldView; row?: { collection: string; id: string } } = $props();
66
const session = useSession();
7-
const id = $derived(`ce-${field.path}`);
7+
const id = $derived(`field-${field.path}`);
88
const described = $derived([field.hint && `${id}-hint`, field.error && `${id}-error`].filter(Boolean).join(' ') || undefined);
99
const classes = $derived(field.kind === 'toggle' ? 'field field--toggle' : 'field');
1010

‎config-editor/ui/components/FieldSearch.svelte‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
const workbench = useWorkbench();
77
88
function escape(event: KeyboardEvent) {
9-
if (event.key === 'Escape') { query = ''; document.getElementById('ce-search')?.focus(); }
9+
if (event.key === 'Escape') { query = ''; document.getElementById('editor-search')?.focus(); }
1010
}
1111
1212
const matches = $derived.by(() => {
@@ -18,9 +18,9 @@
1818
</script>
1919

2020
<div class="field-search" role="search" aria-label="查找配置项">
21-
<label for="ce-search">查找配置项</label>
22-
<div class="field-search__input"><input id="ce-search" type="search" onkeydown={escape} bind:value={query} placeholder="搜索名称、说明或配置路径" autocomplete="off" />
23-
{#if query}<button type="button" onclick={() => { query = ''; document.getElementById('ce-search')?.focus(); }}>清除</button>{/if}
21+
<label for="editor-search">查找配置项</label>
22+
<div class="field-search__input"><input id="editor-search" type="search" onkeydown={escape} bind:value={query} placeholder="搜索名称、说明或配置路径" autocomplete="off" />
23+
{#if query}<button type="button" onclick={() => { query = ''; document.getElementById('editor-search')?.focus(); }}>清除</button>{/if}
2424
</div>
2525
{#if query.trim()}
2626
<div class="field-search__results">

‎config-editor/ui/components/FormSection.svelte‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121
{/snippet}
2222

2323
{#if section.collapsed}
24-
<details id={`ce-section-${section.name}`} class="form-section form-section--advanced" hidden={!section.visible}>
24+
<details id={`section-${section.name}`} class="form-section form-section--advanced" hidden={!section.visible}>
2525
<summary>{@render heading()}<span class="form-section__chevron" aria-hidden="true">⌄</span></summary>
2626
{@render contents()}
2727
</details>
2828
{:else}
29-
<section id={`ce-section-${section.name}`} class="form-section" hidden={!section.visible} aria-labelledby={`ce-heading-${section.name}`}>
30-
<h2 id={`ce-heading-${section.name}`} tabindex="-1">{@render heading()}</h2>
29+
<section id={`section-${section.name}`} class="form-section" hidden={!section.visible} aria-labelledby={`section-${section.name}-heading`}>
30+
<h2 id={`section-${section.name}-heading`} tabindex="-1">{@render heading()}</h2>
3131
{@render contents()}
3232
</section>
3333
{/if}

‎config-editor/ui/components/PreviewPanel.svelte‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
const format = $derived(view.format?.value ?? 'json');
1212
</script>
1313

14-
<aside id="ce-preview" class="preview" aria-label="配置预览区" tabindex="-1">
14+
<aside id="editor-preview" class="preview" aria-label="配置预览区" tabindex="-1">
1515
<div class="preview__top">
1616
<h2>配置预览</h2>
1717
<span class="preview__validity" role="status" data-valid={String(view.valid)}>
@@ -27,7 +27,7 @@
2727
</div>
2828
{#if view.format}
2929
{@const field = view.format}
30-
<select id={`ce-${field.key}`} aria-label={field.label} value={field.value}
30+
<select id={`field-${field.key}`} aria-label={field.label} value={field.value}
3131
onchange={(event) => session.dispatch({ type: 'set', field: field.key, value: event.currentTarget.value })}>
3232
{#each field.options as [value, label] (value)}
3333
<option {value}>{label}</option>
@@ -37,8 +37,8 @@
3737
</div>
3838
<div class="preview__filebar">
3939
<strong>{view.filename}</strong>
40-
<label for="ce-reveal">
41-
<input id="ce-reveal" type="checkbox" checked={session.reveal} onchange={(event) => session.setReveal(event.currentTarget.checked)} />
40+
<label for="editor-reveal">
41+
<input id="editor-reveal" type="checkbox" checked={session.reveal} onchange={(event) => session.setReveal(event.currentTarget.checked)} />
4242
显示密码
4343
</label>
4444
</div>

‎config-editor/ui/components/SectionNav.svelte‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@
1212
const update = () => {
1313
cancelAnimationFrame(frame);
1414
frame = requestAnimationFrame(() => {
15-
const elements = names.map(name => document.getElementById(`ce-section-${name}`)).filter((element): element is HTMLElement => !!element && element.getClientRects().length > 0);
15+
const elements = names.map(name => document.getElementById(`section-${name}`)).filter((element): element is HTMLElement => !!element && element.getClientRects().length > 0);
1616
if (!elements.length) return;
1717
const atBottom = window.scrollY > 0 && window.scrollY + window.innerHeight >= document.documentElement.scrollHeight - 2;
1818
const current = atBottom ? elements.at(-1) : elements.filter(element => element.getBoundingClientRect().top <= 140).at(-1) ?? elements[0];
19-
active = current?.id.replace('ce-section-', '') ?? '';
19+
active = current?.id.replace('section-', '') ?? '';
2020
});
2121
};
2222
update();
2323
const observer = new ResizeObserver(update);
24-
const form = document.getElementById('ce-editor');
24+
const form = document.getElementById('editor-form');
2525
if (form) observer.observe(form);
2626
window.addEventListener('scroll', update, { passive: true });
2727
window.addEventListener('resize', update);
@@ -31,8 +31,8 @@
3131

3232
<nav class="section-nav" aria-label="配置分区">
3333
<p>配置目录</p>
34-
<label class="section-nav__picker" for="ce-section-picker">配置分区
35-
<select id="ce-section-picker" value={active} onchange={(event) => focusSection(event.currentTarget.value)}>
34+
<label class="section-nav__picker" for="editor-section-picker">配置分区
35+
<select id="editor-section-picker" value={active} onchange={(event) => focusSection(event.currentTarget.value)}>
3636
{#each session.visibleSections as section (section.name)}<option value={section.name}>{section.label}{session.errorCount(section) ? ` · ${session.errorCount(section)} 项待修正` : ''}</option>{/each}
3737
</select>
3838
</label>
@@ -44,7 +44,7 @@
4444
</button>
4545
{/if}
4646
{/each}
47-
<a href="#ce-preview" onclick={(event) => { event.preventDefault(); workbench.showPreview(); }}>查看配置预览 →</a>
47+
<a href="#editor-preview" onclick={(event) => { event.preventDefault(); workbench.showPreview(); }}>查看配置预览 →</a>
4848
</nav>
4949

5050
<style>

‎config-editor/ui/lib/dom.spec.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ describe('download', () => {
2121

2222
describe('focusField', () => {
2323
it('opens collapsed ancestors and focuses the field', () => {
24-
document.body.innerHTML = '<details id="ce-section-s"><summary>sec</summary><input id="ce-host"></details>';
25-
const input = document.getElementById('ce-host') as HTMLInputElement;
24+
document.body.innerHTML = '<details id="section-s"><summary>sec</summary><input id="field-host"></details>';
25+
const input = document.getElementById('field-host') as HTMLInputElement;
2626
focusField('host');
27-
expect((document.getElementById('ce-section-s') as HTMLDetailsElement).open).toBe(true);
27+
expect((document.getElementById('section-s') as HTMLDetailsElement).open).toBe(true);
2828
expect(document.activeElement).toBe(input);
2929
expect(input.scrollIntoView).toHaveBeenCalled();
3030
});
@@ -36,10 +36,10 @@ describe('focusField', () => {
3636

3737
describe('focusSection', () => {
3838
it('opens the section and focuses its heading', () => {
39-
document.body.innerHTML = '<details id="ce-section-s"><summary>sec</summary></details>';
39+
document.body.innerHTML = '<details id="section-s"><summary>sec</summary></details>';
4040
const summary = document.querySelector('summary') as HTMLElement;
4141
focusSection('s');
42-
expect((document.getElementById('ce-section-s') as HTMLDetailsElement).open).toBe(true);
42+
expect((document.getElementById('section-s') as HTMLDetailsElement).open).toBe(true);
4343
expect(document.activeElement).toBe(summary);
4444
});
4545
});

‎config-editor/ui/lib/dom.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ function openAncestors(element: HTMLElement): void {
2121
}
2222

2323
export function focusField(path: string): void {
24-
const element = document.getElementById(`ce-${path}`);
24+
const element = document.getElementById(`field-${path}`);
2525
if (!element) return;
2626
openAncestors(element);
2727
element.focus();
2828
element.scrollIntoView({ block: 'center' });
2929
}
3030

3131
export function focusSection(name: string): void {
32-
const section = document.getElementById(`ce-section-${name}`);
32+
const section = document.getElementById(`section-${name}`);
3333
if (!section) return;
3434
if (section instanceof HTMLDetailsElement) section.open = true;
3535
const heading = section.querySelector<HTMLElement>('summary, h2');

0 commit comments

Comments
 (0)