feat: registry

This commit is contained in:
2026-04-01 12:16:05 +02:00
parent 01a3e0095c
commit 8d3245b7b1
7 changed files with 475 additions and 13 deletions

View File

@@ -224,15 +224,39 @@ const COMMANDS: CmdEntry[] = [
},
];
// Dynamic commands from /api/dsl-meta — merged into COMMANDS
let dynamicCommands: CmdEntry[] = [];
/** Update slash commands from /api/dsl-meta response. */
export function setDslCommands(
commands: { label: string; detail: string; section: string; snippet: string }[],
) {
// Build dynamic commands from API data, only for entries not already in COMMANDS
const existingLabels = new Set(COMMANDS.map((c) => c.label));
dynamicCommands = commands
.filter((c) => c.detail && c.snippet && !existingLabels.has(c.label))
.map((c) => ({
label: c.label,
detail: c.detail,
section: c.section,
apply: c.snippet.includes("${")
? slashSnippet(c.snippet)
: insert(c.snippet),
}));
}
export function uframeCommandSource(
ctx: CompletionContext,
): CompletionResult | null {
const match = ctx.matchBefore(/\/\w*/);
if (!match || (match.from === match.to && !ctx.explicit)) return null;
const allCommands = [...COMMANDS, ...dynamicCommands];
return {
from: match.from + 1,
filter: true,
options: COMMANDS.map((cmd) => ({
options: allCommands.map((cmd) => ({
label: cmd.label,
detail: cmd.detail,
section: cmd.section,

View File

@@ -8,14 +8,13 @@ import { tags } from "@lezer/highlight";
/**
* CodeMirror 6 syntax highlighting for the µFrame .uf DSL.
*
* Keywords: page, box, row, col, spacer, pad, heading, text, label,
* divider, link, list, item, gauge, sparkline, status,
* table, columns, form, field, radio, checkbox, button,
* source, let, if, elif, else, for, align, color, bg,
* bold, italic, underline, cache, state, on_submit
* Keywords and values can be updated dynamically via setDslKeywords()
* which is called when the frontend fetches /api/dsl-meta.
*/
const KEYWORDS = new Set([
// Mutable sets — updated from /api/dsl-meta
let KEYWORDS = new Set([
// Fallback defaults (used before API response arrives)
"page", "box", "row", "col", "spacer", "pad",
"heading", "text", "label", "divider", "link",
"list", "item", "gauge", "sparkline", "status",
@@ -23,17 +22,24 @@ const KEYWORDS = new Set([
"checkbox", "button", "source", "let", "if",
"elif", "else", "for", "align", "color", "bg",
"bold", "italic", "underline", "cache", "state",
"on_submit", "meter", "bar_h", "bar_v", "bar",
"heatmap", "component", "use",
"on_submit", "theme", "component", "use",
]);
const WEIGHT_VALS = new Set([
let WEIGHT_VALS = new Set([
"light", "heavy", "double", "rounded",
"bullet", "dash", "number", "arrow",
"left", "center", "right",
"online", "offline", "degraded", "unknown",
"shell", "file", "json", "python", "rns", "param",
"default", "nouveau", "gothic", "bamboo", "circuit", "brutalist",
]);
/** Update keywords and values from /api/dsl-meta response. */
export function setDslKeywords(keywords: string[], values: string[]) {
if (keywords.length > 0) KEYWORDS = new Set(keywords);
if (values.length > 0) WEIGHT_VALS = new Set(values);
}
const uframeLanguage = StreamLanguage.define({
token(stream) {
// Comments

View File

@@ -0,0 +1,35 @@
import { useEffect, useState } from "react";
export interface DslMeta {
keywords: string[];
values: string[];
commands: { label: string; detail: string; section: string; snippet: string }[];
themes: string[];
}
const DEFAULT_META: DslMeta = {
keywords: [],
values: [],
commands: [],
themes: [],
};
let cachedMeta: DslMeta | null = null;
export function useDslMeta(): DslMeta {
const [meta, setMeta] = useState<DslMeta>(cachedMeta || DEFAULT_META);
useEffect(() => {
if (cachedMeta) return;
fetch("/api/dsl-meta")
.then((r) => r.json())
.then((data: DslMeta) => {
cachedMeta = data;
setMeta(data);
})
.catch(() => {});
}, []);
return meta;
}

View File

@@ -6,8 +6,9 @@ import { useEditorStore } from "@/stores/editorStore";
import { usePagesStore } from "@/stores/pagesStore";
import { useUnsavedGuard } from "@/hooks/useUnsavedGuard";
import { useCompile } from "@/hooks/useCompile";
import { uframeHighlight } from "@/components/editor/uframeHighlight";
import { uframeCommandSource } from "@/components/editor/uframeCommands";
import { useDslMeta } from "@/hooks/useDslMeta";
import { uframeHighlight, setDslKeywords } from "@/components/editor/uframeHighlight";
import { uframeCommandSource, setDslCommands } from "@/components/editor/uframeCommands";
import EditorPane from "@/components/editor/EditorPane";
import PreviewPane from "@/components/editor/PreviewPane";
import ToolBar from "@/components/editor/ToolBar";
@@ -47,6 +48,15 @@ export default function EditorView() {
[],
);
// Load DSL metadata (keywords, values, commands) from backend
const dslMeta = useDslMeta();
useEffect(() => {
if (dslMeta.keywords.length > 0) {
setDslKeywords(dslMeta.keywords, dslMeta.values);
setDslCommands(dslMeta.commands);
}
}, [dslMeta]);
// Auto-compile on source changes
useCompile();
useUnsavedGuard();