Files
matsandClaude Sonnet 5.5 1af9ec3172 Add on-page editor for the go-graph-cms
Injectable React editor that is side-loaded onto the statically rendered
page and edits through the CMS HTTP API (POST /api/edit).

- Shadow-root UI over server HTML; patches morphed in, undo/redo from
  server inverses (reuses @k6n/cms-editor EditClient/morph/markdown editor)
- Sign-in via go-auth device grant as the CMS client (aud matches), token
  refresh + 401 retry; live preview swapped in on load and on tab focus
- Schema-driven property form (arrays, maps, variants, $ref, validation)
- Every string property editable where it is rendered (value-bound inline
  fields and attribute chips), markdown WYSIWYG for marked fields
- Block palette + drag & drop with ghost drop targets, insertion slots,
  sort snapping between siblings (Alt to nest)
- x-style annotations: on-page padding/margin/gap handles, Style panel,
  wrap-in-layout; x-graph label/group for titles and grouping
- Dev workflow: Vite proxy to the live site (token injected server-side)
  or an offline in-memory mock CMS; jsdom test suite

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 22:34:48 +01:00

40 lines
1.9 KiB
TypeScript

// `npm run dev:mock`: no network, no login. Serves a fixture page and the mock API.
import type { Plugin } from 'vite'
import { createMockCms } from '../mock/server'
import { injectDevEntry } from './shared'
export function cmsMockPlugin(): Plugin {
// CMS_MOCK_AUTH=1 makes the mock require the device-login flow (approve on the mock /device page) so the login UI can be developed.
const cms = createMockCms(process.env.CMS_MOCK_AUTH ? { auth: true } : {})
return {
name: 'cms-mock',
configureServer(server) {
server.middlewares.use(async (req, res, next) => {
const url = req.url ?? '/'
if (url.startsWith('/__dev/')) return void ((res.statusCode = 204), res.end())
const isApi = url.startsWith('/api/')
const isReset = url === '/__mock/reset' && req.method === 'POST'
const isMock = url === '/device' || url.startsWith('/device?') || url === '/__mock/approve'
const isPage = req.method === 'GET' && !/^\/(@|src\/|node_modules\/|mock\/|__)/.test(url) && String(req.headers.accept ?? '').includes('text/html')
if (!isApi && !isReset && !isPage && !isMock) return next()
if (isReset) {
cms.reset()
res.statusCode = 204
return void res.end()
}
const chunks: Buffer[] = []
for await (const c of req) chunks.push(c as Buffer)
const body = chunks.length ? Buffer.concat(chunks) : undefined
const resp = await cms.handle(
new Request(new URL(url, `http://${req.headers.host ?? 'localhost'}`), { method: req.method, headers: req.headers as Record<string, string>, body: body && req.method !== 'GET' ? body : undefined }),
)
res.statusCode = resp.status
resp.headers.forEach((v, k) => res.setHeader(k, v))
let text = await resp.text()
if (resp.headers.get('x-cms-page')) text = await server.transformIndexHtml(url, injectDevEntry(text))
res.end(text)
})
},
}
}