diff --git a/astro.config.mjs b/astro.config.mjs index 2308de6..ad9dd9e 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -81,6 +81,7 @@ export default defineConfig({ { label: 'Navigation und Sprachen', slug: 'projekt/navigation-i18n' }, { label: 'Suche', slug: 'projekt/suche' }, { label: 'Pagebuilder-Anbindung', slug: 'projekt/pagebuilder-dev' }, + { label: 'Custom Tools', slug: 'projekt/tools-schreiben' }, { label: 'Website mit SSR', slug: 'projekt/ssr-website' }, { label: 'Jobs und Aktionen', slug: 'projekt/jobs-actions' }, { label: 'LLM nutzen', slug: 'projekt/llm-nutzen' }, @@ -101,6 +102,7 @@ export default defineConfig({ { label: 'Medien', slug: 'admin/medien' }, { label: 'Exporte', slug: 'admin/exporte' }, { label: 'Aktionen', slug: 'admin/aktionen' }, + { label: 'Eigene Tools', slug: 'admin/tools' }, { label: 'Benutzer und Rechte', slug: 'admin/benutzer-rechte' }, { label: 'KI-Funktionen', slug: 'admin/ki-funktionen' }, ], @@ -134,6 +136,8 @@ export default defineConfig({ { label: 'Schema-Properties', slug: 'reference/schema-properties' }, { label: 'System-Routen', slug: 'reference/rest-system' }, { label: 'Hook-Kontexte', slug: 'reference/hook-kontexte' }, + { label: 'Custom-Tool-Kontext', slug: 'reference/tool-kontext' }, + { label: 'Custom-Tool-UI', slug: 'reference/tool-ui' }, { label: 'Hook-Referenz: Daten', slug: 'reference/hooks-daten' }, { label: 'Hook-Referenz: Netze und Nachrichten', slug: 'reference/hooks-netze' }, { label: 'Hook-Referenz: Sicherheit und Identität', slug: 'reference/hooks-sicherheit' }, diff --git a/src/assets/admin/custom-tools-overview.png b/src/assets/admin/custom-tools-overview.png new file mode 100644 index 0000000..6d25c7d Binary files /dev/null and b/src/assets/admin/custom-tools-overview.png differ diff --git a/src/content/docs/admin/customizing.mdx b/src/content/docs/admin/customizing.mdx index c80fdad..6aba920 100644 --- a/src/content/docs/admin/customizing.mdx +++ b/src/content/docs/admin/customizing.mdx @@ -66,6 +66,7 @@ Volle Kontext-Tabelle in der Repo-Doku `custom-export-script.md`; Bedienung unte - Eigene Eingabeelemente registrierst du am `WidgetDispatcher` (Feldtyp + `meta.widget`-Name) — erst Schema in [Felder und Widgets](/projekt/felder-widgets/) prüfen. - Neue Admin-Bereiche folgen dem Muster `components/admin/*Tab + *CreateDrawer + *DetailDrawer` mit Suche, Filter, Sortierung und Paginierung. +- **Eigene Tools** für projektspezifische Abläufe liegen unter `meta.tools` und laufen als ES-Module im Admin — siehe [Eigene Tools](/admin/tools/) und [Custom Tools schreiben](/projekt/tools-schreiben/). - KI-Kontext (`aiController`) bleibt automatisch konsistent, solange du `data-ai-id`-Attribute setzt. diff --git a/src/content/docs/admin/tools.mdx b/src/content/docs/admin/tools.mdx new file mode 100644 index 0000000..da15b62 --- /dev/null +++ b/src/content/docs/admin/tools.mdx @@ -0,0 +1,55 @@ +--- +title: 'Eigene Tools im Admin' +description: 'Projektspezifische Werkzeuge bedienen: Navigation, Rechte, Status, Abbrechen und Grenzen.' +--- + +Eigene Tools erscheinen im Projekt als eigener Abschnitt **Tools** unterhalb der +Sammlungen. Sie sind keine Sammlungen und keine Actions: Ein Tool ist eine eigene +Seite mit freiem Ablauf, die der Admin aus dem Projekt lädt. + +## Wo du Tools findest + +![Projekt-Übersicht als Custom Tool im Mock: Kennzahlen zu Seiten, Artikeln und Produkten sowie eine Tabelle mit den letzten Editor-Einträgen](../../../assets/admin/custom-tools-overview.png) + +Das Beispiel ist **projektabhängig**: Es zählt die Sammlungen `pages`, `articles` und +`products`. In einem anderen Projekt heißt das Referenz-Tool anders und nutzt dessen +Sammlungen. + + +- Öffne ein Projekt. Unter den Sammlungen und Aktionen erscheint der Abschnitt + **Tools**. +- Ein Klick öffnet die Tool-Seite. Die Breadcrumb führt zurück zum Projekt. +- Mit `hidden: true` konfigurierte Tools erscheinen nicht in der Liste; die + direkte URL funktioniert weiterhin. +- Fehlende `permissions` blenden das Tool ebenfalls nur aus. Datenrechte bleiben + serverseitig pro Sammlung geschützt. + +## Bedienung + +| Element | Bedeutung | +|---|---| +| Status-Badge | Bereit, Lädt, Läuft, Abgebrochen oder Fehler | +| **Neu laden** | Startet das Tool neu und räumt den vorherigen Lauf auf | +| **Abbrechen** | Bricht den Lauf zwischen `await`-Schritten ab | +| **Protokoll** | Log-Ausgaben des Tools, inklusive Details | +| **Fortschritt** | Fortschrittsbalken, sofern das Tool `ctx.progress` nutzt | + +Ein Tool kann eigene UI mitbringen oder die Admin-Widgets verwenden. Beides sieht +bewusst wie der Admin aus; das Projekt-Theme kann über `meta.ui.css` gescopet +ergänzt werden. + +## Grenzen + +- Tools laufen im Browser mit der Admin-Session. Es gibt in v1 keine iframe-Sandbox. +- Ein Tool kann den Browser-Tab blockieren, wenn es synchron schwer rechnet. + Abbrechen wirkt nur zwischen `await`-Schritten. +- Gleichzeitige Änderungen sind Last-Write-Wins; das Tool muss selbst neu lesen + oder idempotent arbeiten. +- Nach einem Deployment lädt der Browser das Tool-Modul über einen Config-Hash neu. + +## Für Projekt-Entwickler + +Wie du Tools konfigurierst und schreibst, steht unter +[Custom Tools schreiben](/projekt/tools-schreiben/). Die UI-Referenz liegt unter +[Custom-Tool-UI](/reference/tool-ui/), der Kontext unter +[Custom-Tool-Kontext](/reference/tool-kontext/). diff --git a/src/content/docs/projekt/tools-schreiben.mdx b/src/content/docs/projekt/tools-schreiben.mdx new file mode 100644 index 0000000..df0c78b --- /dev/null +++ b/src/content/docs/projekt/tools-schreiben.mdx @@ -0,0 +1,86 @@ +--- +title: 'Custom Tools schreiben' +description: 'Projektspezifische Werkzeuge als ES-Module im Admin: Config, mount, ctx und Grenzen.' +--- + +Ein Custom Tool ist ein ES-Modul im Projekt-Repo, das der Admin als eigene Seite +lädt. Es läuft im Browser mit der Admin-Session und demselben `ctx`/`context`. + +## Konfiguration + +```yaml title="api/config.yml" +meta: + tools: + - name: stock-import + label: { de: Bestandsabgleich, en: Stock reconciliation } + module: _/assets/tools/stock-import/index.js + permissions: [inventory] + capabilities: [db, files, xlsx, ui] +``` + +`name` ist ein Slug (`[a-z0-9-_]+`). `module` wird projektrelativ aufgelöst; +`.js` ist der Default, `.mjs` optional. `permissions` steuern nur die Sichtbarkeit +in der Navigation – Datenrechte bleiben serverseitig. + +## Modul + +```js title="api/assets/tools/stock-import/index.js" +export const messages = { + de: { 'stock.title': 'Bestand' }, + en: { 'stock.title': 'Stock' }, +} + +export async function mount(el, ctx) { + el.textContent = ctx.i18n.t('stock.title') + return () => el.replaceChildren() +} +``` + +- `mount(el, ctx)` ist Pflicht. +- Die Rückgabe ist eine optionale Cleanup-Funktion. +- `ctx === ctx.context`; beide Bindings sind dasselbe Objekt. +- Module laufen same-origin und sind vertrauenswürdig (eigenes Repo/Deploy). + +## Kontext + +| Feld | Inhalt | +|---|---| +| `ctx.db` | `find`, `count`, `loadAll`, `create`, `update`, `delete`, `updateMany`, `deleteMany` | +| `ctx.files` | Datei/Blob/URL → `{ src, name, type, size }` | +| `ctx.xlsx` | gebündeltes `xlsx-js-style` | +| `ctx.auth` | `{ id, username, role, permissions }` | +| `ctx.state` | `Map` pro Tool-Instanz | +| `ctx.i18n` | `t`, `text`, `has`, `add`, `extend`, `onChange` | +| `ctx.theme` | `{ mode, tokens }` read-only | +| `ctx.log`, `ctx.progress`, `ctx.signal` | Log, Fortschritt, Abbrechen | +| `ctx.project`, `ctx.tool`, `ctx.hostData` | read-only Config/Clone | + +`ctx.db.update` ist Last-Write-Wins. Tools mit Read-Modify-Write müssen vor dem +Schreiben neu lesen oder idempotent arbeiten. `updateMany` bricht beim ersten +Fehler ab (`failedIndex`), ohne Rollback. + +## UI + +`ctx.ui.field` und `ctx.ui.form` rendern die vorhandenen Admin-Widgets. Eigene UI +läuft über DOM/Custom Elements, `ctx.ui.slot`, `ctx.ui.styles` und +`ctx.ui.registerWidget`. Details: [Tool-UI-Referenz](/reference/tool-ui/). + +## Auslieferung und Deployment + +Tool-Module liegen als Projekt-Assets unter `_/assets/...`. Der Browser lädt sie +per dynamischem `import()`; dabei wird **kein** Admin-Token mitgeschickt. Die +Assets müssen deshalb öffentlich lesbar sein, so wie Pagebuilder-Registries heute. +Das ist eine **Projekt-/Deployment-Eigenschaft**: Wenn ein Projekt Assets schützt, +muss die Server-Konfiguration angepasst werden, bevor Tools dort laden können. + +`.js` ist der Default und wird von üblichen Servern als JavaScript ausgeliefert. +`.mjs` ist optional und braucht einen belegten JavaScript-MIME. Der Mock liefert +für beide `text/javascript`; im echten Deployment muss der MIME pro Projekt +geprüft werden. + +## Grenzen + +- Keine iframe-Sandbox in v1. +- Kein serverseitiges `_tools`-Rechtemodell. +- Kein Worker/Timeout; `ctx.signal` wirkt zwischen `await`-Schritten. +- `meta.ui.css` benötigt `@scope`. diff --git a/src/content/docs/reference/tool-kontext.mdx b/src/content/docs/reference/tool-kontext.mdx new file mode 100644 index 0000000..f6cfff3 --- /dev/null +++ b/src/content/docs/reference/tool-kontext.mdx @@ -0,0 +1,64 @@ +--- +title: 'Custom-Tool-Kontext' +description: 'ctx/context-Referenz für Custom Tools: db, files, xlsx, auth, i18n, theme und Signale.' +--- + +`ctx` und `context` sind dasselbe Objekt. Die folgende Tabelle beschreibt den +v1-Vertrag; Erweiterungen sind additiv. + +| Feld | Signatur / Inhalt | +|---|---| +| `ctx.context` | `ctx` selbst | +| `ctx.apiVersion` | `1` | +| `ctx.uiVersion` | `1` | +| `ctx.host` | `"tool"` (später `"macro"`, `"server"`) | +| `ctx.capabilities` | deklarierte Fähigkeiten | +| `ctx.project` | read-only Projekt-Clone | +| `ctx.tool` | read-only Tool-Config | +| `ctx.auth` | `{ id, username, role, permissions } \| null` | +| `ctx.lang` | aktive Admin-Sprache | +| `ctx.languages` | Inhaltssprachen des Projekts | +| `ctx.state` | `Map` pro Tool-Instanz | +| `ctx.signal` | `AbortSignal` beim Verlassen/Abbrechen | +| `ctx.hostData` | Host-Daten (ToolView: `projectId`, `toolName`) | +| `ctx.asset(spec)` | Projekt-Asset-Spezifizierer auflösen | + +## `ctx.db` + +```ts +find(collection, { filter?, sort?, limit?, offset?, select?, lookup? }): Promise[]> +count(collection, { filter? }): Promise +loadAll(collection, opts?, onProgress?): Promise[]> +create(collection, data): Promise> +update(collection, id, data): Promise>> +delete(collection, id): Promise +updateMany(collection, items): Promise<{ ok, failedIndex?, error? }> +deleteMany(collection, ids): Promise<{ ok, failedIndex?, error? }> +``` + +`loadAll` batcht mit 500 Datensätzen, respektiert `ctx.signal` und ruft +`onProgress(done,total)`. + +## `ctx.i18n` + +```ts +t(key, vars?): string +text(value, vars?): string +has(key): boolean +add(lang, messages): () => void +extend(messages, lang?): () => void +onChange(cb): () => void +``` + +Reihenfolge: Laufzeit → Modul → Projekt → Admin-JSON → Key. ICU/Plural wird über +`IntlMessageFormat` formatiert. + +## `ctx.files` + +`ctx.files.get(fileOrBlobOrUrl)` liefert `{ src, name, type?, size? }`; `src` ist +eine Data-URL. Uploads werden erst beim Speichern des Eintrags persistiert. + +## `ctx.xlsx` + +Das gebündelte `xlsx-js-style`-Modul. Große Arbeitsmappen blockieren den +Main-Thread; das ist in v1 dokumentiert und nicht durch einen Worker abgefedert. diff --git a/src/content/docs/reference/tool-ui.mdx b/src/content/docs/reference/tool-ui.mdx new file mode 100644 index 0000000..a94ab49 --- /dev/null +++ b/src/content/docs/reference/tool-ui.mdx @@ -0,0 +1,85 @@ +--- +title: 'Custom-Tool-UI' +description: 'Widget-Katalog, Feld-/Form-Handles, registerWidget und gescoptes Projekt-CSS.' +--- + +`ctx.ui` bietet Admin-Widgets und eine kleine DOM-API. Alle Widgets laufen auf +denselben Admin-Komponenten; eigene UI ist ein opt-in. + +## Feld-Widgets + +```js +const field = ctx.ui.field({ + collection: 'editor_lab', + field: 'title', + value: 'Hallo', + onChange: (value) => console.log(value), +}) +el.appendChild(field.el) +``` + +Typisierte Helfer: `text`, `number`, `boolean`, `select`, `date`, `richtext`, +`json`, `chips`, `file`, `files`, `foreignKey`, `foreignKeys`, `foreignFile`, +`object`, `objectArray`, `navigationTree`, `pagebuilder`. + +`FieldHandle`: `el`, `update`, `destroy`, `getValue`, `setValue`, `validate`, +`flush`. + +## Formulare + +```js +const form = ctx.ui.form({ + collection: 'editor_lab', + fields: ['title', 'score'], + value: { title: 'Formular', score: 7 }, +}) +``` + +`FormHandle` ergänzt `getValue`, `setValue`, `validate` und `flush` (wartet auf +laufende Uploads). + +## Deskriptoren und Handles + +```js +const field = ctx.ui.field({ collection: 'editor_lab', field: 'title' }) +const page = ctx.ui.page({ title: 'Bestand', content: field }) + +ctx.ui.render(el, { + type: 'section', + props: { title: 'Details' }, + slots: { content: [{ type: 'badge', props: { content: '3 Unterschiede' } }] }, +}) +``` + +Unbekannte Typen rendern einen sichtbaren Platzhalter; Fehler fängt die +`WidgetBoundary`. + +## Eigene Deskriptor-Typen + +```js +const dispose = ctx.ui.registerWidget('tool.demo.badge', ({ el, props }) => { + el.textContent = props.label + return { update: (p) => (el.textContent = p.label), destroy: () => el.replaceChildren() } +}) +``` + +Built-ins nur mit `{ replace: true }` überschreiben. Die Registrierung endet mit +dem Tool oder über `dispose()`. + +## Theme und CSS + +`ctx.ui.styles` liefert Admin-Primitives plus rohe Projekt-CSS für Shadow DOM. +`meta.ui.css` wird in `@scope (.tibi-tool-)` geladen; Projekt-CSS ohne +eigenen Tool-Präfix schreiben. Ohne `@scope` wird die Datei nicht geladen. + +## Tabellen + +```js +const table = ctx.ui.table({ + collection: 'editor_lab', + rows, + columns: [{ field: 'title', label: 'Titel' }], + selection: 'multi', + onSelectionChange: (ids) => console.log(ids), +}) +```