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>
This commit is contained in:
commit
1af9ec3172
67 files changed
+12322
No files matched your search
@@ -0,0 +1,48 @@
|
||||
import type { IncomingHttpHeaders, IncomingMessage } from 'node:http'
|
||||
|
||||
/** Paths Vite itself serves (HMR client, source modules, deps). */
|
||||
export const isViteOwned = (url: string) =>
|
||||
/^\/(@|src\/|node_modules\/|__|mock\/|test\/)/.test(url)
|
||||
|
||||
/** A navigation to a page we should fetch + inject, as opposed to an asset/API call. */
|
||||
export function isPageRequest(req: IncomingMessage): boolean {
|
||||
const url = req.url ?? '/'
|
||||
if (req.method !== 'GET' || isViteOwned(url)) return false
|
||||
if (/^\/(api|editor|assets|js|css|fonts|static)(\/|$)/.test(url)) return false
|
||||
if (/\.[a-z0-9]{2,5}($|\?)/i.test(url.split('?')[0])) return false // has a file extension
|
||||
return String(req.headers.accept ?? '').includes('text/html')
|
||||
}
|
||||
|
||||
export interface Auth {
|
||||
token?: string
|
||||
apiKey?: string
|
||||
cookie?: string
|
||||
}
|
||||
|
||||
const hasPreviewCookie = (cookie?: string) => /(?:^|;\s*)cms_preview=1/.test(cookie ?? '')
|
||||
|
||||
/** Headers for an upstream request: browser cookies + configured credentials + preview flag. */
|
||||
export function upstreamHeaders(
|
||||
incoming: IncomingHttpHeaders,
|
||||
auth: Auth,
|
||||
opts: { identity?: boolean } = {},
|
||||
): Record<string, string> {
|
||||
const h: Record<string, string> = {}
|
||||
if (incoming.accept) h.accept = String(incoming.accept)
|
||||
if (incoming['accept-language']) h['accept-language'] = String(incoming['accept-language'])
|
||||
if (opts.identity) h['accept-encoding'] = 'identity'
|
||||
const cookie = [incoming.cookie, auth.cookie].filter(Boolean).join('; ')
|
||||
if (cookie) h.cookie = cookie
|
||||
// The edge routes on the cookie OR this header; send both so a proxy hop can't lose it.
|
||||
if (hasPreviewCookie(cookie)) h['x-cms-preview'] = '1'
|
||||
if (auth.token) h.authorization = `Bearer ${auth.token}`
|
||||
else if (incoming.authorization) h.authorization = String(incoming.authorization)
|
||||
if (auth.apiKey) h['x-api-key'] = auth.apiKey
|
||||
return h
|
||||
}
|
||||
|
||||
/** Put the dev entry in front of </body> (or at the end). */
|
||||
export function injectDevEntry(html: string): string {
|
||||
const tag = '<script type="module" src="/src/dev.ts"></script>'
|
||||
return /<\/body>/i.test(html) ? html.replace(/<\/body>/i, `${tag}</body>`) : html + tag
|
||||
}
|
||||
Reference in new issue
Block a user