diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 168c1f1..0000000 --- a/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ - -# CMS (db-less) secrets + users + build -cms/.env -cms/users.json -public/ -resources/ diff --git a/ADMIN.md b/ADMIN.md index 9ece62b..7c4ac92 100644 --- a/ADMIN.md +++ b/ADMIN.md @@ -1,36 +1,68 @@ -# Admin (openbureau-core CMS) +# Admin (Decap CMS) -The site is edited through **openbureau-core** — a small Node CMS (vendored at -`cms/core`) that builds the Hugo site and serves it together with an editor at -`/admin`. It is **DB-less**: file-based login (`auth: 'local'`) and Markdown on -disk. No Supabase, no Postgres. +A lightweight, git-based editor at `/admin`. It commits markdown straight to the +Gitea repo — no database, no server. The public site stays static; rebuilding it +after an edit is a separate step (see *Deploy* below). -## What's where +## 1. Put the repo on Gitea -- `cms/kgva.config.js` — the content model (collections + fields) the editor renders. -- `cms/core/` — the engine (git subtree of `git.openbureau.ch/karim/openbureau-core`). - Update it with: - `git subtree pull --prefix=cms/core https://git.openbureau.ch/karim/openbureau-core.git main --squash` -- `cms/.env` — `JWT_SECRET` (sign-in secret) + `ADMIN_EMAILS`. Copy from `.env.example`. -- `cms/users.json` — the user store (bcrypt hashes). Git-ignored. Seed with: - `docker compose run --rm cms node /site/cms/seed-admin.mjs you@mail yourpass` +The site must live in a Gitea repo (e.g. `git.kgva.ch/karim/kgva`). Set the same +`repo` and `branch` in `static/admin/config.yml`. -## Run +## 2. Create a Gitea OAuth app + +In Gitea: **Settings → Applications → Create OAuth2 application**. + +- Redirect URI: `https:///admin/` +- Copy the **Client ID** into `static/admin/config.yml` → `backend.app_id`. + +Decap uses PKCE, so no client secret and no auth proxy are needed. + +## 3. Edit + +Open `https:///admin/`, log in with Gitea. New project: +*Portfolio → New Project*; images upload into `static/img` and are referenced as +`/img/...`. Saving commits to the repo on the configured branch. + +## 4. Deploy (rebuild after an edit) + +Because the site is baked into the Docker image, a commit must trigger a rebuild. +Pick one: + +- **Manual:** on the host, `docker compose up -d --build`. +- **Gitea Actions** (`.gitea/workflows/deploy.yml`), sketch: + + ```yaml + on: { push: { branches: [main] } } + jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: docker compose up -d --build # on a runner with access to the host + ``` + + (Adjust to your Proxmox/LXC setup — same place OPENBUREAU deploys.) + +- **Webhook:** point a Gitea webhook at a tiny script on the host that runs the + `docker compose` line. + +## Local editing (no auth) ``` -cp cms/.env.example cms/.env # set JWT_SECRET (openssl rand -hex 32) -docker compose build -docker compose run --rm cms node /site/cms/seed-admin.mjs karim@gabrielevarano.ch # once -docker compose up -d +npx decap-server # in one terminal +# uncomment `local_backend: true` in config.yml +hugo server # in another +# open http://localhost:1313/admin/ ``` -Open `http://:8080/admin/`, log in, edit. *Speichern* writes the Markdown, -*Vorschau* builds a draft preview, *Publizieren* rebuilds the public site. +## Self-hosting the CMS bundle (optional) -## Deploy +To avoid the CDN (in the spirit of the colophon), download the bundle once and +point `static/admin/index.html` at it: -A single container (Hugo build + Node serve). See `deploy/` — -`provision-lxc.sh` stands up an LXC and does all of the above (incl. a seeded -admin whose password lands in `cms/ADMIN_PASSWORD.txt`); `Caddyfile` terminates -TLS and reverse-proxies the domain. Safe cutover: provision a NEW container on a -temporary IP, verify, point Caddy at it, then retire the old one. +``` +curl -L https://unpkg.com/decap-cms@^3.3.3/dist/decap-cms.js \ + -o static/admin/decap-cms.js +# then in index.html: +``` diff --git a/assets/css/custom.css b/assets/css/custom.css index a768610..01d332b 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -10,10 +10,15 @@ .prompt { font-family: var(--font-family-mono); display: flex; - flex-wrap: wrap; + /* stay on ONE line: when the command gets too wide, clip the left (older) + part terminal-style and keep the caret/right end visible (shell.js scrolls + it to the end). previously flex-wrap:wrap dropped to a 2nd line and bugged. */ + flex-wrap: nowrap; align-items: flex-start; gap: 0; line-height: 1.5; + overflow: hidden; + white-space: nowrap; } .prompt__user { font-weight: 700; } .prompt__path { color: var(--color-text-muted); } @@ -299,9 +304,11 @@ body { user-select: text; } -/* dotted content + footer links: unchanged at rest, invert to black on hover */ +/* dotted content + footer links: unchanged at rest, invert to black on hover. + include the header language switch so en/de behave like every other link. */ main a:hover, -footer a:hover { +footer a:hover, +.lang-switch:hover { background: var(--color-text-primary); color: var(--color-bg-primary); border-bottom-color: transparent; diff --git a/assets/js/filebrowser.js b/assets/js/filebrowser.js index cbcc85a..52cfbee 100644 --- a/assets/js/filebrowser.js +++ b/assets/js/filebrowser.js @@ -31,7 +31,7 @@ if (img.complete) img.style.opacity = "1"; // already cached → no fade } - function select(i, silent) { + function select(i, silent, scroll) { if (!rows.length) return; if (i < 0) i = 0; if (i >= rows.length) i = rows.length - 1; @@ -39,7 +39,9 @@ for (var r = 0; r < rows.length; r++) rows[r].classList.remove("is-selected"); var a = rows[i]; a.classList.add("is-selected"); - if (a.scrollIntoView) a.scrollIntoView({ block: "nearest" }); + // only scroll for keyboard nav — scrolling on hover nudges edge rows out from + // under the cursor, so the top/bottom row "sometimes" can't be clicked. + if (scroll && a.scrollIntoView) a.scrollIntoView({ block: "nearest" }); var src = a.getAttribute("data-img"); var tkey = a.getAttribute("data-text"); @@ -100,8 +102,8 @@ var t = e.target; if (t && (t.isContentEditable || /^(input|textarea|select)$/i.test(t.tagName))) return; if (!rows.length) return; - if (e.key === "ArrowDown") { e.preventDefault(); select(cur + 1); } - else if (e.key === "ArrowUp") { e.preventDefault(); select(cur - 1); } + if (e.key === "ArrowDown") { e.preventDefault(); select(cur + 1, false, true); } + else if (e.key === "ArrowUp") { e.preventDefault(); select(cur - 1, false, true); } else if (e.key === "Enter") { var href = rows[cur] && rows[cur].getAttribute("href"); if (href) { if (window.pjax) window.pjax(href); else window.location.href = href; } diff --git a/assets/js/shell.js b/assets/js/shell.js index 65b59a9..59f39e1 100644 --- a/assets/js/shell.js +++ b/assets/js/shell.js @@ -8,8 +8,13 @@ var input = document.querySelector(".prompt__input"); var out = document.querySelector(".prompt__out"); var menu = document.querySelector(".menu"); + var promptEl = document.querySelector(".prompt"); if (!bar || !input) return; + // keep the caret/right end in view: when the line overflows, scroll it fully + // right so the left (older) part clips off terminal-style instead of wrapping. + function keepEnd() { if (promptEl) promptEl.scrollLeft = promptEl.scrollWidth; } + var home = bar.getAttribute("data-home") || "/"; // command → url: the always-open menu, plus any context routes (e.g. the @@ -307,7 +312,7 @@ caretToEnd(); say(""); } }); - input.addEventListener("input", function () { say(""); }); + input.addEventListener("input", function () { say(""); keepEnd(); }); function caretToEnd() { var r = document.createRange(); @@ -316,6 +321,7 @@ var s = window.getSelection(); s.removeAllRanges(); s.addRange(r); + keepEnd(); } // start typing anywhere → the keystroke goes straight into the command line diff --git a/cms/.env.example b/cms/.env.example deleted file mode 100644 index 60c77a8..0000000 --- a/cms/.env.example +++ /dev/null @@ -1,7 +0,0 @@ -# kgva CMS — DB-less (openbureau-core, auth: local). Copy to cms/.env, fill in. -# Sign-in token secret — random: openssl rand -hex 32 -JWT_SECRET=CHANGE_ME -# Always-admin emails (comma-separated). -ADMIN_EMAILS=karim@gabrielevarano.ch -# Commit edits back to git on publish (optional). -GIT_PUBLISH=false diff --git a/cms/core/.gitignore b/cms/core/.gitignore deleted file mode 100644 index 15c78a2..0000000 --- a/cms/core/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -node_modules/ -.env -.env.* -*.log -.DS_Store - -# build output -admin/dist/ diff --git a/cms/core/HANDOVER.md b/cms/core/HANDOVER.md deleted file mode 100644 index c9750c0..0000000 --- a/cms/core/HANDOVER.md +++ /dev/null @@ -1,202 +0,0 @@ -# openbureau-core — handover - -## For a fresh instance — START HERE -- **Repo (local):** cloned at `/home/karim/openbureau-core`. Remote - `git.openbureau.ch/karim/openbureau-core` is **private**; auth via an HTTPS token - in `~/.git-credentials` (the SSH key `id_ed25519_openbureau` is currently rejected - by gitea/proxmox — server seems rebuilt; fix the key or keep using the token). -- **Where the work is:** branch **`stufe-2-5-scaffold`** (pushed, NOT merged). `main` - is still the pre-stage-2 foundation. Open a PR or merge when happy. -- **Done:** stages 1–4 complete, stage 5 *scaffolded*. See "Migration stages" below - (markers `[done]`/`[scaffold]`). New code: `api/src/collections.js`, - `api/src/plugin-manager.js`, `api/src/plugins/dialog/index.js` + their tests. -- **Run tests:** `cd api && npm install && node --test` → **44 green**. `node_modules` - is git-ignored; the fresh clone needs `npm install` once. -- **Status:** stages 1–9 DONE; openbureau + kgva BOTH LIVE on core. openbureau on dev CT134 (schema editor + supabase); kgva LIVE at karimgabrielevarano.xyz via core (Variant A: CMS serves the site; CT130 rebuilt in-place — Docker + kgva-cms on :8081, nginx :80 proxies to it and keeps /media, static timer disabled, Caddy untouched). CAVEAT: kgva GIT_PUBLISH=false → edits on CT130 disk, not git-backed (enable GIT_PUBLISH + creds to fix). Rollback for kgva: restore nginx .static.bak + re-enable kgva-deploy.timer + stop kgva-cms. Original stage-5 note kept below for history. -- **Immediate next action (historic):** stage 5 + 5.5 **DONE** on the branch (commits `c4230a4` - cutover, `5e06337` DDL, `a93d11a` migration runner). Plugin-manager singleton - (`api/src/plugins.js`) wired into `index.js`/`stats.js`/`publish.js`, hard-coded - dialog removed, source moved under `api/src/plugins/dialog/`; DDL captured into - `plugins/dialog/migrations/001_dialog.sql` (idempotent); `api/src/migrate.js` - applies migrations once via `schema_migrations` at boot. **53 tests green** + a real - boot. **Live-verified on the dev stack** (CT 134): a 2nd cms instance from - `cms-cms:latest` (my `src` mounted over `/app/src`, real Supabase via `kong:8000`, - empty SITE_DIR → no writes) returned **byte-identical** `/api/forums` & `/api/recent` - vs the running container, `/api/content` 401 on both; the runner's tracking SQL was - proven on a throwaway DB. **NOT deployed** (the running stack still runs old code). - NEXT (stage 8): deploy core to openbureau — needs `DATABASE_URL` set for the cms - service (e.g. `postgres://postgres:$POSTGRES_PASSWORD@db:5432/postgres`) so the - runner can apply migrations, then full login/edit/publish verify before cutting the - live container over. Then stages 6 (admin `/api/schema`), 7 (local auth), 9 (kgva). - Dev stack: Proxmox CT 134 `openbureau-dev`, repo `/opt/openbureau`, compose - `cms/docker-compose.yml`, containers openbureau-{cms,auth,kong,rest,db}, dev URL - dev.openbureau.ch. **Do NOT push a blind refactor to prod.** -- **Env gotcha:** editing a file that's open in Karim's VSCode makes the Edit/Write - tool hang/"interrupt" — write such files via Bash (`cat > f <<'EOF' … EOF`) instead. - -## Goal -Extract a generic, **schema-driven** Hugo CMS engine ("openbureau-core") from the -openbureau site's bundled CMS, so that **both** openbureau and -karimgabrielevarano.xyz (kgva) consume it as a dependency. Decision: own repo -(this one), and migrate openbureau to depend on it. - -## State (done this session) -- Repo `karim/openbureau-core` created on Gitea, foundation pushed. - - `api/` = engine copied verbatim from `karim/OPENBUREAU` → `cms/api` (Hono/Node). - - `admin/` = React/Vite SPA copied verbatim from `OPENBUREAU/cms/admin`. - - `api/src/config.js` = NEW loader: reads `CMS_CONFIG` → a site config module. - - `examples/openbureau.config.js`, `examples/kgva.config.js` = target schemas. - - `README.md` = config API + plugin model + the migration checklist. -- Nothing in the live openbureau CMS was changed yet. - -## Engine is ~80% generic. openbureau-specific seams to make config-driven: -1. [DONE] `api/src/files.js` `classify(rel)` + the `order` map — now derived from - `config.collections` via `api/src/collections.js` (classify/buildPath/compareEntries); - `buildPath` exposed there too. -2. [DONE] `api/src/routes/stats.js` — now per-collection (`statKey`/`draftStatKey`), - dialog counts gated by `hasPlugin('dialog')`. -3. `api/src/index.js` — always mounts dialog routes + runs `syncLibrary` on boot. - → only when `config.plugins` includes `dialog`. -4. `api/src/routes/publish.js` — calls `syncLibrary`. → gate by plugin. -5. `admin/src/App.jsx` (714 lines) — hard-codes `SECTIONS`, `KIND_LABEL`, the field - set, type dropdown, `buildPath`. → render sidebar groups + editor fields from the - schema (add `GET /api/schema` returning `config.collections`). -6. dialog subsystem (`dialog-store.js`, `routes/dialog.js`, `routes/comments.js` - + Supabase tables forums/threads/comments + library↔thread sync) = the `dialog` - **plugin** (openbureau on, kgva off). First gate by config, later move to - `api/src/plugins/dialog/`. - -## CRITICAL -api and admin share a data contract (stats keys, field shapes). Generalising the -**backend alone breaks the live openbureau admin** — they must change together and -be tested on the live Supabase/Hugo stack. Do NOT push a blind half-refactor to -production openbureau. - -## Plugin system (decided architecture) -Everything beyond the generic engine is a **plugin**; the engine gets a small -**plugin manager**. A plugin = `api/src/plugins//index.js` exporting a -manifest `{ name, routes:[{path,app,public?,admin?}], onBoot, onPublish, onPreview, -migrations:[…], stats, admin:{…} }`. The manager reads `config.plugins`, imports -each, mounts routes in the right auth tier (public before `requireAuth`, rest -after, `admin:true` behind `requireAdmin`), registers boot/publish/preview/stats -hooks, runs migrations; the admin SPA loads UI from `admin/src/plugins/`. -openbureau's extras become plugins (first: `dialog`); kgva enables none. -(Full spec in README → "Plugins".) - -## Migration stages (also README checklist) -1. [done] repo + engine + config loader + example configs. -2. [done] files.js classify/order/buildPath ← config.collections - (new `api/src/collections.js` = pure classify/buildPath/compareEntries/contentStats; - files.js uses it, loads config lazily in listEntries; `api/test/collections.test.js` - proves 1:1 vs openbureau.config.js). All api tests green. -3. [done] stats.js ← collections: `content` via `statKey` + `draftStatKey` - (openbureau beitrag got `draftStatKey: 'entwuerfe'`), dialog counts gated by - `hasPlugin('dialog')` (pluginless site does zero DB reads here). -4. [built, not wired] **plugin manager** — `api/src/plugin-manager.js` - (`loadPlugins(names)` imports `plugins//index.js`; `createManager(manifests)` - = pure wiring: `mountPublic`/`mountPrivate(+requireAdmin)`, `runBoot/runPublish/ - runPreview`, `collectStats` (merged under plugin name), `migrations()` (resolves - file URLs; execution deferred to the live DB)). Unit-tested in - `api/test/plugin-manager.test.js` (tiers/hooks/stats/migrations). TODO: wire into - index.js (stage 5), and admin loads plugin UI from `admin/src/plugins/`. -5. [DONE — commits c4230a4 (cutover) + 5e06337 (DDL), live e2e verified] extract openbureau extras - into the **dialog** plugin. DONE: `api/src/plugins/dialog/index.js` = manifest - wrapping the existing modules unchanged (public reads + widget login(rate-limited) - + authed writes + self-guarding mod/adminForums sub-apps; onBoot/onPublish = - syncLibrary; stats = forum/thread/comment counts). CUTOVER DONE: shared manager - singleton `api/src/plugins.js` wired into index.js (mountPublic before requireAuth, - mountPrivate(+requireAdmin) after, manager.runBoot at serve()), publish.js - (manager.runPublish) and stats.js (manager.collectStats, `{content,users,dialog}` - contract preserved); hard-coded dialog REMOVED from all three; dialog source - physically moved to `plugins/dialog/{dialog-store,dialog,comments}.js` (imports - fixed). Tested: `api/test/{dialog-plugin,cutover}.test.js`, 48 green + real boot. - DDL CAPTURED (commit 5e06337): `migrations/001_dialog.sql` from the live dev DB, - idempotent, validated on a throwaway DB; manifest `migrations: ['001_dialog.sql']`. - LIVE E2E VERIFIED (dev CT 134): 2nd cms instance from cms-cms:latest with my src - mounted, real Supabase via kong:8000 — /api/forums & /api/recent byte-identical to - the running container, /api/content 401 both, no writes. Migration runner = stage - 5.5 (commit a93d11a, see new item below). Deploy is stage 8. - NB: dialog-store.js still filters `e.kind === 'beitrag'` — openbureau-specific, - already moved with the plugin. -5.5 [done, commit a93d11a] migration runner — `api/src/migrate.js` `runMigrations` - tracks applied migrations in `public.schema_migrations` and applies each declared - file once, in its own transaction, at boot (wired into index.js before runBoot). - Lean: no declared migrations → no DB connection, no pg import (kgva stays DB-less). - Executor is injectable (unit-tested without a DB); default uses a lazily-imported - `pg` against `DATABASE_URL`, warning-and-skipping if unset. Needs `DATABASE_URL` - wired into the cms service env for openbureau (stage 8). Tested: migrate.test.js + - tracking SQL proven on the live dev Postgres (throwaway DB). -6. [DONE, commits f709b5d + f8f4131 + 59b9e20] schema-driven admin. `GET /api/schema` - + `GET /api/system` + a System panel; AND the editor now renders sidebar groups + - fields from the site's collections (admin/src/fields.jsx: per-type controls - string/text/slug/number/date/bool/select/list/image/color/markdown; form↔frontmatter - + path building). One admin serves any site. Unknown frontmatter is PRESERVED on - save (__raw) — no data loss. Verified working via kgva (stage 9). NOTE: openbureau - is NOT yet redeployed with the schema editor (its running image keeps the bespoke - one); when it is, its config field types (color→swatch, layout opts) are already - tuned. Visual review of the openbureau editor by Karim still recommended before - redeploying it. -7. [DONE, commit 86f9f57] **auth provider** (`config.auth` = `supabase` | `local`). - `api/src/auth-local.js` = file-based users (bcryptjs) + self-signed HS256 JWTs of - the SAME claim shape; `routes/auth.js` POST /api/auth/login (mounted only when - auth:local); supabase.js made null-safe (fail-fast moved to index.js); auth/stats/ - users.js branch local↔GoTrue; admin supabase.js gains a local-auth SHIM (same - interface) when VITE_SUPABASE_URL is absent → App/api untouched, supabase path - byte-identical. examples/kgva.config.js → auth:'local'. Verified: 59 tests + - DB-less boot smoke (no Supabase: local login → /api/me admin → stats-from-file) - + openbureau redeployed and confirmed unregressed (auth=supabase). -8. [DONE, OB main 3d902a0] openbureau consumes core. Mechanism: **git subtree** at - `OPENBUREAU/cms/core` (fits the pull-based deploy; update via - `git subtree pull --prefix=cms/core main --squash`). docker-compose builds - the cms image from `./core`; `cms/openbureau.config.js` (CMS_CONFIG) carries the - content model; old vendored cms/api+cms/admin removed. DATABASE_URL left unset (the - stack's migrate service owns db/schema.sql incl. dialog). Deployed to dev CT 134 - and FULL write-verified: login/list/edit/preview/publish/dialog all identical, no - git divergence (GIT_PUBLISH=false). Rollback image `cms-cms:rollback` kept. -9. [DONE — LIVE on core (Variant A)] kgva consumes core. In - `karim/kgva` (git.kgva.ch, main 51a94b8): `cms/core` = core subtree, `cms/kgva.config.js` - (project/page/section, auth:'local', no plugins), `docker-compose.yml` builds the core - image (Hugo 0.163.3, no VITE_SUPABASE_URL → local-auth admin), `cms/seed-admin.mjs`, - updated deploy/provision-lxc.sh + ADMIN.md. VERIFIED DB-less end-to-end on real content - (staged on CT134): local login → schema editor → 42 entries (de/en) → stats → - create/preview/cleanup; image galleries preserved. The LIVE site is UNCHANGED — CT130 - still deploys statically (timer → `hugo` → nginx; hugo ignores cms/); the CMS is dormant. - PENDING (deliberate, his call — repoints a live domain): provision a kgva-core container - (deploy/provision-lxc.sh, free CTID + temp IP) and decide topology — CMS serves the site - (Caddy → it) OR CMS edits + GIT_PUBLISH=true commits to git and the CT130 static timer - keeps serving. Then Caddy cutover per deploy/README. openbureau also not yet redeployed - with the schema editor (stage 6 note). - -## How to run / test -openbureau CMS stack: `OPENBUREAU/cms/docker-compose.yml` (Node api + Hugo + -Supabase: postgres/kong/gotrue). Env in `cms/.env` (see `.env.example`): -SUPABASE_URL/SERVICE_KEY, JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY (derive via -`scripts/generate-keys.mjs`), ADMIN_EMAILS, SITE_URL, GIT_*. For core add -`CMS_CONFIG` (path to the site config) and `SITE_DIR` (site repo mount, default -`/site`). Admin at `/admin`, preview `/_preview`, publish builds `public/`. - -## Infra access (NO secrets in this file) -- Proxmox node `192.168.1.2`, user `root`. Password: **ask the user** (provided ad - hoc, not stored). No SSH key installed — use SSH_ASKPASS, or install a key first. -- Gitea = container 120 (`pct exec 120 …`), ROOT_URL `git.openbureau.ch`, sqlite at - `/var/lib/gitea/data/gitea.db`. Make a token: - `pct exec 120 -- su gitea -s /bin/bash -c "/usr/local/bin/gitea admin user - generate-access-token --username karim --scopes all --token-name X --raw - --config /etc/gitea/app.ini"`. Keep token `kgva-deploy`. Delete tokens via sqlite - (`DELETE FROM access_token WHERE name LIKE '…'`) since the API needs the account - password. -- Push without local git auth: tar → scp to node → `pct push 120` into the gitea - container → `git push http://karim:@127.0.0.1:3000/karim/.git`. -- kgva site deploy: container 130 (kgva-website). nginx serves - `/var/www/karimgabrielevarano.xyz` (= ZFS `tank/kgva-website`, owned uid 100000). - systemd timer runs `/opt/kgva-deploy.sh` every 60s: pull `karim/kgva` (token - `kgva-deploy`) → `hugo` build → copy into webroot. So a push to `karim/kgva` is - live in ~1 min. Self-hosted video at `/var/www/media` (nginx `/media/` alias, - outside the build). Public TLS for the domain terminates upstream → 192.168.1.130:80. - -## Watch out -- Gitea does NOT send CORS on its OAuth token endpoint → a browser git-CMS - (Decap-style) can't auth without a same-origin proxy. openbureau-core uses its own - Supabase auth, so this doesn't affect it; it's why kgva edits go via git push. -- Hugo versions: openbureau CMS bundles 0.161.1; the kgva site needs 0.163.3 - features. Mind the version a core instance builds with. diff --git a/cms/core/README.md b/cms/core/README.md deleted file mode 100644 index 3f6f3b6..0000000 --- a/cms/core/README.md +++ /dev/null @@ -1,149 +0,0 @@ -# openbureau-core - -A generic, schema-driven Hugo CMS engine, extracted from the openbureau site. -A site provides one config (collections + plugins); the core gives it an admin, -content CRUD over `content/**/*.md` (gray-matter), real Hugo preview/publish, -Supabase auth, uploads and git backup. openbureau and karimgabrielevarano.xyz -both consume it. - -## How a site uses it - -1. Point `CMS_CONFIG` at a config module (e.g. `/site/cms.config.js`). -2. Mount the site repo at `SITE_DIR` (default `/site`). Run the container. - -``` -SITE_DIR=/site -CMS_CONFIG=/site/cms.config.js -SUPABASE_URL=… SUPABASE_SERVICE_KEY=… JWT_SECRET=… ADMIN_EMAILS=… -``` - -## Config API - -```js -export default { - site: 'name', - admins: ['you@example.com'], // bootstrap admins (env ADMIN_EMAILS still wins) - auth: 'supabase', // 'supabase' (GoTrue) | 'local' (file JWTs, no DB) - plugins: ['dialog'], // optional features (see Plugins) - collections: [ - { - kind: 'project', // internal id - label: 'Portfolio', // shown in the admin - order: 0, // sort order in the list - path: 'portfolio/:slug', // file pattern under content/ (:params captured) - // index: true, // matches _index.md (a section) - // fallback: true, // matches anything not matched above - // sections: [...], // fixed choices for a :section param - statKey: 'projects', // key in the stats response - fields: [ // editor form, in order - { name: 'title', type: 'string', required: true }, - { name: 'images', type: 'list', of: { src: 'image', name: 'string' } }, - { name: 'body', type: 'markdown' }, - ], - }, - ], -}; -``` - -`path` patterns: literal segments must match; `:param` segments are captured -(the last one is the slug). `index: true` → `_index.md`. `fallback: true` → -everything else. The engine derives content classification, the `buildPath`, the -list ordering, the stats counters and the admin editor from this. - -Field `type`s: `string · text · markdown · slug · date · number · bool · select -(options) · image · list · list(of:{…})`. - -## Plugins - -Everything beyond the generic engine is a **plugin**. A site opts into plugins via -`plugins: [...]` in its config; core's plugin manager loads them and wires them in. -The engine stays lean; openbureau = core + its plugins + its config. - -A plugin is a module at `api/src/plugins//index.js` exporting a manifest: - -```js -export default { - name: 'dialog', - routes: [ // mounted under /api - { path: '/forums', app: forums, public: true }, // skips requireAuth - { path: '/comments', app: comments }, // auth required - { path: '/admin/forums', app: adminForums, admin: true }, // admins only - ], - onBoot: async (ctx) => {}, // run once at startup (e.g. syncLibrary) - onPublish: async (ctx, { path }) => {}, // hook after a publish build - onPreview: async (ctx, { path }) => {}, // hook after a preview build - migrations: ['001_forums.sql'], // applied to Postgres/Supabase on boot - stats: async (ctx) => ({ forums, threads, comments }), // merged into /api/stats - admin: { /* UI panels the admin SPA mounts for this plugin */ }, -}; -``` - -The **plugin manager** (core): reads `config.plugins`, imports each module, mounts -its routes in the right auth tier (public → before `requireAuth`, the rest after, -`admin: true` behind `requireAdmin`), registers its boot/publish/preview/stats -hooks, and runs its migrations. The admin SPA loads UI from -`admin/src/plugins/` for enabled plugins only. - -First plugin: **`dialog`** — the comment/forum subsystem (Supabase -`forums/threads/comments`, library↔thread sync, forum admin UI). openbureau enables -it; kgva doesn't, so none of its routes, hooks, tables or stats load there. - -## Auth providers - -Auth is a config seam (`auth: 'supabase' | 'local'`, default `supabase`) so a site -can run **without a database**. The engine already verifies tokens locally — HS256 -against `JWT_SECRET`, no GoTrue roundtrip ([api/src/auth.js]) — so a provider only -has to *issue* tokens and *store users*. - -- **`supabase`** (today): GoTrue logs the user in and is the user store; the engine - verifies the JWT. Needed when there are multiple editors or the `dialog` plugin - (its forums/threads/comments live in Postgres). This is the heavy path. -- **`local`**: file-based users (bcrypt hashes) + self-signed HS256 JWTs of the - **same claim shape** (`sub`, `email`, `app_metadata.role`, `exp`), so `requireAuth` - and roles are unchanged. No GoTrue, no Postgres, no PostgREST. A dialog-less site - (e.g. kgva, single admin) then runs as just **Node + Hugo + nginx** (~80 MB vs the - ~1–2 GB Supabase suite). Trade-off: you own password hashing; you lose GoTrue's - password-reset / rate-limiting — fine for a single-admin CMS. - -What a pluginless core needs from Supabase is *only* the auth half anyway: content -is Markdown on disk, uploads are local FS (`static/images`, via sharp), and -PostgREST/tables are a `dialog`-plugin need. `local` drops that half too. - -## Architecture - -- `api/` — Node engine (Hono): `auth` (Supabase JWT), `files` (gray-matter - CRUD), `hugo` (coalesced build / preview / publish + git), `routes/*`, - `ratelimit`, `config` (this loader). -- `admin/` — React/Vite SPA; renders list + editor from the config schema. -- `examples/` — `openbureau.config.js`, `kgva.config.js` (the two consumers). - -## Status / migration stages - -This repo currently holds the **engine as extracted** plus the config API and the -two target configs. Generalisation is staged because the api and admin share a -data contract and openbureau is in production — they must change together and be -tested on the live Supabase/Hugo stack, not flipped blind. - -- [x] Repo, engine, config loader, example configs (openbureau + kgva) -- [x] `files.js` classify / order / buildPath driven by `collections` - (`api/src/collections.js` + tests; behaviour 1:1 vs openbureau.config.js) -- [x] `stats.js` counters driven by `collections` (`statKey` / `draftStatKey`), - dialog counts gated by `hasPlugin('dialog')` -- [~] **plugin manager** built + unit-tested (`api/src/plugin-manager.js`, - `api/test/plugin-manager.test.js`): loads `config.plugins`, mounts routes by - auth tier, runs boot/publish/preview hooks, merges stats, surfaces migrations. - NOT yet wired into `index.js` (lands with stage 5, needs the live stack); - admin plugin-UI loading still TODO -- [~] extract openbureau's extras into plugins — first **`dialog`**: manifest - scaffold at `api/src/plugins/dialog/index.js` wraps the existing dialog - modules (routes by tier, `syncLibrary` on boot/publish, forum counters as - `stats`), loaded + structurally tested (`api/test/dialog-plugin.test.js`). - TODO (live-tested): wire it into `index.js`, capture the Supabase DDL into - `migrations/001_dialog.sql`, move the dialog source under `plugins/dialog/` -- [ ] `index.js` / `publish.js`: no hard-coded dialog — everything via the manager -- [ ] admin: editor + sidebar groups rendered from the config schema -- [ ] **auth provider** (`supabase` | `local`): `local` = file users + self-signed - HS256 JWTs (same claim shape), no DB — lets a dialog-less core run Supabase-free -- [ ] cut openbureau over to consume core (= core + `dialog` plugin + its config), - behaviour identical — tested on the live stack -- [ ] onboard kgva as the second consumer (`auth: 'local'`, no plugins, DB-less) diff --git a/cms/core/admin/index.html b/cms/core/admin/index.html deleted file mode 100644 index 9b62c50..0000000 --- a/cms/core/admin/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - OPENBUREAU — CMS - - -
- - - diff --git a/cms/core/admin/package-lock.json b/cms/core/admin/package-lock.json deleted file mode 100644 index a9fa815..0000000 --- a/cms/core/admin/package-lock.json +++ /dev/null @@ -1,2060 +0,0 @@ -{ - "name": "openbureau-cms-admin", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "openbureau-cms-admin", - "version": "0.1.0", - "dependencies": { - "@supabase/supabase-js": "^2.47.10", - "@toast-ui/editor": "^3.2.2", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.0.7" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", - "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", - "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", - "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", - "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", - "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", - "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", - "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", - "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", - "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", - "cpu": [ - "arm" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", - "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", - "cpu": [ - "arm" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", - "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", - "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", - "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", - "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", - "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", - "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", - "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", - "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", - "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", - "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", - "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", - "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", - "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", - "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", - "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", - "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", - "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@supabase/auth-js": { - "version": "2.106.2", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.106.2.tgz", - "integrity": "sha512-VcAjUErkHkhC5Jaf+g/G1qbkQrFh8edaCdHa7pxJmHUjkWKjT7UnYCtPA89XV0N0GIYRkEqJZw5V62CtOxTmBQ==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/functions-js": { - "version": "2.106.2", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.106.2.tgz", - "integrity": "sha512-oRnr0QrL8H+zTO1YyQ1QjiHZU/957jvubbxSJTUm2XLAgzoGGV9Tahfyd+uvLsBLRVmXLtpU3oyCjdQIvkGMOA==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/phoenix": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.2.tgz", - "integrity": "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==", - "license": "MIT" - }, - "node_modules/@supabase/postgrest-js": { - "version": "2.106.2", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.106.2.tgz", - "integrity": "sha512-tDOzyPgp9pIRMR2x6C9+uDSJrnXSzxLtt3d7nC+Lrsy3jnJDHYfdQC/xcRyhJE/TOBJ0heSqRKR3UmejDjZxsw==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/realtime-js": { - "version": "2.106.2", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.106.2.tgz", - "integrity": "sha512-LdRGT7DNhyZkPjubUv5bSdAZ0jSEX8wTHvx7htj7+K59TOZRvz4TuQK7tL2RWxyIZVeFMRluL04SzWS61rKnUA==", - "license": "MIT", - "dependencies": { - "@supabase/phoenix": "^0.4.2", - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/storage-js": { - "version": "2.106.2", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.106.2.tgz", - "integrity": "sha512-xgKCSYuev1YarV+iVqr+zlfgSyremnJtn8T0NCT8L4XmMv1CLtESc0Q6kNp8+mKWdX/8ND0nzm7OMKx08kwNAw==", - "license": "MIT", - "dependencies": { - "iceberg-js": "^0.8.1", - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/supabase-js": { - "version": "2.106.2", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.106.2.tgz", - "integrity": "sha512-2/RZ/1fmJx/MRSEDG2Xk8+J4JVk5clM9V0uSI6kUTrcS32KA89DtqI5RUOC9r6mzY3WBC9qexLjssIHjbLyVJA==", - "license": "MIT", - "dependencies": { - "@supabase/auth-js": "2.106.2", - "@supabase/functions-js": "2.106.2", - "@supabase/postgrest-js": "2.106.2", - "@supabase/realtime-js": "2.106.2", - "@supabase/storage-js": "2.106.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@toast-ui/editor": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@toast-ui/editor/-/editor-3.2.2.tgz", - "integrity": "sha512-ASX7LFjN2ZYQJrwmkUajPs7DRr9FsM1+RQ82CfTO0Y5ZXorBk1VZS4C2Dpxinx9kl55V4F8/A2h2QF4QMDtRbA==", - "license": "MIT", - "dependencies": { - "dompurify": "^2.3.3", - "prosemirror-commands": "^1.1.9", - "prosemirror-history": "^1.1.3", - "prosemirror-inputrules": "^1.1.3", - "prosemirror-keymap": "^1.1.4", - "prosemirror-model": "^1.14.1", - "prosemirror-state": "^1.3.4", - "prosemirror-view": "^1.18.7" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.33", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", - "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001793", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", - "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dompurify": { - "version": "2.5.9", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.9.tgz", - "integrity": "sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA==", - "license": "(MPL-2.0 OR Apache-2.0)" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.364", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz", - "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==", - "dev": true, - "license": "ISC" - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/iceberg-js": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", - "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.46", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", - "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/orderedmap": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", - "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prosemirror-commands": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", - "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.10.2" - } - }, - "node_modules/prosemirror-history": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", - "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.2.2", - "prosemirror-transform": "^1.0.0", - "prosemirror-view": "^1.31.0", - "rope-sequence": "^1.3.0" - } - }, - "node_modules/prosemirror-inputrules": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", - "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" - } - }, - "node_modules/prosemirror-keymap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", - "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "w3c-keyname": "^2.2.0" - } - }, - "node_modules/prosemirror-model": { - "version": "1.25.7", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.7.tgz", - "integrity": "sha512-A79aN8QEFUwI6cax8Yq4Rpcx1TJZ3Kagn+ii7qLo4/V8H3mMiHrhFyhTyHHvpSnOgMPpWiDGSwM3etwrxE50ug==", - "license": "MIT", - "dependencies": { - "orderedmap": "^2.0.0" - } - }, - "node_modules/prosemirror-state": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", - "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-transform": "^1.0.0", - "prosemirror-view": "^1.27.0" - } - }, - "node_modules/prosemirror-transform": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz", - "integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.21.0" - } - }, - "node_modules/prosemirror-view": { - "version": "1.41.8", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz", - "integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.20.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.1.0" - } - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", - "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.4", - "@rollup/rollup-android-arm64": "4.60.4", - "@rollup/rollup-darwin-arm64": "4.60.4", - "@rollup/rollup-darwin-x64": "4.60.4", - "@rollup/rollup-freebsd-arm64": "4.60.4", - "@rollup/rollup-freebsd-x64": "4.60.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", - "@rollup/rollup-linux-arm-musleabihf": "4.60.4", - "@rollup/rollup-linux-arm64-gnu": "4.60.4", - "@rollup/rollup-linux-arm64-musl": "4.60.4", - "@rollup/rollup-linux-loong64-gnu": "4.60.4", - "@rollup/rollup-linux-loong64-musl": "4.60.4", - "@rollup/rollup-linux-ppc64-gnu": "4.60.4", - "@rollup/rollup-linux-ppc64-musl": "4.60.4", - "@rollup/rollup-linux-riscv64-gnu": "4.60.4", - "@rollup/rollup-linux-riscv64-musl": "4.60.4", - "@rollup/rollup-linux-s390x-gnu": "4.60.4", - "@rollup/rollup-linux-x64-gnu": "4.60.4", - "@rollup/rollup-linux-x64-musl": "4.60.4", - "@rollup/rollup-openbsd-x64": "4.60.4", - "@rollup/rollup-openharmony-arm64": "4.60.4", - "@rollup/rollup-win32-arm64-msvc": "4.60.4", - "@rollup/rollup-win32-ia32-msvc": "4.60.4", - "@rollup/rollup-win32-x64-gnu": "4.60.4", - "@rollup/rollup-win32-x64-msvc": "4.60.4", - "fsevents": "~2.3.2" - } - }, - "node_modules/rope-sequence": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", - "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", - "license": "MIT" - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", - "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/w3c-keyname": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "license": "MIT" - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - } - } -} diff --git a/cms/core/admin/package.json b/cms/core/admin/package.json deleted file mode 100644 index 99f1939..0000000 --- a/cms/core/admin/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "openbureau-cms-admin", - "version": "0.1.0", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "@supabase/supabase-js": "^2.47.10", - "@toast-ui/editor": "^3.2.2", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.0.7" - } -} diff --git a/cms/core/admin/src/App.jsx b/cms/core/admin/src/App.jsx deleted file mode 100644 index b43b042..0000000 --- a/cms/core/admin/src/App.jsx +++ /dev/null @@ -1,732 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import ToastEditor from '@toast-ui/editor'; -import '@toast-ui/editor/dist/toastui-editor.css'; -import { supabase } from './supabase.js'; -import { api } from './api.js'; -import { - Field, blankForm, formFromFrontmatter, frontmatterFromForm, targetPath, - isShort, isBody, hexOf, DEFAULT_PALETTE, -} from './fields.jsx'; - -export default function App() { - const [session, setSession] = useState(null); - const [loading, setLoading] = useState(true); - useEffect(() => { - supabase.auth.getSession().then(({ data }) => { setSession(data.session); setLoading(false); }); - const { data: sub } = supabase.auth.onAuthStateChange((_e, s) => setSession(s)); - return () => sub.subscription.unsubscribe(); - }, []); - if (loading) return
; - if (!session) return ; - return ; -} - -function Login() { - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [err, setErr] = useState(null); - async function submit(e) { - e.preventDefault(); setErr(null); - const { error } = await supabase.auth.signInWithPassword({ email, password }); - if (error) setErr(error.message); - } - return ( -
-
-
OPENBUREAU
-
Redaktion
- setEmail(e.target.value)} autoFocus /> - setPassword(e.target.value)} /> - - {err &&

{err}

} -
-
- ); -} - -function Dashboard({ email }) { - const [entries, setEntries] = useState([]); - const [collections, setCollections] = useState(null); - const [plugins, setPlugins] = useState([]); - const [site, setSite] = useState(''); - const [current, setCurrent] = useState(null); - const [query, setQuery] = useState(''); - const [view, setView] = useState('content'); - const [me, setMe] = useState(null); - const [msg, setMsg] = useState(null); - - async function refresh() { - try { setEntries(await api.list()); } - catch (e) { setMsg({ type: 'err', text: e.message }); } - } - useEffect(() => { - refresh(); - api.getMe().then(setMe).catch(() => {}); - api.schema().then((sc) => { - setCollections((sc.collections || []).slice().sort((a, b) => (a.order ?? 99) - (b.order ?? 99))); - setPlugins(sc.plugins || []); setSite(sc.site || ''); - }).catch(() => setCollections([])); - }, []); - useEffect(() => { if (!msg) return; const t = setTimeout(() => setMsg(null), 4000); return () => clearTimeout(t); }, [msg]); - - const cols = collections || []; - const hasDialog = plugins.includes('dialog'); - const collectionOf = (kind) => cols.find((c) => c.kind === kind) || cols.find((c) => c.fallback) || cols[0] || null; - - async function open(entry) { - const col = collectionOf(entry.kind); - try { - const r = await api.read(entry.path); - const bodyField = (col?.fields || []).find(isBody); - setCurrent({ - isNew: false, kind: entry.kind, path: entry.path, - ...formFromFrontmatter(r.frontmatter || {}, col || { fields: [] }), - ...(bodyField ? { [bodyField.name]: r.body || '' } : {}), - }); - } catch (err) { setMsg({ type: 'err', text: err.message }); } - } - function openNew() { - const col = cols[0]; - if (col) setCurrent({ isNew: true, kind: col.kind, path: '', ...blankForm(col) }); - } - - const q = query.trim().toLowerCase(); - const filtered = q ? entries.filter((e) => (e.title || '').toLowerCase().includes(q) || (e.section || '').includes(q)) : entries; - const fallbackKind = (cols.find((c) => c.fallback) || cols[0] || {}).kind; - const groups = {}; - for (const c of cols) groups[c.kind] = []; - for (const e of filtered) { const k = groups[e.kind] ? e.kind : fallbackKind; if (groups[k]) groups[k].push(e); } - - return ( -
-
- {(site || 'cms').toUpperCase()} - Redaktion - - - {email} - -
- -
- {view === 'overview' ? ( - - ) : view === 'profile' ? ( - - ) : view === 'users' ? ( - - ) : view === 'forums' ? ( - - ) : view === 'moderation' ? ( - - ) : view === 'system' ? ( - - ) : ( - <> - -
- {current - ? { setCurrent(loaded); refresh(); }} onMsg={setMsg} /> - :

Wähle links einen Eintrag — oder leg einen neuen an.

} -
- - )} -
- - {msg &&
setMsg(null)}>{msg.text}
} -
- ); -} - -function Editor({ initial, collections, onSaved, onMsg }) { - const [f, setF] = useState(initial); - const [previewUrl, setPreviewUrl] = useState(null); - const [showPreview, setShowPreview] = useState(false); - const [pw, setPw] = useState(44); - const [busy, setBusy] = useState(false); - const editorRef = useRef(null); - const dragging = useRef(false); - - const cols = collections || []; - const collection = cols.find((c) => c.kind === f.kind) || cols[0] || { fields: [] }; - const fields = collection.fields || []; - const titleField = fields.find((x) => x.name === 'title') || fields.find((x) => x.type === 'string' && x.required) || null; - const bodyField = fields.find(isBody); - const draftField = fields.find((x) => x.type === 'bool' && x.name === 'draft'); - const shortFields = fields.filter((x) => x !== titleField && !isBody(x) && isShort(x)); - const longFields = fields.filter((x) => x !== titleField && !isBody(x) && !isShort(x)); - const isDraft = draftField ? !!f[draftField.name] : false; - const hasSlugField = fields.some((x) => x.name === 'slug'); - - const setField = (name, val) => setF((p) => ({ ...p, [name]: val })); - const upload = async (file) => (await api.upload(file)).url; - function changeKind(kind) { - const col = cols.find((c) => c.kind === kind); - if (col) setF({ isNew: true, kind, path: '', ...blankForm(col) }); - } - - // Ziehbarer Trenner Editor ↔ Vorschau. - useEffect(() => { - function move(e) { - if (!dragging.current || !editorRef.current) return; - const r = editorRef.current.getBoundingClientRect(); - setPw(Math.min(70, Math.max(25, ((r.right - e.clientX) / r.width) * 100))); - } - function up() { dragging.current = false; document.body.style.cursor = ''; document.body.style.userSelect = ''; } - window.addEventListener('mousemove', move); - window.addEventListener('mouseup', up); - return () => { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); }; - }, []); - function startDrag(e) { dragging.current = true; document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; e.preventDefault(); } - - async function save(overrides = {}) { - const data = { ...f, ...overrides }; - const path = data.isNew ? targetPath(data, collection) : data.path; - if (!path) { onMsg({ type: 'err', text: 'Bitte einen Slug (und ggf. Pflichtsegmente wie Rubrik) angeben.' }); return null; } - if (titleField && !((data[titleField.name] || '').trim())) { onMsg({ type: 'err', text: 'Titel fehlt.' }); return null; } - setBusy(true); - try { - const fm = frontmatterFromForm(data, collection); - const body = bodyField ? (data[bodyField.name] || '') : ''; - await api.save(path, fm, body); - const r = await api.read(path); - const loaded = { - isNew: false, kind: data.kind, path, - ...formFromFrontmatter(r.frontmatter || {}, collection), - ...(bodyField ? { [bodyField.name]: r.body || '' } : {}), - }; - onSaved(loaded); setF(loaded); - return path; - } catch (e) { onMsg({ type: 'err', text: e.message }); return null; } - finally { setBusy(false); } - } - async function preview() { - const path = await save(); if (!path) return; - setShowPreview(true); setBusy(true); - try { const res = await api.preview(path); setPreviewUrl(`${res.url}?t=${Date.now()}`); } - catch (e) { onMsg({ type: 'err', text: e.message }); } - finally { setBusy(false); } - } - async function publish() { - if (!confirm('Live publizieren?')) return; - const path = await save(draftField ? { [draftField.name]: false } : {}); - if (!path) return; - setBusy(true); - try { const res = await api.publish(path); onMsg({ type: 'ok', text: `Live: ${res.url}` }); } - catch (e) { onMsg({ type: 'err', text: e.message }); } - finally { setBusy(false); } - } - - return ( -
-
-
-
{f.isNew ? 'Neuer Eintrag' : f.path}
- - {draftField && (isDraft ? Entwurf : Veröffentlicht)} - - - - -
- -
- {f.isNew && ( -
- - {!hasSlugField && ( - - )} -
- )} - - {titleField && ( - - )} - - {shortFields.length > 0 && ( -
- {shortFields.map((fld) => )} -
- )} - - {longFields.map((fld) => )} - - {bodyField && ( -
- setField(bodyField.name, body)} onUpload={upload} /> -
- )} -
-
- - {showPreview &&
} - {showPreview && ( -
- {previewUrl - ?