initial ai assistance

This commit is contained in:
mats committed 2026-11-01 10:27:56 +01:00
1 parent 1af9ec3172
commit 2e0bf1e88e
24 files changed
+1293 -60

No files matched your search

+72 -28
View File
@@ -41,19 +41,21 @@ function Properties({ node, schema, ctrl, close }: EditorProps) {
}}
>
<SchemaForm schema={schema} value={draft} onChange={setDraft} node={node} ctrl={ctrl} />
<footer className="cms-actions">
<button type="button" className="cms-btn" onClick={() => setDraft(node.data ?? {})} disabled={!changed}>
<Icon name="reset" size={16} /> Reset
</button>
{problems.length > 0 && (
<span className="cms-error cms-actions__note" role="alert" title={problems.map(([p, m]) => `${p || 'form'}: ${m}`).join('\n')}>
{problems.length} problem{problems.length === 1 ? '' : 's'} to fix
</span>
)}
<button type="submit" className="cms-btn cms-btn--primary" disabled={!changed || saving || problems.length > 0}>
<Icon name="save" size={16} /> {saving ? 'Saving…' : 'Save'}
</button>
</footer>
{changed && (
<footer className="cms-actions">
<button type="button" className="cms-btn" onClick={() => setDraft(node.data ?? {})}>
<Icon name="reset" size={16} /> Reset
</button>
{problems.length > 0 && (
<span className="cms-error cms-actions__note" role="alert" title={problems.map(([p, m]) => `${p || 'form'}: ${m}`).join('\n')}>
{problems.length} problem{problems.length === 1 ? '' : 's'} to fix
</span>
)}
<button type="submit" className="cms-btn cms-btn--primary" disabled={saving || problems.length > 0}>
<Icon name="save" size={16} /> {saving ? 'Saving…' : 'Save'}
</button>
</footer>
)}
</form>
)
}
@@ -66,15 +68,13 @@ function RawJson({ node, ctrl, close }: EditorProps) {
return (
<div className="cms-form">
<JsonArea value={node.data ?? {}} onChange={setDraft} />
<footer className="cms-actions">
<button
className="cms-btn cms-btn--primary"
disabled={!Object.keys(patch).length}
onClick={async () => (await ctrl.setData(node.id, patch, { version: node.version })) && close()}
>
<Icon name="save" size={16} /> Save
</button>
</footer>
{Object.keys(patch).length > 0 && (
<footer className="cms-actions">
<button className="cms-btn cms-btn--primary" onClick={async () => (await ctrl.setData(node.id, patch, { version: node.version })) && close()}>
<Icon name="save" size={16} /> Save
</button>
</footer>
)}
</div>
)
}
@@ -155,7 +155,10 @@ function AddChild({ info, ctrl, close }: EditorProps) {
}
function AddAfter({ info, ctrl, close }: EditorProps) {
const [target, setTarget] = useState<{ parent: string; index: number } | null>(null)
const [target, setTarget] = useState<{
parent: string
index: number
} | null>(null)
useEffect(() => {
void (async () => {
const parent = await ctrl.parentIdOf(info.id)
@@ -168,12 +171,53 @@ function AddAfter({ info, ctrl, close }: EditorProps) {
export function registerBuiltins(): () => void {
const off = [
registerEditor({ id: 'properties', label: 'Properties', icon: 'edit', match: '*', priority: 100, onDoubleClick: true, component: Properties }),
registerEditor({
id: 'properties',
label: 'Properties',
icon: 'edit',
match: '*',
priority: 100,
onDoubleClick: true,
component: Properties,
}),
// Matches on the schema, not the node type: any type with x-style fields gets spacing/layout controls.
registerEditor({ id: 'style', label: 'Style', icon: 'style', match: (i) => describeStyle(i.schema).length > 0, priority: 90, size: 'md', component: StylePanel }),
registerEditor({ id: 'add-child', label: 'Add inside', icon: 'add-inside', match: (i) => i.container, priority: 50, size: 'sm', component: AddChild }),
registerEditor({ id: 'add-after', label: 'Add after', icon: 'add', match: '*', priority: 40, size: 'sm', component: AddAfter }),
registerEditor({ id: 'raw-json', label: 'JSON', icon: 'json', placement: 'menu', match: '*', priority: -100, size: 'lg', component: RawJson }),
registerEditor({
id: 'style',
label: 'Style',
icon: 'style',
match: (i) => describeStyle(i.schema).length > 0,
priority: 90,
size: 'md',
component: StylePanel,
}),
registerEditor({
id: 'add-child',
label: 'Add inside',
icon: 'add-inside',
match: (i) => i.container,
priority: 50,
size: 'sm',
component: AddChild,
}),
registerEditor({
id: 'add-after',
label: 'Add after',
icon: 'add',
match: '*',
priority: 40,
size: 'sm',
component: AddAfter,
}),
registerEditor({
id: 'raw-json',
label: 'JSON',
icon: 'json',
placement: 'menu',
match: '*',
priority: -100,
size: 'lg',
component: RawJson,
}),
registerInlineEditor({ id: 'text', start: startPlainText }),
registerInlineEditor({ id: 'markdown', start: startMarkdown }),
]