@bettercms-ai/kit 2.0.0-next.1 → 2.0.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/lossy.d.ts +4 -0
- package/dist/adapters/lossy.js +8 -2
- package/dist/adapters/lossy.js.map +1 -1
- package/dist/adapters/schema.js +28 -0
- package/dist/adapters/schema.js.map +1 -1
- package/dist/controls/link.d.ts +11 -4
- package/dist/controls/link.js +8 -6
- package/dist/controls/link.js.map +1 -1
- package/dist/controls/rich-text/article-shell.js +1 -0
- package/dist/controls/rich-text/article-shell.js.map +1 -1
- package/dist/controls/rich-text/blocks.d.ts +6 -0
- package/dist/controls/rich-text/blocks.js +188 -0
- package/dist/controls/rich-text/blocks.js.map +1 -1
- package/dist/controls/rich-text/button.d.ts +2 -2
- package/dist/controls/rich-text/button.js +10 -14
- package/dist/controls/rich-text/button.js.map +1 -1
- package/dist/controls/rich-text/catalog.d.ts +2 -5
- package/dist/controls/rich-text/catalog.js +105 -7
- package/dist/controls/rich-text/catalog.js.map +1 -1
- package/dist/controls/rich-text/codec.d.ts +13 -1
- package/dist/controls/rich-text/codec.js +7 -0
- package/dist/controls/rich-text/codec.js.map +1 -1
- package/dist/controls/rich-text/control.js +1 -0
- package/dist/controls/rich-text/control.js.map +1 -1
- package/dist/controls/rich-text/dom.d.ts +79 -0
- package/dist/controls/rich-text/dom.js +159 -7
- package/dist/controls/rich-text/dom.js.map +1 -1
- package/dist/controls/rich-text/editor.d.ts +4 -2
- package/dist/controls/rich-text/editor.js +109 -18
- package/dist/controls/rich-text/editor.js.map +1 -1
- package/dist/controls/rich-text/link-form.d.ts +10 -0
- package/dist/controls/rich-text/link-form.js +39 -5
- package/dist/controls/rich-text/link-form.js.map +1 -1
- package/dist/controls/rich-text/ops.js +6 -0
- package/dist/controls/rich-text/ops.js.map +1 -1
- package/dist/controls/rich-text/related.d.ts +12 -4
- package/dist/controls/rich-text/related.js +22 -11
- package/dist/controls/rich-text/related.js.map +1 -1
- package/dist/controls/rich-text/toc.d.ts +3 -2
- package/dist/controls/rich-text/toc.js +1 -2
- package/dist/controls/rich-text/toc.js.map +1 -1
- package/dist/core/core.d.ts +1 -0
- package/dist/core/core.js +2 -0
- package/dist/core/core.js.map +1 -1
- package/dist/core/facets.d.ts +8 -0
- package/dist/core/facets.js +8 -0
- package/dist/core/facets.js.map +1 -1
- package/dist/library/kit-catalog.json +4 -2
- package/dist/schema/types.d.ts +8 -0
- package/dist/schema/types.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/ui/overlay.d.ts +4 -1
- package/dist/ui/overlay.js +2 -1
- package/dist/ui/overlay.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"article-shell.js","names":[],"sources":["../../../src/controls/rich-text/article-shell.tsx"],"sourcesContent":["/**\n * RICH TEXT · the article-block shell (V2 article-blocks.tsx ArticleBlock), shared by every article block: the one-open\n * store (one panel or popover open at a time), the card the block draws in (a focusable group: Enter opens it, Escape\n * closes it then leaves, ⌘Z / ⌘⇧Z, ⌘D, ⌥↑/↓, Backspace), the edit / remove chip on its top edge, the empty state, the\n * schema form a panel or popover holds, and the floating properties panel (D54).\n *\n * surface \"panel\" ArticlePanelBlock: ArticleCard + useArticleOpen + ArticlePanel(ArticleForm) (pull quote, gallery,\n * related posts)\n * surface \"popover\" ArticlePopoverBlock: ArticleCard + useArticleOpen + a Popover of ArticleForm (embed, table of\n * contents, author bio)\n * surface \"inline\" ArticleCard without onOpen: the block edits in the text itself (key takeaways)\n */\nimport * as React from \"react\";\nimport { IconArrowsMove, IconCopy, IconDimensions, IconDots, IconLayoutSidebar, IconLayoutSidebarRight, IconSettings, IconTrash, IconX } from \"@tabler/icons-react\";\nimport { cn } from \"../../lib/cn\";\nimport { eventKey } from \"../../lib/key-event\";\nimport { Button, IconButton } from \"../../ui/button\";\nimport { Menu, MenuContent, MenuItem, MenuSeparator, MenuTrigger, Popover, PopoverAnchor, PopoverContent, Tooltip } from \"../../ui/overlay\";\nimport { FloatingPanel, type FloatingPanelHandle } from \"../../ui/floating-panel\";\nimport { SchemaForm } from \"../../engine/schema-form\";\nimport type { TypeSchema } from \"../../schema/types\";\nimport type { IslandEl } from \"./dom\";\nimport type { IslandCtx } from \"./blocks\";\n\n/* ── one article panel open at a time (V2 useOpenArticle) ── */\nexport interface OpenArticle {\n id: string;\n /** the field the panel focuses when it opens (a click on the preview's `data-field`), null = the first */\n focusKey: string | null;\n /** bumps on every open, so re-opening the open block refocuses */\n nonce: number;\n}\nlet openArticle: OpenArticle | null = null;\nconst articleSubs = new Set<() => void>();\nconst setOpenArticle = (next: OpenArticle | null) => {\n openArticle = next;\n articleSubs.forEach((f) => f());\n};\n/** The article block open now, if any. */\nexport const currentOpenArticle = () => openArticle;\nexport function useOpenArticle() {\n return React.useSyncExternalStore(\n (cb) => (articleSubs.add(cb), () => void articleSubs.delete(cb)),\n () => openArticle,\n () => openArticle,\n );\n}\n\n/**\n * One block's place in the one-open store: `open(field?)` opens it (closing any other), `close()` closes it if it is\n * the open one. It closes when the block unmounts, and a fresh insert (`autoFocus`) opens its form.\n */\nexport function useArticleOpen({ ro, autoFocus }: { ro: boolean; autoFocus: boolean }) {\n const uid = React.useId();\n const open = useOpenArticle();\n const editing = open?.id === uid;\n const openPanel = React.useCallback((k?: string | null) => {\n if (!ro) setOpenArticle({ id: uid, focusKey: k ?? null, nonce: (openArticle?.nonce ?? 0) + 1 });\n }, [uid, ro]);\n const close = React.useCallback(() => {\n if (openArticle?.id === uid) setOpenArticle(null);\n }, [uid]);\n React.useEffect(() => () => {\n if (openArticle?.id === uid) setOpenArticle(null);\n }, [uid]);\n React.useEffect(() => {\n // a fresh insert opens its form\n if (autoFocus && !ro) openPanel();\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n return { open, editing, openPanel, close };\n}\n\nconst controlOf = (el: HTMLElement) =>\n Array.from(el.querySelectorAll<HTMLElement>(\"textarea, input:not([type=hidden]), [contenteditable=true], [role=combobox]\")).find((n) => n.getClientRects().length) ??\n Array.from(el.querySelectorAll<HTMLElement>(\":scope > :not(:first-child) button\")).find((n) => n.getClientRects().length);\n/** Focus the form row `key` names (`data-field-key`), else the first row. */\nexport function focusField(root: HTMLElement | null, key: string | null) {\n if (!root) return;\n const rows = Array.from(root.querySelectorAll<HTMLElement>(\"[data-field-key]\"));\n const row = (key && rows.find((r) => r.getAttribute(\"data-field-key\") === key)) || rows[0];\n if (!row) return;\n row.scrollIntoView({ block: \"nearest\" });\n controlOf(row)?.focus({ preventScroll: true });\n}\n\n/**\n * V2 ArticleBlock onKeyDown, on the card itself (its own fields handle their own keys). With `onOpen` (a panel or\n * popover block) Enter / Space open it and Escape closes it first; without (an inline block) Escape leaves the block.\n */\nexport function articleCardKeyDown({\n card, ctx, el, ro, editing = false, onOpen, onClose, focusCard,\n}: {\n card: React.RefObject<HTMLElement | null>;\n ctx: IslandCtx;\n el: IslandEl;\n ro: boolean;\n editing?: boolean;\n onOpen?: () => void;\n onClose?: () => void;\n focusCard: () => void;\n}) {\n return (e: React.KeyboardEvent) => {\n if (e.defaultPrevented || e.target !== card.current) return;\n const mod = e.metaKey || e.ctrlKey;\n const k = eventKey(e);\n if (mod && k === \"z\") {\n e.preventDefault();\n e.stopPropagation();\n if (e.shiftKey) ctx.redo?.();\n else ctx.undo?.();\n return;\n }\n if (onOpen && (e.key === \"Enter\" || e.key === \" \")) {\n e.preventDefault();\n onOpen();\n return;\n }\n if (e.key === \"Escape\") {\n e.preventDefault();\n e.stopPropagation();\n if (editing) onClose?.();\n else ctx.exit(el);\n return;\n }\n if (ro) return;\n if (mod && k === \"d\" && ctx.duplicate) {\n e.preventDefault();\n e.stopPropagation();\n ctx.duplicate(el);\n } else if (e.altKey && (e.key === \"ArrowUp\" || e.key === \"ArrowDown\") && ctx.move) {\n e.preventDefault();\n ctx.move(el, e.key === \"ArrowUp\" ? -1 : 1);\n focusCard();\n } else if (e.key === \"Backspace\" || e.key === \"Delete\") {\n e.preventDefault();\n ctx.remove(el);\n }\n };\n}\n\n/** The chip on the block's top edge (so it never covers the block's own text): Edit (with `onEdit`) and Remove. */\nexport function ArticleActions({\n label, ctx, el, editing = false, onEdit, tooltips = false,\n}: {\n label: string;\n ctx: IslandCtx;\n el: IslandEl;\n editing?: boolean;\n /** toggles the block's panel or popover; absent on an inline block */\n onEdit?: () => void;\n /** each button also says what it does in a tooltip */\n tooltips?: boolean;\n}) {\n if (ctx.readOnly) return null;\n const tip = (content: string, button: React.ReactElement) => (tooltips ? <Tooltip content={content}>{button}</Tooltip> : button);\n return (\n <div\n className={cn(\n \"absolute -top-3.5 right-2 z-[2] flex items-center gap-0.5 rounded-kit-md border border-kit-line bg-kit-surface-raised p-0.5 opacity-0 shadow-kit-sm transition-opacity\",\n \"group-hover/art:opacity-100 group-focus-within/art:opacity-100 pointer-coarse:opacity-100\",\n editing && \"opacity-100\",\n )}\n >\n {onEdit\n ? tip(\n `Edit ${label.toLowerCase()}`,\n <IconButton size=\"xs\" label={`Edit ${label.toLowerCase()}`} onClick={onEdit}>\n <IconSettings stroke={1.5} />\n </IconButton>,\n )\n : null}\n {tip(\n `Remove ${label.toLowerCase()}`,\n <IconButton size=\"xs\" label={`Remove ${label.toLowerCase()}`} onClick={() => ctx.remove(el)} className=\"hover:text-kit-danger-fg\">\n <IconTrash stroke={1.5} />\n </IconButton>,\n )}\n </div>\n );\n}\n\n/**\n * The card an article block draws in. With `onOpen` (panel / popover) it is pressable: a click opens it on the field\n * clicked (`data-field`), and it shows the open ring while `editing`. Without, it is the inline block's quiet group.\n */\nexport const ArticleCard = React.forwardRef<\n HTMLDivElement,\n { label: string; kind: string; ro: boolean; editing?: boolean; onOpen?: (field: string | null) => void; onKeyDown: (e: React.KeyboardEvent) => void; children: React.ReactNode }\n>(function ArticleCard({ label, kind, ro, editing = false, onOpen, onKeyDown, children }, ref) {\n if (!onOpen)\n return (\n <div\n ref={ref}\n role=\"group\"\n tabIndex={0}\n aria-label={label}\n aria-roledescription=\"block\"\n data-rt-isolate=\"\"\n data-article-block={kind}\n onKeyDown={onKeyDown}\n className=\"group/art relative rounded-kit-lg outline-none transition-shadow duration-150 focus-visible:shadow-[0_0_0_1px_var(--kit-line-strong)]\"\n >\n {children}\n </div>\n );\n return (\n <div\n ref={ref}\n role=\"group\"\n tabIndex={0}\n aria-label={`${label}${editing ? \", editing\" : \"\"}`}\n aria-roledescription=\"block\"\n aria-haspopup=\"dialog\"\n aria-expanded={editing}\n data-rt-isolate=\"\"\n data-article-block={kind}\n onKeyDown={onKeyDown}\n onClick={(e) => {\n if (ro || !e.currentTarget.contains(e.target as Node)) return;\n const t = e.target as HTMLElement;\n if (t.closest(\"button,a,[role=menuitem]\")) return;\n onOpen(t.closest<HTMLElement>(\"[data-field]\")?.getAttribute(\"data-field\") ?? null);\n }}\n className={cn(\n \"group/art relative rounded-kit-lg outline-none transition-shadow duration-150\",\n !ro && \"cursor-pointer\",\n \"focus-visible:shadow-[0_0_0_1px_var(--kit-line-strong)]\",\n editing ? \"shadow-[0_0_0_2px_var(--kit-accent)] focus-visible:shadow-[0_0_0_2px_var(--kit-accent)]\" : !ro && \"hover:shadow-[0_0_0_1px_var(--kit-line)]\",\n )}\n >\n {children}\n </div>\n );\n});\n\n/** The block's settings, built from its schema (V2 ArticleForm: the kit SchemaForm, flush to the panel's top). */\nexport function ArticleForm({\n schema, label, value, onChange, ro,\n}: {\n schema: TypeSchema;\n label: string;\n value: Record<string, unknown>;\n onChange: (v: Record<string, unknown>) => void;\n ro: boolean;\n}) {\n return (\n <div data-article-form=\"\" className=\"min-w-0\">\n <SchemaForm schema={schema} value={value} onChange={onChange} variant=\"panel\" scroll={false} readOnly={ro} rootLabel={label} env={{ docId: `article:${schema.name}`, docTitle: label }} />\n <style>{`[data-article-form] [data-kit-form] section:first-of-type { border-top-width: 0; margin-top: 0; padding-top: 0; }`}</style>\n </div>\n );\n}\n\n/** V2 article-blocks.tsx Empty: a block with nothing to show yet. */\nexport const ArticleEmpty = ({ icon, title, action, onAction, ro }: { icon: React.ReactNode; title: string; action: string; onAction: () => void; ro: boolean }) => (\n <div className=\"flex items-center gap-3 rounded-kit-lg border border-dashed border-kit-line-strong bg-kit-subtle px-4 py-3.5\">\n <span className=\"flex size-8 shrink-0 items-center justify-center rounded-kit-md border border-kit-line bg-kit-control-bg text-kit-fg-2 [&_svg]:size-4\">{icon}</span>\n <span className=\"min-w-0 flex-1 text-kit-sm font-medium text-kit-fg\">{title}</span>\n {ro ? null : (\n <Button size=\"sm\" onClick={onAction}>\n {action}\n </Button>\n )}\n </div>\n);\n\n/* ── Floating properties panel (D54 primitive; V2 ArticlePanel) ─────────────── */\nexport function ArticlePanel({\n label, anchor, nonce, focusKey, onClose, focusBack, ctx, el, children,\n}: {\n label: string;\n anchor: HTMLElement | null;\n nonce: number;\n focusKey: string | null;\n onClose: () => void;\n focusBack: () => void;\n ctx: IslandCtx;\n el: IslandEl;\n children: React.ReactNode;\n}) {\n const panel = React.useRef<FloatingPanelHandle>(null);\n const titleId = React.useId();\n const container = anchor?.closest<HTMLElement>(\"[data-expanded-editor]\") ?? null;\n React.useEffect(() => {\n // the panel mounts (and, inside the expanded editor, docks) over a few frames; focus lands on the field asked for,\n // and is put back while it is lost (never taken from somewhere the person went)\n const timers: number[] = [];\n let landed = false;\n for (const ms of [40, 90, 160, 260, 420, 640]) {\n timers.push(\n window.setTimeout(() => {\n const root = panel.current?.el() ?? null;\n if (!root?.querySelector(\"[data-field-key]\")) return;\n const a = document.activeElement as HTMLElement | null;\n const lost = !a || a === document.body || a.hasAttribute(\"data-expanded-editor\") || a.hasAttribute(\"data-article-block\");\n if (root.contains(a)) return void (landed = true);\n if (!landed || lost) focusField(root, focusKey);\n if (root.contains(document.activeElement)) landed = true;\n }, ms),\n );\n }\n return () => timers.forEach((t) => window.clearTimeout(t));\n }, [nonce]); // eslint-disable-line react-hooks/exhaustive-deps\n React.useEffect(() => panel.current?.announce(`${label} properties.`), [label]);\n const header = (\n <>\n <span id={titleId} className=\"min-w-0 flex-1 truncate text-kit-sm font-medium text-kit-fg\">{label}</span>\n <Menu>\n <MenuTrigger\n render={\n <IconButton size=\"sm\" label={`${label} actions`}>\n <IconDots stroke={1.5} />\n </IconButton>\n }\n />\n <MenuContent align=\"end\" className=\"w-52\">\n {ctx.readOnly || !ctx.duplicate ? null : (\n <>\n <MenuItem icon={<IconCopy stroke={1.5} />} onClick={() => ctx.duplicate?.(el)}>Duplicate</MenuItem>\n <MenuSeparator />\n </>\n )}\n <MenuItem icon={<IconArrowsMove stroke={1.5} />} onClick={() => panel.current?.keyboard(\"move\")}>Move panel</MenuItem>\n <MenuItem icon={<IconDimensions stroke={1.5} />} onClick={() => panel.current?.keyboard(\"resize\")}>Resize panel</MenuItem>\n <MenuItem icon={<IconLayoutSidebar stroke={1.5} />} onClick={() => panel.current?.dock(\"left\")}>Dock left</MenuItem>\n <MenuItem icon={<IconLayoutSidebarRight stroke={1.5} />} onClick={() => panel.current?.dock(\"right\")}>Dock right</MenuItem>\n {ctx.readOnly ? null : (\n <>\n <MenuSeparator />\n <MenuItem danger icon={<IconTrash stroke={1.5} />} onClick={() => ctx.remove(el)}>Remove</MenuItem>\n </>\n )}\n </MenuContent>\n </Menu>\n <IconButton size=\"sm\" label=\"Close properties\" tooltip=\"Close\" onClick={() => (onClose(), focusBack())}>\n <IconX stroke={1.5} />\n </IconButton>\n </>\n );\n return (\n <FloatingPanel\n ref={panel}\n id=\"article-properties\"\n labelledBy={titleId}\n anchor={anchor}\n container={container}\n header={header}\n onClose={onClose}\n focusBack={focusBack}\n onPanelKeyDown={(e) => {\n if ((e.metaKey || e.ctrlKey) && !e.altKey && eventKey(e) === \"z\" && !e.defaultPrevented) {\n e.preventDefault();\n if (e.shiftKey) ctx.redo?.();\n else ctx.undo?.();\n }\n }}\n sheetFooter={\n <Button size=\"sm\" variant=\"primary\" onClick={() => (onClose(), focusBack())}>\n Done\n </Button>\n }\n >\n <div className=\"pb-3 pt-1\">{children}</div>\n </FloatingPanel>\n );\n}\n\n/* ── The two article block surfaces ────────────────────────────────── */\n/**\n * A panel article block (V2 ArticleBlock surface \"panel\": Gallery, Related posts): the shell's card + chip, its\n * settings in the floating properties panel, one undo step per field edit.\n */\nexport function ArticlePanelBlock<M extends object>({\n label, kind, schema, m, set, el, ctx, autoFocus, openOnInsert = true, toForm, fromForm, children,\n}: {\n label: string;\n kind: string;\n schema: TypeSchema;\n m: M;\n set: (fn: (m: M) => M, label?: string) => void;\n el: IslandEl;\n ctx: IslandCtx;\n autoFocus: boolean;\n /** V2 article-blocks.tsx:127-133: a fresh insert opens the panel, except Related posts, which focuses the card */\n openOnInsert?: boolean;\n toForm: (m: M) => Record<string, unknown>;\n fromForm: (v: Record<string, unknown>, prev: M) => M;\n children: (open: (field?: string | null) => void, ro: boolean) => React.ReactNode;\n}) {\n const card = React.useRef<HTMLDivElement | null>(null);\n const [cardEl, setCardEl] = React.useState<HTMLDivElement | null>(null);\n const ro = !!ctx.readOnly;\n const { open, editing, openPanel, close } = useArticleOpen({ ro, autoFocus: autoFocus && openOnInsert });\n // a stable ref: a new callback each render would pass null for a moment and remount the panel\n const cardRef = React.useCallback((n: HTMLDivElement | null) => {\n card.current = n;\n if (n) setCardEl(n);\n }, []);\n const focusCard = React.useCallback(() => requestAnimationFrame(() => card.current?.focus({ preventScroll: true })), []);\n React.useEffect(() => {\n if (autoFocus && !openOnInsert && !ro) focusCard();\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n const modelRef = React.useRef(m);\n modelRef.current = m;\n\n /** a form edit joins the body's one undo history (D54); typing coalesces per field */\n const commit = (v: Record<string, unknown>) => {\n const prev = modelRef.current;\n const before = toForm(prev);\n const field = Object.keys(v).find((k) => JSON.stringify(v[k]) !== JSON.stringify(before[k])) ?? \"\";\n set(() => fromForm(v, prev), `Edit ${label.toLowerCase()} ${field}`);\n };\n\n return (\n <>\n <ArticleCard\n ref={cardRef}\n label={label}\n kind={kind}\n ro={ro}\n editing={editing}\n onOpen={openPanel}\n onKeyDown={articleCardKeyDown({ card, ctx, el, ro, editing, onOpen: openPanel, onClose: close, focusCard })}\n >\n {children(openPanel, ro)}\n <ArticleActions label={label} ctx={ctx} el={el} editing={editing} onEdit={() => (editing ? close() : openPanel())} tooltips />\n </ArticleCard>\n {editing ? (\n <ArticlePanel label={label} anchor={cardEl} nonce={open!.nonce} focusKey={open!.focusKey} onClose={close} focusBack={focusCard} ctx={ctx} el={el}>\n <ArticleForm schema={schema} label={label} value={toForm(m)} onChange={commit} ro={ro} />\n </ArticlePanel>\n ) : null}\n </>\n );\n}\n\n/**\n * A popover article block (V2 ArticleBlock surface \"popover\": TOC, Embed, Author bio): the shell's card + chip, its\n * settings in a popover from the card's top-right corner, one undo step per field edit.\n */\nexport function ArticlePopoverBlock<M extends object>({\n label, kind, schema, m, set, el, ctx, autoFocus, openOnInsert = true, toForm, fromForm, children,\n}: {\n label: string;\n kind: string;\n schema: TypeSchema;\n m: M;\n set: (fn: (m: M) => M, label?: string) => void;\n el: IslandEl;\n ctx: IslandCtx;\n autoFocus: boolean;\n /** V2 article-blocks.tsx:127-133: a fresh insert opens the form, except TOC and Related, which focus the card */\n openOnInsert?: boolean;\n toForm: (m: M) => Record<string, unknown>;\n fromForm: (v: Record<string, unknown>, prev: M) => M;\n /** the preview; `open(field)` opens the popover on that field (the empty state's action) */\n children: (open: (field?: string | null) => void, ro: boolean) => React.ReactNode;\n}) {\n const card = React.useRef<HTMLDivElement | null>(null);\n const form = React.useRef<HTMLDivElement | null>(null);\n const ro = !!ctx.readOnly;\n const { open, editing, openPanel: openPopover, close } = useArticleOpen({ ro, autoFocus: autoFocus && openOnInsert });\n const focusCard = React.useCallback(() => requestAnimationFrame(() => card.current?.focus({ preventScroll: true })), []);\n React.useEffect(() => {\n if (autoFocus && !openOnInsert && !ro) focusCard();\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n const modelRef = React.useRef(m);\n modelRef.current = m;\n const nonce = editing ? open!.nonce : 0;\n React.useEffect(() => {\n if (!nonce) return;\n const id = requestAnimationFrame(() => focusField(form.current, currentOpenArticle()?.focusKey ?? null));\n return () => cancelAnimationFrame(id);\n }, [nonce]);\n\n /** a form edit joins the body's one undo history (D54); typing coalesces per field */\n const commit = (v: Record<string, unknown>) => {\n const prev = modelRef.current;\n const before = toForm(prev);\n const field = Object.keys(v).find((k) => JSON.stringify(v[k]) !== JSON.stringify(before[k])) ?? \"\";\n set(() => fromForm(v, prev), `Edit ${label.toLowerCase()} ${field}`);\n };\n\n return (\n <Popover\n open={editing}\n onOpenChange={(o, d) => {\n if (o) return;\n close();\n // V2 onCloseAutoFocus: back on the block, unless the press that closed it went somewhere else\n if (d?.reason !== \"outside-press\") focusCard();\n }}\n >\n <ArticleCard\n ref={card}\n label={label}\n kind={kind}\n ro={ro}\n editing={editing}\n onOpen={openPopover}\n onKeyDown={articleCardKeyDown({ card, ctx, el, ro, editing, onOpen: openPopover, onClose: close, focusCard })}\n >\n {children(openPopover, ro)}\n <ArticleActions label={label} ctx={ctx} el={el} editing={editing} onEdit={() => (editing ? close() : openPopover())} tooltips />\n {/* the popover opens from the block's top-right corner, over the block, wherever the block sits */}\n <PopoverAnchor render={<span aria-hidden className=\"pointer-events-none absolute right-2 top-2 size-px\" />} />\n </ArticleCard>\n {editing ? (\n <PopoverContent\n align=\"end\"\n side=\"bottom\"\n className=\"w-[340px] p-0\"\n initialFocus={false}\n finalFocus={false}\n onKeyDown={(e) => {\n if ((e.metaKey || e.ctrlKey) && !e.altKey && eventKey(e) === \"z\" && !e.defaultPrevented) {\n e.preventDefault();\n if (e.shiftKey) ctx.redo?.();\n else ctx.undo?.();\n }\n }}\n >\n <div className=\"flex h-10 items-center gap-2 border-b border-kit-line-subtle pl-4 pr-1.5\">\n <span className=\"flex-1 truncate text-kit-sm font-medium text-kit-fg\">{label}</span>\n <IconButton size=\"sm\" label=\"Close\" onClick={() => (close(), focusCard())}>\n <IconX stroke={1.5} />\n </IconButton>\n </div>\n <div ref={form} className=\"max-h-[min(480px,70vh)] overflow-y-auto pb-2\">\n <ArticleForm schema={schema} label={label} value={toForm(m)} onChange={commit} ro={ro} />\n </div>\n </PopoverContent>\n ) : null}\n </Popover>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"article-shell.js","names":[],"sources":["../../../src/controls/rich-text/article-shell.tsx"],"sourcesContent":["/**\n * RICH TEXT · the article-block shell (V2 article-blocks.tsx ArticleBlock), shared by every article block: the one-open\n * store (one panel or popover open at a time), the card the block draws in (a focusable group: Enter opens it, Escape\n * closes it then leaves, ⌘Z / ⌘⇧Z, ⌘D, ⌥↑/↓, Backspace), the edit / remove chip on its top edge, the empty state, the\n * schema form a panel or popover holds, and the floating properties panel (D54).\n *\n * surface \"panel\" ArticlePanelBlock: ArticleCard + useArticleOpen + ArticlePanel(ArticleForm) (pull quote, gallery,\n * related posts)\n * surface \"popover\" ArticlePopoverBlock: ArticleCard + useArticleOpen + a Popover of ArticleForm (embed, table of\n * contents, author bio)\n * surface \"inline\" ArticleCard without onOpen: the block edits in the text itself (key takeaways)\n */\nimport * as React from \"react\";\nimport { IconArrowsMove, IconCopy, IconDimensions, IconDots, IconLayoutSidebar, IconLayoutSidebarRight, IconSettings, IconTrash, IconX } from \"@tabler/icons-react\";\nimport { cn } from \"../../lib/cn\";\nimport { eventKey } from \"../../lib/key-event\";\nimport { Button, IconButton } from \"../../ui/button\";\nimport { Menu, MenuContent, MenuItem, MenuSeparator, MenuTrigger, Popover, PopoverAnchor, PopoverContent, Tooltip } from \"../../ui/overlay\";\nimport { FloatingPanel, type FloatingPanelHandle } from \"../../ui/floating-panel\";\nimport { SchemaForm } from \"../../engine/schema-form\";\nimport type { TypeSchema } from \"../../schema/types\";\nimport type { IslandEl } from \"./dom\";\nimport type { IslandCtx } from \"./blocks\";\n\n/* ── one article panel open at a time (V2 useOpenArticle) ── */\nexport interface OpenArticle {\n id: string;\n /** the field the panel focuses when it opens (a click on the preview's `data-field`), null = the first */\n focusKey: string | null;\n /** bumps on every open, so re-opening the open block refocuses */\n nonce: number;\n}\nlet openArticle: OpenArticle | null = null;\nconst articleSubs = new Set<() => void>();\nconst setOpenArticle = (next: OpenArticle | null) => {\n openArticle = next;\n articleSubs.forEach((f) => f());\n};\n/** The article block open now, if any. */\nexport const currentOpenArticle = () => openArticle;\nexport function useOpenArticle() {\n return React.useSyncExternalStore(\n (cb) => (articleSubs.add(cb), () => void articleSubs.delete(cb)),\n () => openArticle,\n () => openArticle,\n );\n}\n\n/**\n * One block's place in the one-open store: `open(field?)` opens it (closing any other), `close()` closes it if it is\n * the open one. It closes when the block unmounts, and a fresh insert (`autoFocus`) opens its form.\n */\nexport function useArticleOpen({ ro, autoFocus }: { ro: boolean; autoFocus: boolean }) {\n const uid = React.useId();\n const open = useOpenArticle();\n const editing = open?.id === uid;\n const openPanel = React.useCallback((k?: string | null) => {\n if (!ro) setOpenArticle({ id: uid, focusKey: k ?? null, nonce: (openArticle?.nonce ?? 0) + 1 });\n }, [uid, ro]);\n const close = React.useCallback(() => {\n if (openArticle?.id === uid) setOpenArticle(null);\n }, [uid]);\n React.useEffect(() => () => {\n if (openArticle?.id === uid) setOpenArticle(null);\n }, [uid]);\n React.useEffect(() => {\n // a fresh insert opens its form\n if (autoFocus && !ro) openPanel();\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n return { open, editing, openPanel, close };\n}\n\nconst controlOf = (el: HTMLElement) =>\n Array.from(el.querySelectorAll<HTMLElement>(\"textarea, input:not([type=hidden]), [contenteditable=true], [role=combobox]\")).find((n) => n.getClientRects().length) ??\n Array.from(el.querySelectorAll<HTMLElement>(\":scope > :not(:first-child) button\")).find((n) => n.getClientRects().length);\n/** Focus the form row `key` names (`data-field-key`), else the first row. */\nexport function focusField(root: HTMLElement | null, key: string | null) {\n if (!root) return;\n const rows = Array.from(root.querySelectorAll<HTMLElement>(\"[data-field-key]\"));\n const row = (key && rows.find((r) => r.getAttribute(\"data-field-key\") === key)) || rows[0];\n if (!row) return;\n row.scrollIntoView({ block: \"nearest\" });\n controlOf(row)?.focus({ preventScroll: true });\n}\n\n/**\n * V2 ArticleBlock onKeyDown, on the card itself (its own fields handle their own keys). With `onOpen` (a panel or\n * popover block) Enter / Space open it and Escape closes it first; without (an inline block) Escape leaves the block.\n */\nexport function articleCardKeyDown({\n card, ctx, el, ro, editing = false, onOpen, onClose, focusCard,\n}: {\n card: React.RefObject<HTMLElement | null>;\n ctx: IslandCtx;\n el: IslandEl;\n ro: boolean;\n editing?: boolean;\n onOpen?: () => void;\n onClose?: () => void;\n focusCard: () => void;\n}) {\n return (e: React.KeyboardEvent) => {\n if (e.defaultPrevented || e.target !== card.current) return;\n const mod = e.metaKey || e.ctrlKey;\n const k = eventKey(e);\n if (mod && k === \"z\") {\n e.preventDefault();\n e.stopPropagation();\n if (e.shiftKey) ctx.redo?.();\n else ctx.undo?.();\n return;\n }\n if (onOpen && (e.key === \"Enter\" || e.key === \" \")) {\n e.preventDefault();\n onOpen();\n return;\n }\n if (e.key === \"Escape\") {\n e.preventDefault();\n e.stopPropagation();\n if (editing) onClose?.();\n else ctx.exit(el);\n return;\n }\n if (ro) return;\n if (mod && k === \"d\" && ctx.duplicate) {\n e.preventDefault();\n e.stopPropagation();\n ctx.duplicate(el);\n } else if (e.altKey && (e.key === \"ArrowUp\" || e.key === \"ArrowDown\") && ctx.move) {\n e.preventDefault();\n ctx.move(el, e.key === \"ArrowUp\" ? -1 : 1);\n focusCard();\n } else if (e.key === \"Backspace\" || e.key === \"Delete\") {\n e.preventDefault();\n ctx.remove(el);\n }\n };\n}\n\n/** The chip on the block's top edge (so it never covers the block's own text): Edit (with `onEdit`) and Remove. */\nexport function ArticleActions({\n label, ctx, el, editing = false, onEdit, tooltips = false,\n}: {\n label: string;\n ctx: IslandCtx;\n el: IslandEl;\n editing?: boolean;\n /** toggles the block's panel or popover; absent on an inline block */\n onEdit?: () => void;\n /** each button also says what it does in a tooltip */\n tooltips?: boolean;\n}) {\n if (ctx.readOnly) return null;\n const tip = (content: string, button: React.ReactElement) => (tooltips ? <Tooltip content={content}>{button}</Tooltip> : button);\n return (\n <div\n className={cn(\n \"absolute -top-3.5 right-2 z-[2] flex items-center gap-0.5 rounded-kit-md border border-kit-line bg-kit-surface-raised p-0.5 opacity-0 shadow-kit-sm transition-opacity\",\n \"group-hover/art:opacity-100 group-focus-within/art:opacity-100 pointer-coarse:opacity-100\",\n editing && \"opacity-100\",\n )}\n >\n {onEdit\n ? tip(\n `Edit ${label.toLowerCase()}`,\n <IconButton size=\"xs\" label={`Edit ${label.toLowerCase()}`} onClick={onEdit}>\n <IconSettings stroke={1.5} />\n </IconButton>,\n )\n : null}\n {tip(\n `Remove ${label.toLowerCase()}`,\n <IconButton size=\"xs\" label={`Remove ${label.toLowerCase()}`} onClick={() => ctx.remove(el)} className=\"hover:text-kit-danger-fg\">\n <IconTrash stroke={1.5} />\n </IconButton>,\n )}\n </div>\n );\n}\n\n/**\n * The card an article block draws in. With `onOpen` (panel / popover) it is pressable: a click opens it on the field\n * clicked (`data-field`), and it shows the open ring while `editing`. Without, it is the inline block's quiet group.\n */\nexport const ArticleCard = React.forwardRef<\n HTMLDivElement,\n { label: string; kind: string; ro: boolean; editing?: boolean; onOpen?: (field: string | null) => void; onKeyDown: (e: React.KeyboardEvent) => void; children: React.ReactNode }\n>(function ArticleCard({ label, kind, ro, editing = false, onOpen, onKeyDown, children }, ref) {\n if (!onOpen)\n return (\n <div\n ref={ref}\n role=\"group\"\n tabIndex={0}\n aria-label={label}\n aria-roledescription=\"block\"\n data-rt-isolate=\"\"\n data-article-block={kind}\n onKeyDown={onKeyDown}\n className=\"group/art relative rounded-kit-lg outline-none transition-shadow duration-150 focus-visible:shadow-[0_0_0_1px_var(--kit-line-strong)]\"\n >\n {children}\n </div>\n );\n return (\n <div\n ref={ref}\n role=\"group\"\n tabIndex={0}\n aria-label={`${label}${editing ? \", editing\" : \"\"}`}\n aria-roledescription=\"block\"\n aria-haspopup=\"dialog\"\n aria-expanded={editing}\n data-rt-isolate=\"\"\n data-article-block={kind}\n onKeyDown={onKeyDown}\n onClick={(e) => {\n if (ro || !e.currentTarget.contains(e.target as Node)) return;\n const t = e.target as HTMLElement;\n if (t.closest(\"button,a,[role=menuitem]\")) return;\n onOpen(t.closest<HTMLElement>(\"[data-field]\")?.getAttribute(\"data-field\") ?? null);\n }}\n className={cn(\n \"group/art relative rounded-kit-lg outline-none transition-shadow duration-150\",\n !ro && \"cursor-pointer\",\n \"focus-visible:shadow-[0_0_0_1px_var(--kit-line-strong)]\",\n editing ? \"shadow-[0_0_0_2px_var(--kit-accent)] focus-visible:shadow-[0_0_0_2px_var(--kit-accent)]\" : !ro && \"hover:shadow-[0_0_0_1px_var(--kit-line)]\",\n )}\n >\n {children}\n </div>\n );\n});\n\n/** The block's settings, built from its schema (V2 ArticleForm: the kit SchemaForm, flush to the panel's top). */\nexport function ArticleForm({\n schema, label, value, onChange, ro,\n}: {\n schema: TypeSchema;\n label: string;\n value: Record<string, unknown>;\n onChange: (v: Record<string, unknown>) => void;\n ro: boolean;\n}) {\n return (\n <div data-article-form=\"\" className=\"min-w-0\">\n <SchemaForm schema={schema} value={value} onChange={onChange} variant=\"panel\" scroll={false} readOnly={ro} rootLabel={label} env={{ docId: `article:${schema.name}`, docTitle: label }} />\n <style>{`[data-article-form] [data-kit-form] section:first-of-type { border-top-width: 0; margin-top: 0; padding-top: 0; }`}</style>\n </div>\n );\n}\n\n/** V2 article-blocks.tsx Empty: a block with nothing to show yet. */\nexport const ArticleEmpty = ({ icon, title, action, onAction, ro }: { icon: React.ReactNode; title: string; action: string; onAction: () => void; ro: boolean }) => (\n <div className=\"flex items-center gap-3 rounded-kit-lg border border-dashed border-kit-line-strong bg-kit-subtle px-4 py-3.5\">\n <span className=\"flex size-8 shrink-0 items-center justify-center rounded-kit-md border border-kit-line bg-kit-control-bg text-kit-fg-2 [&_svg]:size-4\">{icon}</span>\n <span className=\"min-w-0 flex-1 text-kit-sm font-medium text-kit-fg\">{title}</span>\n {ro ? null : (\n <Button size=\"sm\" onClick={onAction}>\n {action}\n </Button>\n )}\n </div>\n);\n\n/* ── Floating properties panel (D54 primitive; V2 ArticlePanel) ─────────────── */\nexport function ArticlePanel({\n label, anchor, nonce, focusKey, onClose, focusBack, ctx, el, children,\n}: {\n label: string;\n anchor: HTMLElement | null;\n nonce: number;\n focusKey: string | null;\n onClose: () => void;\n focusBack: () => void;\n ctx: IslandCtx;\n el: IslandEl;\n children: React.ReactNode;\n}) {\n const panel = React.useRef<FloatingPanelHandle>(null);\n const titleId = React.useId();\n const container = anchor?.closest<HTMLElement>(\"[data-expanded-editor]\") ?? null;\n React.useEffect(() => {\n // the panel mounts (and, inside the expanded editor, docks) over a few frames; focus lands on the field asked for,\n // and is put back while it is lost (never taken from somewhere the person went)\n const timers: number[] = [];\n let landed = false;\n for (const ms of [40, 90, 160, 260, 420, 640]) {\n timers.push(\n window.setTimeout(() => {\n const root = panel.current?.el() ?? null;\n if (!root?.querySelector(\"[data-field-key]\")) return;\n const a = document.activeElement as HTMLElement | null;\n const lost = !a || a === document.body || a.hasAttribute(\"data-expanded-editor\") || a.hasAttribute(\"data-article-block\");\n if (root.contains(a)) return void (landed = true);\n if (!landed || lost) focusField(root, focusKey);\n if (root.contains(document.activeElement)) landed = true;\n }, ms),\n );\n }\n return () => timers.forEach((t) => window.clearTimeout(t));\n }, [nonce]); // eslint-disable-line react-hooks/exhaustive-deps\n React.useEffect(() => panel.current?.announce(`${label} properties.`), [label]);\n const header = (\n <>\n <span id={titleId} className=\"min-w-0 flex-1 truncate text-kit-sm font-medium text-kit-fg\">{label}</span>\n <Menu>\n <MenuTrigger\n render={\n <IconButton size=\"sm\" label={`${label} actions`}>\n <IconDots stroke={1.5} />\n </IconButton>\n }\n />\n <MenuContent align=\"end\" className=\"w-52\">\n {ctx.readOnly || !ctx.duplicate ? null : (\n <>\n <MenuItem icon={<IconCopy stroke={1.5} />} onClick={() => ctx.duplicate?.(el)}>Duplicate</MenuItem>\n <MenuSeparator />\n </>\n )}\n <MenuItem icon={<IconArrowsMove stroke={1.5} />} onClick={() => panel.current?.keyboard(\"move\")}>Move panel</MenuItem>\n <MenuItem icon={<IconDimensions stroke={1.5} />} onClick={() => panel.current?.keyboard(\"resize\")}>Resize panel</MenuItem>\n <MenuItem icon={<IconLayoutSidebar stroke={1.5} />} onClick={() => panel.current?.dock(\"left\")}>Dock left</MenuItem>\n <MenuItem icon={<IconLayoutSidebarRight stroke={1.5} />} onClick={() => panel.current?.dock(\"right\")}>Dock right</MenuItem>\n {ctx.readOnly ? null : (\n <>\n <MenuSeparator />\n <MenuItem danger icon={<IconTrash stroke={1.5} />} onClick={() => ctx.remove(el)}>Remove</MenuItem>\n </>\n )}\n </MenuContent>\n </Menu>\n <IconButton size=\"sm\" label=\"Close properties\" tooltip=\"Close\" onClick={() => (onClose(), focusBack())}>\n <IconX stroke={1.5} />\n </IconButton>\n </>\n );\n return (\n <FloatingPanel\n ref={panel}\n id=\"article-properties\"\n labelledBy={titleId}\n anchor={anchor}\n container={container}\n header={header}\n onClose={onClose}\n focusBack={focusBack}\n onPanelKeyDown={(e) => {\n if ((e.metaKey || e.ctrlKey) && !e.altKey && eventKey(e) === \"z\" && !e.defaultPrevented) {\n e.preventDefault();\n if (e.shiftKey) ctx.redo?.();\n else ctx.undo?.();\n }\n }}\n sheetFooter={\n <Button size=\"sm\" variant=\"primary\" onClick={() => (onClose(), focusBack())}>\n Done\n </Button>\n }\n >\n <div className=\"pb-3 pt-1\">{children}</div>\n </FloatingPanel>\n );\n}\n\n/* ── The two article block surfaces ────────────────────────────────── */\n/**\n * A panel article block (V2 ArticleBlock surface \"panel\": Gallery, Related posts): the shell's card + chip, its\n * settings in the floating properties panel, one undo step per field edit.\n */\nexport function ArticlePanelBlock<M extends object>({\n label, kind, schema, m, set, el, ctx, autoFocus, openOnInsert = true, toForm, fromForm, children,\n}: {\n label: string;\n kind: string;\n schema: TypeSchema;\n m: M;\n set: (fn: (m: M) => M, label?: string) => void;\n el: IslandEl;\n ctx: IslandCtx;\n autoFocus: boolean;\n /** V2 article-blocks.tsx:127-133: a fresh insert opens the panel, except Related posts, which focuses the card */\n openOnInsert?: boolean;\n toForm: (m: M) => Record<string, unknown>;\n fromForm: (v: Record<string, unknown>, prev: M) => M;\n children: (open: (field?: string | null) => void, ro: boolean) => React.ReactNode;\n}) {\n const card = React.useRef<HTMLDivElement | null>(null);\n const [cardEl, setCardEl] = React.useState<HTMLDivElement | null>(null);\n const ro = !!ctx.readOnly;\n const { open, editing, openPanel, close } = useArticleOpen({ ro, autoFocus: autoFocus && openOnInsert });\n // a stable ref: a new callback each render would pass null for a moment and remount the panel\n const cardRef = React.useCallback((n: HTMLDivElement | null) => {\n card.current = n;\n if (n) setCardEl(n);\n }, []);\n const focusCard = React.useCallback(() => requestAnimationFrame(() => card.current?.focus({ preventScroll: true })), []);\n React.useEffect(() => {\n if (autoFocus && !openOnInsert && !ro) focusCard();\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n const modelRef = React.useRef(m);\n modelRef.current = m;\n\n /** a form edit joins the body's one undo history (D54); typing coalesces per field */\n const commit = (v: Record<string, unknown>) => {\n const prev = modelRef.current;\n const before = toForm(prev);\n const field = Object.keys(v).find((k) => JSON.stringify(v[k]) !== JSON.stringify(before[k])) ?? \"\";\n set(() => fromForm(v, prev), `Edit ${label.toLowerCase()} ${field}`);\n };\n\n return (\n <>\n <ArticleCard\n ref={cardRef}\n label={label}\n kind={kind}\n ro={ro}\n editing={editing}\n onOpen={openPanel}\n onKeyDown={articleCardKeyDown({ card, ctx, el, ro, editing, onOpen: openPanel, onClose: close, focusCard })}\n >\n {children(openPanel, ro)}\n <ArticleActions label={label} ctx={ctx} el={el} editing={editing} onEdit={() => (editing ? close() : openPanel())} tooltips />\n </ArticleCard>\n {editing ? (\n <ArticlePanel label={label} anchor={cardEl} nonce={open!.nonce} focusKey={open!.focusKey} onClose={close} focusBack={focusCard} ctx={ctx} el={el}>\n <ArticleForm schema={schema} label={label} value={toForm(m)} onChange={commit} ro={ro} />\n </ArticlePanel>\n ) : null}\n </>\n );\n}\n\n/**\n * A popover article block (V2 ArticleBlock surface \"popover\": TOC, Embed, Author bio): the shell's card + chip, its\n * settings in a popover from the card's top-right corner, one undo step per field edit.\n */\nexport function ArticlePopoverBlock<M extends object>({\n label, kind, schema, m, set, el, ctx, autoFocus, openOnInsert = true, toForm, fromForm, children,\n}: {\n label: string;\n kind: string;\n schema: TypeSchema;\n m: M;\n set: (fn: (m: M) => M, label?: string) => void;\n el: IslandEl;\n ctx: IslandCtx;\n autoFocus: boolean;\n /** V2 article-blocks.tsx:127-133: a fresh insert opens the form, except TOC and Related, which focus the card */\n openOnInsert?: boolean;\n toForm: (m: M) => Record<string, unknown>;\n fromForm: (v: Record<string, unknown>, prev: M) => M;\n /** the preview; `open(field)` opens the popover on that field (the empty state's action) */\n children: (open: (field?: string | null) => void, ro: boolean) => React.ReactNode;\n}) {\n const card = React.useRef<HTMLDivElement | null>(null);\n const form = React.useRef<HTMLDivElement | null>(null);\n const ro = !!ctx.readOnly;\n const { open, editing, openPanel: openPopover, close } = useArticleOpen({ ro, autoFocus: autoFocus && openOnInsert });\n const focusCard = React.useCallback(() => requestAnimationFrame(() => card.current?.focus({ preventScroll: true })), []);\n React.useEffect(() => {\n if (autoFocus && !openOnInsert && !ro) focusCard();\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n const modelRef = React.useRef(m);\n modelRef.current = m;\n const nonce = editing ? open!.nonce : 0;\n React.useEffect(() => {\n if (!nonce) return;\n const id = requestAnimationFrame(() => focusField(form.current, currentOpenArticle()?.focusKey ?? null));\n return () => cancelAnimationFrame(id);\n }, [nonce]);\n\n /** a form edit joins the body's one undo history (D54); typing coalesces per field */\n const commit = (v: Record<string, unknown>) => {\n const prev = modelRef.current;\n const before = toForm(prev);\n const field = Object.keys(v).find((k) => JSON.stringify(v[k]) !== JSON.stringify(before[k])) ?? \"\";\n set(() => fromForm(v, prev), `Edit ${label.toLowerCase()} ${field}`);\n };\n\n return (\n <Popover\n open={editing}\n onOpenChange={(o, d) => {\n if (o) return;\n close();\n // V2 onCloseAutoFocus: back on the block, unless the press that closed it went somewhere else\n if (d?.reason !== \"outside-press\") focusCard();\n }}\n >\n <ArticleCard\n ref={card}\n label={label}\n kind={kind}\n ro={ro}\n editing={editing}\n onOpen={openPopover}\n onKeyDown={articleCardKeyDown({ card, ctx, el, ro, editing, onOpen: openPopover, onClose: close, focusCard })}\n >\n {children(openPopover, ro)}\n <ArticleActions label={label} ctx={ctx} el={el} editing={editing} onEdit={() => (editing ? close() : openPopover())} tooltips />\n {/* the popover opens from the block's top-right corner, over the block, wherever the block sits */}\n <PopoverAnchor render={<span aria-hidden className=\"pointer-events-none absolute right-2 top-2 size-px\" />} />\n </ArticleCard>\n {editing ? (\n <PopoverContent\n align=\"end\"\n side=\"bottom\"\n // the anchor is a 1px dot at the card's top-right: no arrow inset, so the popup's edge sits on it (V2 AB:259-300)\n arrowPadding={0}\n className=\"w-[340px] p-0\"\n initialFocus={false}\n finalFocus={false}\n onKeyDown={(e) => {\n if ((e.metaKey || e.ctrlKey) && !e.altKey && eventKey(e) === \"z\" && !e.defaultPrevented) {\n e.preventDefault();\n if (e.shiftKey) ctx.redo?.();\n else ctx.undo?.();\n }\n }}\n >\n <div className=\"flex h-10 items-center gap-2 border-b border-kit-line-subtle pl-4 pr-1.5\">\n <span className=\"flex-1 truncate text-kit-sm font-medium text-kit-fg\">{label}</span>\n <IconButton size=\"sm\" label=\"Close\" onClick={() => (close(), focusCard())}>\n <IconX stroke={1.5} />\n </IconButton>\n </div>\n <div ref={form} className=\"max-h-[min(480px,70vh)] overflow-y-auto pb-2\">\n <ArticleForm schema={schema} label={label} value={toForm(m)} onChange={commit} ro={ro} />\n </div>\n </PopoverContent>\n ) : null}\n </Popover>\n );\n}\n"],"mappings":"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"}
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import * as React from "react";
|
|
19
19
|
import { type IslandEl, type IslandKind, type IslandModels } from "./dom";
|
|
20
|
+
import { type OutlineStore } from "./toc";
|
|
21
|
+
import type { RichTextRead } from "./codec";
|
|
20
22
|
import type { RichTextFeature } from "./codec";
|
|
21
23
|
export interface IslandCtx {
|
|
22
24
|
readOnly?: boolean;
|
|
@@ -44,6 +46,10 @@ export interface IslandCtx {
|
|
|
44
46
|
drag?: (el: IslandEl, e: React.PointerEvent) => void;
|
|
45
47
|
/** what the field's stored shape keeps (the host codec's features): a setting it cannot store is not drawn */
|
|
46
48
|
allows?: (f: RichTextFeature) => boolean;
|
|
49
|
+
/** the body's live outline (its top-level H2/H3), which a table of contents lists */
|
|
50
|
+
outline?: OutlineStore;
|
|
51
|
+
/** where an author bio and related posts point (the field's config, from the host codec) */
|
|
52
|
+
references?: RichTextRead["references"];
|
|
47
53
|
}
|
|
48
54
|
/** `label` names the step in the editor's one undo history ("Edit alt text"). */
|
|
49
55
|
export type Setter<K extends IslandKind> = (patch: Partial<IslandModels[K]> | ((m: IslandModels[K]) => IslandModels[K]), label?: string, record?: boolean) => void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isCropRect } from "../../adapters/values.js";
|
|
1
2
|
import { cn } from "../../lib/cn.js";
|
|
2
3
|
import { useKitServices } from "../../context.js";
|
|
3
4
|
import { Button, IconButton } from "../../ui/button.js";
|
|
@@ -6,9 +7,14 @@ import { Input, Textarea } from "../../ui/input.js";
|
|
|
6
7
|
import { TableEditor } from "../table/table-editor.js";
|
|
7
8
|
import { VideoPreview } from "../media.js";
|
|
8
9
|
import { ArticleActions, ArticleCard, ArticleForm, ArticlePanel, articleCardKeyDown, useArticleOpen } from "./article-shell.js";
|
|
10
|
+
import { AuthorBlock } from "./author.js";
|
|
9
11
|
import { CALLOUT_TONES, placeCaret } from "./dom.js";
|
|
10
12
|
import { LinkForm } from "./link-form.js";
|
|
11
13
|
import { EmbedBlock } from "./embed.js";
|
|
14
|
+
import { GalleryBlock } from "./gallery.js";
|
|
15
|
+
import { TocBlock } from "./toc.js";
|
|
16
|
+
import { ColumnsBlock } from "./columns.js";
|
|
17
|
+
import { RelatedBlock } from "./related.js";
|
|
12
18
|
import { ComponentCard } from "./component-card.js";
|
|
13
19
|
import * as React from "react";
|
|
14
20
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -119,6 +125,36 @@ function Island({ el, ctx }) {
|
|
|
119
125
|
autoFocus,
|
|
120
126
|
m: model,
|
|
121
127
|
set
|
|
128
|
+
}) : kind === "gallery" ? /* @__PURE__ */ jsx(GalleryIsland, {
|
|
129
|
+
el,
|
|
130
|
+
ctx,
|
|
131
|
+
autoFocus,
|
|
132
|
+
m: model,
|
|
133
|
+
set
|
|
134
|
+
}) : kind === "toc" ? /* @__PURE__ */ jsx(TocIsland, {
|
|
135
|
+
el,
|
|
136
|
+
ctx,
|
|
137
|
+
autoFocus,
|
|
138
|
+
m: model,
|
|
139
|
+
set
|
|
140
|
+
}) : kind === "columns" ? /* @__PURE__ */ jsx(ColumnsIsland, {
|
|
141
|
+
el,
|
|
142
|
+
ctx,
|
|
143
|
+
autoFocus,
|
|
144
|
+
m: model,
|
|
145
|
+
set
|
|
146
|
+
}) : kind === "author" ? /* @__PURE__ */ jsx(AuthorIsland, {
|
|
147
|
+
el,
|
|
148
|
+
ctx,
|
|
149
|
+
autoFocus,
|
|
150
|
+
m: model,
|
|
151
|
+
set
|
|
152
|
+
}) : kind === "related" ? /* @__PURE__ */ jsx(RelatedIsland, {
|
|
153
|
+
el,
|
|
154
|
+
ctx,
|
|
155
|
+
autoFocus,
|
|
156
|
+
m: model,
|
|
157
|
+
set
|
|
122
158
|
}) : kind === "file" ? /* @__PURE__ */ jsx(FileBlock, {
|
|
123
159
|
el,
|
|
124
160
|
ctx,
|
|
@@ -160,6 +196,158 @@ function EmbedIsland({ m, set, el, ctx, autoFocus }) {
|
|
|
160
196
|
shape: ctx.allows?.("embedAspect") ?? false
|
|
161
197
|
});
|
|
162
198
|
}
|
|
199
|
+
/** The island's stored gallery (src, alt, caption per image) through gallery.tsx's block (V2's model, an image value each). */
|
|
200
|
+
/** RT9: a gallery image's framing, image-url's 0–1 focal point ⇄ the kit image value's percent */
|
|
201
|
+
var toGallery = (m) => ({
|
|
202
|
+
layout: m.layout ?? "grid",
|
|
203
|
+
images: m.images.map((x) => ({
|
|
204
|
+
_key: x._key,
|
|
205
|
+
image: x.src ? {
|
|
206
|
+
url: x.src,
|
|
207
|
+
...x.alt !== void 0 ? { alt: x.alt } : {},
|
|
208
|
+
...x.crop ? { crop: x.crop } : {},
|
|
209
|
+
...x.focal ? { focal: {
|
|
210
|
+
x: x.focal.x * 100,
|
|
211
|
+
y: x.focal.y * 100
|
|
212
|
+
} } : {}
|
|
213
|
+
} : null,
|
|
214
|
+
caption: x.caption ?? ""
|
|
215
|
+
}))
|
|
216
|
+
});
|
|
217
|
+
var cropOf = (c) => isCropRect(c) ? {
|
|
218
|
+
top: c.top,
|
|
219
|
+
right: c.right,
|
|
220
|
+
bottom: c.bottom,
|
|
221
|
+
left: c.left
|
|
222
|
+
} : void 0;
|
|
223
|
+
var focalOf = (f) => {
|
|
224
|
+
const p = f;
|
|
225
|
+
return typeof p?.x === "number" && typeof p?.y === "number" ? {
|
|
226
|
+
x: Math.round(p.x) / 100,
|
|
227
|
+
y: Math.round(p.y) / 100
|
|
228
|
+
} : void 0;
|
|
229
|
+
};
|
|
230
|
+
function GalleryIsland({ m, set, el, ctx, autoFocus }) {
|
|
231
|
+
return /* @__PURE__ */ jsx(GalleryBlock, {
|
|
232
|
+
m: toGallery(m),
|
|
233
|
+
set: (fn, label) => set((prev) => {
|
|
234
|
+
const next = fn(toGallery(prev));
|
|
235
|
+
const out = {
|
|
236
|
+
...prev,
|
|
237
|
+
images: next.images.map((x) => {
|
|
238
|
+
const crop = cropOf(x.image?.crop);
|
|
239
|
+
const focal = focalOf(x.image?.focal);
|
|
240
|
+
return {
|
|
241
|
+
_key: x._key,
|
|
242
|
+
src: x.image?.url ?? "",
|
|
243
|
+
...x.image?.alt !== void 0 ? { alt: x.image.alt } : {},
|
|
244
|
+
...x.caption ? { caption: x.caption } : {},
|
|
245
|
+
...crop ? { crop } : {},
|
|
246
|
+
...focal ? { focal } : {}
|
|
247
|
+
};
|
|
248
|
+
})
|
|
249
|
+
};
|
|
250
|
+
if (next.layout !== (prev.layout ?? "grid")) out.layout = next.layout;
|
|
251
|
+
return out;
|
|
252
|
+
}, label),
|
|
253
|
+
el,
|
|
254
|
+
ctx,
|
|
255
|
+
autoFocus
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/** The island's stored table of contents (title, depth) through toc.tsx's block, which lists the editor's live outline. */
|
|
259
|
+
function TocIsland({ m, set, el, ctx, autoFocus }) {
|
|
260
|
+
return /* @__PURE__ */ jsx(TocBlock, {
|
|
261
|
+
m: {
|
|
262
|
+
title: m.title ?? "",
|
|
263
|
+
depth: m.depth ?? "3"
|
|
264
|
+
},
|
|
265
|
+
set: (fn, label) => set((prev) => {
|
|
266
|
+
const next = fn({
|
|
267
|
+
title: prev.title ?? "",
|
|
268
|
+
depth: prev.depth ?? "3"
|
|
269
|
+
});
|
|
270
|
+
const out = { ...prev };
|
|
271
|
+
if (next.title !== (prev.title ?? "")) out.title = next.title;
|
|
272
|
+
if (next.depth !== (prev.depth ?? "3")) out.depth = next.depth;
|
|
273
|
+
return out;
|
|
274
|
+
}, label),
|
|
275
|
+
el,
|
|
276
|
+
ctx,
|
|
277
|
+
autoFocus
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
/** The island's stored columns (gap, each column's HTML) through columns.tsx's block. */
|
|
281
|
+
function ColumnsIsland({ m, set, el, ctx, autoFocus }) {
|
|
282
|
+
return /* @__PURE__ */ jsx(ColumnsBlock, {
|
|
283
|
+
m: {
|
|
284
|
+
gap: m.gap ?? "m",
|
|
285
|
+
cols: m.cols
|
|
286
|
+
},
|
|
287
|
+
set: (fn, label) => set((prev) => {
|
|
288
|
+
const next = fn({
|
|
289
|
+
gap: prev.gap ?? "m",
|
|
290
|
+
cols: prev.cols
|
|
291
|
+
});
|
|
292
|
+
const out = {
|
|
293
|
+
...prev,
|
|
294
|
+
cols: next.cols
|
|
295
|
+
};
|
|
296
|
+
if (next.gap !== (prev.gap ?? "m")) out.gap = next.gap;
|
|
297
|
+
return out;
|
|
298
|
+
}, label),
|
|
299
|
+
el,
|
|
300
|
+
ctx,
|
|
301
|
+
autoFocus
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
/** The island's stored author reference through author.tsx's block, picking from the field's authors collection. */
|
|
305
|
+
function AuthorIsland({ m, set, el, ctx, autoFocus }) {
|
|
306
|
+
return /* @__PURE__ */ jsx(AuthorBlock, {
|
|
307
|
+
m: { author: m.author ?? "" },
|
|
308
|
+
set: (fn, label) => set((prev) => {
|
|
309
|
+
const next = fn({ author: prev.author ?? "" });
|
|
310
|
+
return next.author === (prev.author ?? "") ? prev : {
|
|
311
|
+
...prev,
|
|
312
|
+
author: next.author
|
|
313
|
+
};
|
|
314
|
+
}, label),
|
|
315
|
+
el,
|
|
316
|
+
ctx,
|
|
317
|
+
autoFocus,
|
|
318
|
+
collection: ctx.references?.authors ?? ""
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/** The island's stored related posts through related.tsx's block, over the field's posts collection. */
|
|
322
|
+
var toRelated = (m) => ({
|
|
323
|
+
title: m.title ?? "",
|
|
324
|
+
mode: m.mode ?? "topic",
|
|
325
|
+
count: m.count ?? 3,
|
|
326
|
+
posts: m.posts
|
|
327
|
+
});
|
|
328
|
+
function RelatedIsland({ m, set, el, ctx, autoFocus }) {
|
|
329
|
+
return /* @__PURE__ */ jsx(RelatedBlock, {
|
|
330
|
+
m: toRelated(m),
|
|
331
|
+
set: (fn, label) => set((prev) => {
|
|
332
|
+
const was = toRelated(prev);
|
|
333
|
+
const next = fn(was);
|
|
334
|
+
const out = {
|
|
335
|
+
...prev,
|
|
336
|
+
posts: next.posts
|
|
337
|
+
};
|
|
338
|
+
if (next.title !== was.title) out.title = next.title;
|
|
339
|
+
if (next.mode !== was.mode) out.mode = next.mode;
|
|
340
|
+
if (next.count !== was.count) out.count = next.count;
|
|
341
|
+
return out;
|
|
342
|
+
}, label),
|
|
343
|
+
el,
|
|
344
|
+
ctx,
|
|
345
|
+
autoFocus,
|
|
346
|
+
collection: ctx.references?.posts ?? "",
|
|
347
|
+
excludeId: ctx.references?.entryId,
|
|
348
|
+
topic: ctx.references?.topic !== false
|
|
349
|
+
});
|
|
350
|
+
}
|
|
163
351
|
var Sep = () => /* @__PURE__ */ jsx("span", {
|
|
164
352
|
"aria-hidden": true,
|
|
165
353
|
className: "mx-0.5 h-4 w-px shrink-0 bg-kit-line"
|