@openstage/glyph-element 0.2.0
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/THIRD-PARTY-NOTICES.md +1505 -0
- package/dist/chunks/bash-Dw1nAEst.js +171 -0
- package/dist/chunks/bash-Dw1nAEst.js.map +1 -0
- package/dist/chunks/csharp-D8ELXkGi.js +262 -0
- package/dist/chunks/csharp-D8ELXkGi.js.map +1 -0
- package/dist/chunks/css-DQEU4whB.js +142 -0
- package/dist/chunks/css-DQEU4whB.js.map +1 -0
- package/dist/chunks/fsharp-D4SqCnMZ.js +242 -0
- package/dist/chunks/fsharp-D4SqCnMZ.js.map +1 -0
- package/dist/chunks/go-BJIB22rX.js +142 -0
- package/dist/chunks/go-BJIB22rX.js.map +1 -0
- package/dist/chunks/haskell-eGGgAkgA.js +140 -0
- package/dist/chunks/haskell-eGGgAkgA.js.map +1 -0
- package/dist/chunks/highlight-core-C0K6BIzZ.js +967 -0
- package/dist/chunks/highlight-core-C0K6BIzZ.js.map +1 -0
- package/dist/chunks/java-DHPUyZsV.js +171 -0
- package/dist/chunks/java-DHPUyZsV.js.map +1 -0
- package/dist/chunks/javascript-D_CG8suQ.js +438 -0
- package/dist/chunks/javascript-D_CG8suQ.js.map +1 -0
- package/dist/chunks/json-BD6qkh12.js +38 -0
- package/dist/chunks/json-BD6qkh12.js.map +1 -0
- package/dist/chunks/kotlin-C75rnxi-.js +203 -0
- package/dist/chunks/kotlin-C75rnxi-.js.map +1 -0
- package/dist/chunks/markdown-B6LMDWi1.js +183 -0
- package/dist/chunks/markdown-B6LMDWi1.js.map +1 -0
- package/dist/chunks/ocaml-CMtC1T-S.js +54 -0
- package/dist/chunks/ocaml-CMtC1T-S.js.map +1 -0
- package/dist/chunks/prosemirror-dAj0dboi.js +7790 -0
- package/dist/chunks/prosemirror-dAj0dboi.js.map +1 -0
- package/dist/chunks/python-ny4cvzzX.js +240 -0
- package/dist/chunks/python-ny4cvzzX.js.map +1 -0
- package/dist/chunks/rolldown-runtime-BE9Pkid1.js +13 -0
- package/dist/chunks/rust-8OMNiTht.js +171 -0
- package/dist/chunks/rust-8OMNiTht.js.map +1 -0
- package/dist/chunks/scala-BB851ZJZ.js +164 -0
- package/dist/chunks/scala-BB851ZJZ.js.map +1 -0
- package/dist/chunks/sql-CoOMeU5k.js +120 -0
- package/dist/chunks/sql-CoOMeU5k.js.map +1 -0
- package/dist/chunks/tiptap-Cb4W88fy.js +8527 -0
- package/dist/chunks/tiptap-Cb4W88fy.js.map +1 -0
- package/dist/chunks/typescript-Dh7IycF2.js +523 -0
- package/dist/chunks/typescript-Dh7IycF2.js.map +1 -0
- package/dist/chunks/vendor-B82PcruV.js +1799 -0
- package/dist/chunks/vendor-B82PcruV.js.map +1 -0
- package/dist/chunks/xml-BpsgObpu.js +168 -0
- package/dist/chunks/xml-BpsgObpu.js.map +1 -0
- package/dist/codeblock-backspace.d.ts +3 -0
- package/dist/codeblock-backspace.d.ts.map +1 -0
- package/dist/document-conversion.d.ts +29 -0
- package/dist/document-conversion.d.ts.map +1 -0
- package/dist/editor-element.d.ts +93 -0
- package/dist/editor-element.d.ts.map +1 -0
- package/dist/glyph-editor-element.js +783 -0
- package/dist/glyph-editor-element.js.map +1 -0
- package/dist/header.d.ts +18 -0
- package/dist/header.d.ts.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/link-dialog.d.ts +29 -0
- package/dist/link-dialog.d.ts.map +1 -0
- package/dist/safe-href.d.ts +2 -0
- package/dist/safe-href.d.ts.map +1 -0
- package/dist/slash-menu.d.ts +22 -0
- package/dist/slash-menu.d.ts.map +1 -0
- package/dist/styles.d.ts +2 -0
- package/dist/styles.d.ts.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glyph-editor-element.js","names":["Right","Left","Left","Right","#ensuredLangs","#shadow","#theme","#applyTheme","#commands","#slashMenu","#config","#rebuild","#editor","#content","#ensureCodeLanguages","#api","#status","#header","#teardown","#surface","#emit","#createApi","#resolveShowHeader","#applyBounds","#collectCodeBlocks","#refreshCodeDecorations","#refreshingHighlight"],"sources":["../../editor-core/dist/brand.js","../../editor-core/dist/schema.js","../../editor-core/dist/migrate.js","../../editor-core/dist/api.js","../../editor-core/dist/commands.js","../../editor-core/dist/languages.js","../../editor-core/dist/nodes.js","../../renderer/dist/highlight.js","../../renderer/dist/highlight-theme.js","../src/document-conversion.ts","../src/safe-href.ts","../src/styles.ts","../src/codeblock-backspace.ts","../src/slash-menu.ts","../src/link-dialog.ts","../src/header.ts","../src/editor-element.ts","../src/index.ts"],"sourcesContent":["/**\n * Nominal (branded) primitive types — distinct at compile time, plain strings\n * at runtime. Construct with the matching `.of()` helper.\n *\n * @example\n * ```ts\n * const href = Href.of('https://example.com'); // Href, not just string\n * ```\n */\n/** Constructs an {@link Href} from a string. */\nexport const Href = {\n of: (value) => value,\n};\n/** Constructs a {@link Language} from a string. */\nexport const Language = {\n of: (value) => value,\n};\n/** Constructs a {@link CommandId} from a string. */\nexport const CommandId = {\n of: (value) => value,\n};\n//# sourceMappingURL=brand.js.map","import { Left, Right } from 'monadyssey';\n/** Current document schema version stamped onto new documents. */\nexport const SCHEMA_VERSION = 1;\n/** An empty document root (a single empty paragraph). */\nexport const emptyContent = () => ({\n type: 'doc',\n content: [{ type: 'paragraph' }],\n});\n/**\n * A blank document at the current schema version — a good initial value for a\n * new editor or \"new document\" action.\n */\nexport const createEmptyDocument = () => ({\n schemaVersion: SCHEMA_VERSION,\n content: emptyContent(),\n});\nconst isObject = (value) => typeof value === 'object' && value !== null;\n/**\n * Structural type guard for untrusted input (backend responses, `localStorage`,\n * paste payloads). Use it at trust boundaries before {@link migrate}; it checks\n * shape, not deep node validity.\n *\n * @example\n * ```ts\n * const raw: unknown = JSON.parse(localStorage.getItem('doc') ?? 'null');\n * if (isEditorDocument(raw)) editor.setContent(raw);\n * ```\n */\nexport const isEditorDocument = (value) => {\n if (!isObject(value))\n return false;\n if (typeof value.schemaVersion !== 'number')\n return false;\n const content = value.content;\n return isObject(content) && content.type === 'doc' && Array.isArray(content.content);\n};\n// Internal decode boundary: success/failure modeled as Either rather than a\n// thrown control-flow. The thrown TypeError is reconstituted only at the public\n// edge (assertEditorDocument) so the public contract is unchanged.\nconst decodeEditorDocument = (value) => isEditorDocument(value)\n ? Right.pure(value)\n : Left.pure(new TypeError('Value is not a valid EditorDocument'));\n/**\n * Like {@link isEditorDocument} but returns the value typed as\n * {@link EditorDocument}, throwing `TypeError` if it is not one.\n */\nexport const assertEditorDocument = (value) => decodeEditorDocument(value).fold((error) => {\n throw error;\n}, (document) => document);\n//# sourceMappingURL=schema.js.map","import { Left, Right } from 'monadyssey';\nimport { SCHEMA_VERSION } from './schema.js';\n// Ordered upgrade steps keyed by the version they migrate FROM. Empty today\n// (only schema v1 exists); future bumps add a step here and nothing else.\nconst steps = new Map();\nconst applyFrom = (acc) => {\n if (acc.schemaVersion >= SCHEMA_VERSION) {\n return acc.schemaVersion === SCHEMA_VERSION ? acc : { ...acc, schemaVersion: SCHEMA_VERSION };\n }\n const step = steps.get(acc.schemaVersion);\n return applyFrom(step ? step(acc) : { ...acc, schemaVersion: acc.schemaVersion + 1 });\n};\n// The only failure mode is a document newer than we support; modeled as Either\n// internally and re-thrown as RangeError at the public edge.\nconst ensureSupported = (document) => document.schemaVersion > SCHEMA_VERSION\n ? Left.pure(new RangeError(`Document schemaVersion ${document.schemaVersion} is newer than supported ${SCHEMA_VERSION}`))\n : Right.pure(document);\n/**\n * Upgrades a document to the current {@link SCHEMA_VERSION}. Call this on any\n * document loaded from storage before using it; it is the identity for\n * current-version documents.\n *\n * @param document - A structurally valid document (see {@link isEditorDocument}).\n * @returns The document at the current schema version.\n * @throws RangeError if the document's `schemaVersion` is newer than supported.\n */\nexport const migrate = (document) => ensureSupported(document).fold((error) => {\n throw error;\n}, applyFrom);\n//# sourceMappingURL=migrate.js.map","/** Default toolbar layout when `config.toolbar` is not provided. */\nexport const DEFAULT_TOOLBAR = [\n 'bold',\n 'italic',\n 'link',\n 'heading1',\n 'heading2',\n 'heading3',\n 'bulletList',\n 'orderedList',\n 'blockquote',\n 'codeBlock',\n 'undo',\n 'redo',\n 'spacer',\n 'codeLanguage',\n 'status',\n];\nconst THEME_VAR_MAP = {\n background: '--editor-background',\n text: '--editor-text',\n mutedText: '--editor-muted-text',\n border: '--editor-border',\n accent: '--editor-accent',\n selection: '--editor-selection',\n toolbarBackground: '--editor-toolbar-background',\n slashMenuBackground: '--editor-slash-menu-background',\n slashMenuHover: '--editor-slash-menu-hover',\n slashMenuText: '--editor-slash-menu-text',\n codeBackground: '--editor-code-background',\n blockQuoteBorder: '--editor-block-quote-border',\n};\n/** Maps an EditorTheme onto the CSS custom properties the element consumes. */\nexport const themeToCssVars = (theme) => Object.keys(THEME_VAR_MAP).reduce((vars, key) => {\n const value = theme[key];\n return typeof value === 'string' ? { ...vars, [THEME_VAR_MAP[key]]: value } : vars;\n}, {});\n//# sourceMappingURL=api.js.map","import { CommandId } from './brand.js';\n/**\n * Built-in slash-menu commands (headings, paragraph, lists, quote, code).\n * Assign your own array to `editor.commands` to replace or extend; each\n * command's `run` receives the {@link EditorApi}.\n *\n * @example\n * ```ts\n * import { defaultSlashCommands, CommandId } from '@openstage/glyph-core';\n * editor.commands = [\n * ...defaultSlashCommands,\n * { id: CommandId.of('clear'), label: 'Clear', run: (api) => api.setParagraph() },\n * ];\n * ```\n */\nexport const defaultSlashCommands = [\n {\n id: CommandId.of('heading-1'),\n label: 'Heading 1',\n keywords: ['h1', 'title'],\n run: (e) => e.setHeading(1),\n },\n {\n id: CommandId.of('heading-2'),\n label: 'Heading 2',\n keywords: ['h2'],\n run: (e) => e.setHeading(2),\n },\n {\n id: CommandId.of('heading-3'),\n label: 'Heading 3',\n keywords: ['h3'],\n run: (e) => e.setHeading(3),\n },\n {\n id: CommandId.of('paragraph'),\n label: 'Paragraph',\n keywords: ['text', 'body'],\n run: (e) => e.setParagraph(),\n },\n {\n id: CommandId.of('bullet-list'),\n label: 'Bullet list',\n keywords: ['ul', 'unordered'],\n run: (e) => e.toggleBulletList(),\n },\n {\n id: CommandId.of('numbered-list'),\n label: 'Numbered list',\n keywords: ['ol', 'ordered'],\n run: (e) => e.toggleOrderedList(),\n },\n {\n id: CommandId.of('quote'),\n label: 'Quote',\n keywords: ['blockquote'],\n run: (e) => e.toggleBlockquote(),\n },\n {\n id: CommandId.of('code-block'),\n label: 'Code block',\n keywords: ['pre', 'code'],\n run: (e) => e.toggleCodeBlock(),\n },\n];\nconst matches = (q) => (c) => c.label.toLowerCase().includes(q) ||\n (c.keywords ?? []).some((k) => k.toLowerCase().includes(q));\n/**\n * Curried, case-insensitive filter over command label and keywords. An empty\n * query returns a copy of all commands.\n *\n * @example `filterSlashCommands('head')(editor.commands)`\n */\nexport const filterSlashCommands = (query) => (commands) => {\n const q = query.trim().toLowerCase();\n return q ? commands.filter(matches(q)) : [...commands];\n};\n//# sourceMappingURL=commands.js.map","export const supportedCodeLanguages = [\n { id: 'bash', label: 'Bash' },\n { id: 'csharp', label: 'C#' },\n { id: 'css', label: 'CSS' },\n { id: 'fsharp', label: 'F#' },\n { id: 'go', label: 'Go' },\n { id: 'haskell', label: 'Haskell' },\n { id: 'java', label: 'Java' },\n { id: 'javascript', label: 'JavaScript' },\n { id: 'json', label: 'JSON' },\n { id: 'kotlin', label: 'Kotlin' },\n { id: 'markdown', label: 'Markdown' },\n { id: 'ocaml', label: 'OCaml' },\n { id: 'python', label: 'Python' },\n { id: 'rust', label: 'Rust' },\n { id: 'scala', label: 'Scala' },\n { id: 'sql', label: 'SQL' },\n { id: 'typescript', label: 'TypeScript' },\n { id: 'xml', label: 'HTML / XML' },\n];\n//# sourceMappingURL=languages.js.map","import { Href, Language } from './brand.js';\nimport { SCHEMA_VERSION } from './schema.js';\n/**\n * Smart constructors for the document model. Prefer these over object literals:\n * they are pure, correctly typed, brand string fields (href/language), and\n * curry where a node has a configuration argument.\n *\n * @example Build a full document\n * ```ts\n * import {\n * document, heading, paragraph, plain, text, bold, link,\n * bulletList, listItem, codeBlock,\n * } from '@openstage/glyph-core';\n *\n * const doc = document(\n * heading(1)(plain('Welcome')),\n * paragraph(plain('Hello '), text(bold)('world'), plain('.')),\n * paragraph(text(link('https://example.com'))('a link')),\n * bulletList(listItem(paragraph(plain('item one')))),\n * codeBlock('typescript')('const x = 1;'),\n * );\n * editor.setContent(doc);\n * ```\n */\n/** Bold inline mark. Pass to {@link text}: `text(bold)('hi')`. */\nexport const bold = { type: 'bold' };\n/** Italic inline mark. Pass to {@link text}: `text(italic)('hi')`. */\nexport const italic = { type: 'italic' };\n/** Link inline mark. The href is branded; pass a raw string. */\nexport const link = (href) => ({\n type: 'link',\n attrs: { href: Href.of(href) },\n});\n/**\n * Curried text-node builder: apply marks first, then the string.\n *\n * @example `text(bold, italic)('important')` · `plain('plain text')`\n */\nexport const text = (...marks) => (value) => marks.length > 0 ? { type: 'text', text: value, marks } : { type: 'text', text: value };\n/** Unmarked text node — `plain('hi')` is `text()('hi')`. */\nexport const plain = text();\n/** Paragraph block from inline {@link TextNode}s. */\nexport const paragraph = (...content) => content.length > 0 ? { type: 'paragraph', content } : { type: 'paragraph' };\n/**\n * Curried heading builder: choose the level (1–3), then the inline content.\n *\n * @example `heading(2)(plain('Section'))`\n */\nexport const heading = (level) => (...content) => ({\n type: 'heading',\n attrs: { level },\n ...(content.length > 0 ? { content } : {}),\n});\n/** A single list entry; wrap paragraphs for {@link bulletList}/{@link orderedList}. */\nexport const listItem = (...content) => ({\n type: 'listItem',\n content,\n});\n/** Unordered list of {@link listItem}s. */\nexport const bulletList = (...content) => ({\n type: 'bulletList',\n content,\n});\n/** Ordered (numbered) list of {@link listItem}s. */\nexport const orderedList = (...content) => ({\n type: 'orderedList',\n content,\n});\n/** Block quote containing one or more paragraphs. */\nexport const blockquote = (...content) => ({\n type: 'blockquote',\n content,\n});\n/**\n * Curried code-block builder: optional language first, then the raw source.\n * The language is branded and should match a renderer-supported id for\n * highlighting (see `supportedCodeLanguages`).\n *\n * @example `codeBlock('rust')('fn main() {}')` · `codeBlock()('plain text')`\n */\nexport const codeBlock = (language) => (code) => ({\n type: 'codeBlock',\n ...(language ? { attrs: { language: Language.of(language) } } : {}),\n content: code ? [{ type: 'text', text: code }] : [],\n});\n/** Document root from top-level block nodes (no version envelope). */\nexport const doc = (...content) => ({ type: 'doc', content });\n/**\n * A complete {@link EditorDocument} (root + current schema version). This is\n * the value you pass to `editor.setContent` or the renderer.\n */\nexport const document = (...content) => ({\n schemaVersion: SCHEMA_VERSION,\n content: doc(...content),\n});\n//# sourceMappingURL=nodes.js.map","import { createLowlight } from 'lowlight';\n/**\n * Lazy-loaded syntax highlighting. Grammars are NOT bundled — each is a\n * `import()` that the consumer's bundler code-splits into its own chunk and\n * fetches only when a code block actually uses that language. The `lowlight`\n * instance is shared with the editor so edit and read-only views match.\n */\nconst loaders = {\n bash: () => import('highlight.js/lib/languages/bash'),\n csharp: () => import('highlight.js/lib/languages/csharp'),\n css: () => import('highlight.js/lib/languages/css'),\n fsharp: () => import('highlight.js/lib/languages/fsharp'),\n go: () => import('highlight.js/lib/languages/go'),\n haskell: () => import('highlight.js/lib/languages/haskell'),\n java: () => import('highlight.js/lib/languages/java'),\n javascript: () => import('highlight.js/lib/languages/javascript'),\n json: () => import('highlight.js/lib/languages/json'),\n kotlin: () => import('highlight.js/lib/languages/kotlin'),\n markdown: () => import('highlight.js/lib/languages/markdown'),\n ocaml: () => import('highlight.js/lib/languages/ocaml'),\n python: () => import('highlight.js/lib/languages/python'),\n rust: () => import('highlight.js/lib/languages/rust'),\n scala: () => import('highlight.js/lib/languages/scala'),\n sql: () => import('highlight.js/lib/languages/sql'),\n typescript: () => import('highlight.js/lib/languages/typescript'),\n xml: () => import('highlight.js/lib/languages/xml'),\n};\n// Aliases we advertise → canonical loader id (grammars register their own\n// highlight.js aliases too, but only after they are loaded).\nconst aliases = {\n ts: 'typescript',\n js: 'javascript',\n jsx: 'javascript',\n sh: 'bash',\n shell: 'bash',\n zsh: 'bash',\n html: 'xml',\n htm: 'xml',\n xhtml: 'xml',\n svg: 'xml',\n cs: 'csharp',\n 'c#': 'csharp',\n fs: 'fsharp',\n 'f#': 'fsharp',\n kt: 'kotlin',\n py: 'python',\n rs: 'rust',\n md: 'markdown',\n};\nconst lowlight = createLowlight();\nconst normalize = (name) => {\n const n = name.trim().toLowerCase();\n return aliases[n] ?? n;\n};\n/**\n * Whether a language *can* be highlighted (a loader exists). It may not be\n * loaded yet — see {@link ensureLanguage}.\n */\nexport const isLanguageSupported = (language) => normalize(language) in loaders;\nconst pending = new Map();\n/**\n * Loads and registers a grammar on demand (idempotent, de-duplicated).\n * Resolves `true` once the language is highlightable, `false` if there is no\n * grammar for it.\n */\nexport const ensureLanguage = (language) => {\n const id = normalize(language);\n const loader = loaders[id];\n if (!loader)\n return Promise.resolve(false);\n if (lowlight.registered(id))\n return Promise.resolve(true);\n const inflight = pending.get(id);\n if (inflight)\n return inflight;\n const p = loader()\n .then((mod) => {\n if (!lowlight.registered(id)) {\n lowlight.register({ [id]: mod.default });\n }\n return true;\n })\n .catch(() => false);\n pending.set(id, p);\n return p;\n};\n/** Eagerly load grammars (e.g. before a synchronous {@link renderToHTML}). */\nexport const preloadLanguages = (languages) => Promise.all(languages.map(ensureLanguage)).then(() => undefined);\nconst toClassList = (className) => className == null ? [] : Array.isArray(className) ? className : [className];\nconst hastToDom = (doc) => (node) => {\n if (node.type === 'text')\n return doc.createTextNode(node.value);\n const parent = node.type === 'root' ? doc.createDocumentFragment() : doc.createElement(node.tagName);\n if (node.type === 'element') {\n const classes = toClassList(node.properties?.className);\n if (classes.length > 0)\n parent.className = classes.join(' ');\n }\n node.children.forEach((child) => parent.appendChild(hastToDom(doc)(child)));\n return parent;\n};\n/**\n * Synchronously highlight `code` into detached DOM, or `null` if the grammar\n * is unknown or not yet loaded. Callers wanting lazy upgrade should fall back\n * to plain text and re-render after {@link ensureLanguage} resolves.\n */\nexport const highlightToDom = (doc, language, code) => {\n const id = normalize(language);\n if (!lowlight.registered(id))\n return null;\n const tree = lowlight.highlight(id, code);\n return hastToDom(doc)(tree);\n};\nexport { lowlight };\n//# sourceMappingURL=highlight.js.map","/**\n * Token stylesheet for highlighted code (the `hljs-*` classes lowlight emits).\n * Every colour is a CSS custom property with a light-theme fallback, so a host\n * can re-theme highlighting the same token-driven way as the editor — e.g.\n * `:root { --glyph-hl-keyword: #c678dd; }` — without touching this file.\n */\nexport const highlightThemeCss = /* css */ `\n.hljs { color: var(--glyph-hl-text, #24292e); }\n.hljs-comment, .hljs-quote { color: var(--glyph-hl-comment, #6a737d); font-style: italic; }\n.hljs-keyword, .hljs-selector-tag, .hljs-built_in, .hljs-name, .hljs-tag {\n color: var(--glyph-hl-keyword, #d73a49);\n}\n.hljs-string, .hljs-attr, .hljs-template-tag, .hljs-template-variable,\n.hljs-addition, .hljs-regexp {\n color: var(--glyph-hl-string, #032f62);\n}\n.hljs-number, .hljs-literal, .hljs-boolean {\n color: var(--glyph-hl-number, #005cc5);\n}\n.hljs-title, .hljs-title.function_, .hljs-section, .hljs-selector-id {\n color: var(--glyph-hl-title, #6f42c1);\n}\n.hljs-type, .hljs-class .hljs-title, .hljs-title.class_ {\n color: var(--glyph-hl-type, #e36209);\n}\n.hljs-variable, .hljs-params, .hljs-property {\n color: var(--glyph-hl-variable, #24292e);\n}\n.hljs-symbol, .hljs-bullet, .hljs-link, .hljs-meta, .hljs-attribute {\n color: var(--glyph-hl-meta, #005cc5);\n}\n.hljs-deletion { color: var(--glyph-hl-deletion, #b31d28); }\n.hljs-emphasis { font-style: italic; }\n.hljs-strong { font-weight: 600; }\n`;\n//# sourceMappingURL=highlight-theme.js.map","import {\n type EditorContent,\n type EditorDocument,\n SCHEMA_VERSION,\n emptyContent,\n} from '@openstage/glyph-core';\n\n/**\n * Structural shape of an editing-engine (ProseMirror/Tiptap) JSON node. This\n * module is the *only* place that knows the engine wire format, keeping that\n * knowledge out of the public `@openstage/glyph-core` surface. Pure (no engine\n * import) so it stays unit-testable.\n */\nexport type EngineNode = {\n type?: string;\n attrs?: Record<string, unknown>;\n content?: EngineNode[];\n marks?: { type: string; attrs?: Record<string, unknown> }[];\n text?: string;\n};\n\n/**\n * Public document → engine document JSON. The public schema and the engine's\n * default schema are intentionally aligned, so this drops the version envelope\n * and hands over the inner doc.\n */\nexport const toEngineDoc = (document: EditorDocument): EngineNode =>\n document.content as unknown as EngineNode;\n\n/**\n * Engine document JSON → public document, re-applying the schema-version\n * envelope. Empty/garbage input collapses to an empty document.\n */\nexport const fromEngineDoc = (doc: EngineNode | null | undefined): EditorDocument => ({\n schemaVersion: SCHEMA_VERSION,\n content:\n doc && doc.type === 'doc' && Array.isArray(doc.content)\n ? (doc as unknown as EditorContent)\n : emptyContent(),\n});\n","/**\n * Defence-in-depth href allowlist, shared by the editor's Link extension and\n * the link dialog. Mirrors the renderer's sanitizer, including stripping\n * control/space chars the browser would otherwise ignore when resolving a URL\n * (so `java\\nscript:` cannot slip through).\n */\n// eslint-disable-next-line no-control-regex\nconst CONTROL_OR_SPACE = /[\\u0000-\\u0020]+/g;\n\nexport const isSafeLinkHref = (url: string): boolean => {\n const cleaned = url.replace(CONTROL_OR_SPACE, '');\n if (cleaned === '') return false;\n return (\n /^(https?:|mailto:|tel:)/i.test(cleaned) ||\n /^[/#.]/.test(cleaned) ||\n !/^[a-z][a-z0-9+.-]*:/i.test(cleaned)\n );\n};\n","export const editorStyles = /* css */ `\n:host {\n --editor-background: #ffffff;\n --editor-text: #111827;\n --editor-muted-text: #6b7280;\n --editor-border: #e5e7eb;\n --editor-accent: #2563eb;\n --editor-selection: #bfdbfe;\n --editor-toolbar-background: #f9fafb;\n --editor-slash-menu-background: #ffffff;\n --editor-slash-menu-hover: #f3f4f6;\n --editor-slash-menu-text: #111827;\n --editor-code-background: #f3f4f6;\n --editor-block-quote-border: #d1d5db;\n --editor-radius: 0.5rem;\n\n display: block;\n position: relative;\n color: var(--editor-text);\n background: var(--editor-background);\n border: 1px solid var(--editor-border);\n border-radius: var(--editor-radius);\n font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n}\n\n/* config.bordered === false — hide only the outer frame border. */\n:host(.borderless) {\n border-color: transparent;\n}\n\n.header {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n align-items: center;\n padding: 0.5rem;\n background: var(--editor-toolbar-background);\n border-top-left-radius: var(--editor-radius);\n border-top-right-radius: var(--editor-radius);\n}\n\n/* Stay visible while the page scrolls past a long document. */\n.header.floating {\n position: sticky;\n top: 0;\n z-index: 5;\n backdrop-filter: saturate(180%) blur(2px);\n}\n\n.header select {\n font: inherit;\n font-size: 0.85rem;\n padding: 0.2rem 0.35rem;\n border: 1px solid var(--editor-border);\n border-radius: 0.25rem;\n background: var(--editor-background);\n color: var(--editor-text);\n cursor: pointer;\n}\n\n.header select[hidden] {\n display: none;\n}\n\n.header button {\n font: inherit;\n font-size: 0.85rem;\n padding: 0.25rem 0.5rem;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n background: transparent;\n color: var(--editor-text);\n cursor: pointer;\n}\n\n.header button:hover { background: var(--editor-slash-menu-hover); }\n.header button.active { border-color: var(--editor-accent); color: var(--editor-accent); }\n.header .spacer { flex: 1; }\n.header .status { font-size: 0.75rem; color: var(--editor-muted-text); }\n\n.surface { padding: 1rem; }\n\n.ProseMirror { outline: none; min-height: 8rem; line-height: 1.6; }\n.ProseMirror:focus { outline: none; }\n.ProseMirror ::selection { background: var(--editor-selection); }\n\n.ProseMirror p { margin: 0 0 0.75rem; }\n.ProseMirror h1 { font-size: 1.75rem; margin: 1rem 0 0.5rem; }\n.ProseMirror h2 { font-size: 1.4rem; margin: 1rem 0 0.5rem; }\n.ProseMirror h3 { font-size: 1.15rem; margin: 1rem 0 0.5rem; }\n.ProseMirror a { color: var(--editor-accent); text-decoration: underline; }\n.ProseMirror ul, .ProseMirror ol { padding-left: 1.5rem; margin: 0 0 0.75rem; }\n.ProseMirror blockquote {\n margin: 0 0 0.75rem;\n padding-left: 1rem;\n border-left: 3px solid var(--editor-block-quote-border);\n color: var(--editor-muted-text);\n}\n.ProseMirror pre {\n white-space: pre;\n overflow-x: auto;\n padding: 1rem;\n border-radius: 0.5rem;\n background: var(--editor-code-background);\n margin: 0 0 0.75rem;\n}\n.ProseMirror pre code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 0.875rem;\n}\n.ProseMirror p.is-editor-empty:first-child::before {\n content: attr(data-placeholder);\n color: var(--editor-muted-text);\n float: left;\n height: 0;\n pointer-events: none;\n}\n\n.slash-menu {\n position: fixed;\n z-index: 1000;\n min-width: 12rem;\n background: var(--editor-slash-menu-background);\n color: var(--editor-slash-menu-text);\n border: 1px solid var(--editor-border);\n border-radius: 0.5rem;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n padding: 0.25rem;\n overflow: hidden;\n}\n.slash-menu[hidden] { display: none; }\n.slash-menu button {\n display: block;\n width: 100%;\n text-align: left;\n font: inherit;\n font-size: 0.875rem;\n padding: 0.4rem 0.6rem;\n border: 0;\n border-radius: 0.25rem;\n background: transparent;\n color: inherit;\n cursor: pointer;\n}\n.slash-menu button:hover,\n.slash-menu button.active { background: var(--editor-slash-menu-hover); }\n\n.link-dialog {\n position: fixed;\n z-index: 1001;\n width: min(24rem, calc(100vw - 2rem));\n background: var(--editor-background);\n color: var(--editor-text);\n border: 1px solid var(--editor-border);\n border-radius: 0.5rem;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);\n padding: 0.85rem;\n}\n.link-dialog[hidden] { display: none; }\n.link-dialog__title {\n margin: 0 0 0.75rem;\n font-size: 1rem;\n font-weight: 600;\n color: var(--editor-text);\n}\n.link-dialog__label {\n display: block;\n font-size: 0.85rem;\n color: var(--editor-muted-text);\n}\n.link-dialog__input {\n display: block;\n width: 100%;\n box-sizing: border-box;\n margin-top: 0.4rem;\n font: inherit;\n padding: 0.45rem 0.55rem;\n border: 1px solid var(--editor-border);\n border-radius: 0.375rem;\n background: var(--editor-background);\n color: var(--editor-text);\n}\n.link-dialog__input:focus {\n outline: 2px solid var(--editor-accent);\n outline-offset: -1px;\n}\n.link-dialog__error {\n margin: 0.5rem 0 0;\n font-size: 0.8rem;\n color: #dc2626;\n}\n.link-dialog__error[hidden] { display: none; }\n.link-dialog__actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-top: 0.9rem;\n}\n.link-dialog__spacer { flex: 1; }\n.link-dialog button {\n font: inherit;\n font-size: 0.85rem;\n padding: 0.4rem 0.7rem;\n border: 1px solid var(--editor-border);\n border-radius: 0.375rem;\n background: var(--editor-background);\n color: var(--editor-text);\n cursor: pointer;\n}\n.link-dialog button:hover { background: var(--editor-slash-menu-hover); }\n.link-dialog__save {\n border-color: var(--editor-accent);\n color: var(--editor-accent);\n}\n.link-dialog__remove[hidden] { display: none; }\n`;\n","import { Extension, type Editor } from '@tiptap/core';\n\n/**\n * Safari does not reliably perform Backspace/Delete inside `white-space: pre`\n * code blocks (the browser's default deletion never reaches ProseMirror), so\n * editing elsewhere works but code blocks feel \"stuck\".\n *\n * This extension takes ownership of Backspace/Delete *only* when the selection\n * is inside a code block and performs the edit through ProseMirror commands,\n * which is browser-independent. At the block edges it returns false so the\n * normal join/exit behaviour (and other extensions) still apply.\n */\ntype Direction = -1 | 1;\n\nexport const CodeBlockBackspace = Extension.create({\n name: 'codeBlockBackspace',\n\n addKeyboardShortcuts() {\n const handle =\n (direction: Direction) =>\n ({ editor }: { editor: Editor }): boolean => {\n if (!editor.isActive('codeBlock')) return false;\n\n const { selection } = editor.state;\n const { empty, from, $from } = selection;\n\n if (!empty) return editor.chain().focus().deleteSelection().run();\n\n const atStart = from <= $from.start();\n const atEnd = from >= $from.end();\n if (direction === -1 && atStart) return false; // let join/lift run\n if (direction === 1 && atEnd) return false;\n\n const range = direction === -1 ? { from: from - 1, to: from } : { from, to: from + 1 };\n return editor.chain().focus().deleteRange(range).run();\n };\n\n return {\n Backspace: handle(-1),\n Delete: handle(1),\n };\n },\n});\n","import type { Editor } from '@tiptap/core';\nimport {\n type VirtualElement,\n autoUpdate,\n computePosition,\n flip,\n offset,\n shift,\n} from '@floating-ui/dom';\nimport type { EditorApi, SlashCommand } from '@openstage/glyph-core';\nimport { filterSlashCommands } from '@openstage/glyph-core';\n\nexport interface SlashMenuController {\n setCommands: (commands: readonly SlashCommand[]) => void;\n update: () => void;\n /** Returns true if it consumed the key. */\n handleKey: (event: KeyboardEvent) => boolean;\n destroy: () => void;\n}\n\nexport type SlashMenuDeps = {\n editor: Editor;\n api: EditorApi;\n commands: readonly SlashCommand[];\n container: HTMLElement;\n};\n\n/**\n * Lightweight slash menu as a closure factory. Rather than depend on\n * @tiptap/suggestion, it reads the text between the start of the current block\n * and the cursor and shows the menu while it matches /query.\n */\nexport const createSlashMenu = ({\n editor,\n api,\n commands,\n container,\n}: SlashMenuDeps): SlashMenuController => {\n const el = container.ownerDocument.createElement('div');\n el.className = 'slash-menu';\n el.hidden = true;\n container.appendChild(el);\n\n let allCommands = commands;\n let active = false;\n let from = 0;\n let filtered: SlashCommand[] = [];\n let selectedIndex = 0;\n let cleanup: (() => void) | null = null;\n\n // A virtual reference at the caret. Floating UI resolves the floating\n // element against its real containing block, so this stays correct even\n // when an ancestor (transform/filter/backdrop-filter/contain) re-roots\n // `position: fixed` — the failure the hand-rolled math could not survive.\n const reference: VirtualElement = {\n contextElement: editor.view.dom,\n getBoundingClientRect: () => {\n const c = editor.view.coordsAtPos(editor.state.selection.$from.pos);\n return {\n x: c.left,\n y: c.top,\n left: c.left,\n right: c.right,\n top: c.top,\n bottom: c.bottom,\n width: c.right - c.left,\n height: c.bottom - c.top,\n };\n },\n };\n\n const reposition = (): void => {\n void computePosition(reference, el, {\n strategy: 'fixed',\n placement: 'bottom-start',\n middleware: [offset(4), flip({ padding: 8 }), shift({ padding: 8 })],\n }).then(({ x, y }) => {\n el.style.left = `${Math.round(x)}px`;\n el.style.top = `${Math.round(y)}px`;\n });\n };\n\n const hide = (): void => {\n if (!active && el.hidden) return;\n active = false;\n el.hidden = true;\n cleanup?.();\n cleanup = null;\n };\n\n const run = (index: number): void => {\n const command = filtered[index];\n if (!command) return;\n editor.chain().focus().deleteRange({ from, to: editor.state.selection.$from.pos }).run();\n command.run(api);\n hide();\n };\n\n const render = (): void => {\n el.replaceChildren();\n filtered.forEach((command, i) => {\n const btn = el.ownerDocument.createElement('button');\n btn.type = 'button';\n btn.textContent = command.label;\n if (i === selectedIndex) btn.classList.add('active');\n btn.addEventListener('mousedown', (e) => {\n e.preventDefault();\n run(i);\n });\n el.appendChild(btn);\n });\n el.hidden = false;\n // autoUpdate keeps it pinned to the caret across scroll/resize and\n // recomputes against the correct containing block.\n if (!cleanup) cleanup = autoUpdate(reference, el, reposition);\n else reposition();\n };\n\n const update = (): void => {\n const { state } = editor;\n const { $from, empty } = state.selection;\n if (!empty) return hide();\n\n const textBefore = state.doc.textBetween($from.start(), $from.pos, '\\n', '\\n');\n const match = /(?:^|\\s)\\/([\\w-]*)$/.exec(textBefore);\n if (!match) return hide();\n\n const query = match[1] ?? '';\n from = $from.pos - query.length - 1;\n filtered = filterSlashCommands(query)(allCommands);\n if (filtered.length === 0) return hide();\n\n active = true;\n selectedIndex = 0;\n render();\n };\n\n const handleKey = (event: KeyboardEvent): boolean => {\n if (!active) return false;\n const move = (delta: number): boolean => {\n selectedIndex = (selectedIndex + delta + filtered.length) % filtered.length;\n render();\n return true;\n };\n if (event.key === 'ArrowDown') return move(1);\n if (event.key === 'ArrowUp') return move(-1);\n if (event.key === 'Enter') {\n run(selectedIndex);\n return true;\n }\n if (event.key === 'Escape') {\n hide();\n return true;\n }\n return false;\n };\n\n return {\n setCommands: (next) => {\n allCommands = next;\n },\n update,\n handleKey,\n destroy: () => {\n cleanup?.();\n cleanup = null;\n el.remove();\n },\n };\n};\n","import {\n type VirtualElement,\n autoUpdate,\n computePosition,\n flip,\n offset,\n shift,\n} from '@floating-ui/dom';\n\nexport interface LinkDialogController {\n /** Open the popover, pre-filled with `initial` (empty for a new link). */\n open: (opts: {\n initial: string;\n onSubmit: (href: string) => void;\n onClear: () => void;\n /** Called on every close so the caller can return focus to the editor. */\n restoreFocus?: () => void;\n }) => void;\n destroy: () => void;\n}\n\nexport type LinkDialogDeps = {\n container: HTMLElement;\n isSafe: (url: string) => boolean;\n /** Viewport-rect of the anchor (the selection/caret) to position against. */\n getAnchorRect: () => DOMRectReadOnly | DOMRect;\n};\n\n/**\n * Prefix a bare host (`codeberg.org`) with `https://` so it becomes an\n * absolute external link instead of a page-relative path. Existing schemes\n * and relative/anchor paths are left untouched.\n */\nconst normalizeHref = (value: string): string => {\n const v = value.trim();\n if (v === '') return v;\n if (/^[a-z][a-z0-9+.-]*:/i.test(v)) return v; // already has a scheme\n if (/^[/#.]/.test(v)) return v; // relative path or fragment\n if (/^[^\\s/]+\\.[^\\s]+/.test(v) || /^localhost(:\\d+)?(\\/|$)/i.test(v)) {\n return `https://${v}`;\n }\n return v;\n};\n\n/**\n * A small, themed, in-Shadow-DOM link popover replacing the native\n * `window.prompt`. Anchored to the selection via Floating UI, so it is\n * positioned correctly even when an ancestor (transform / filter /\n * backdrop-filter / contain) re-roots `position: fixed` — and it is never\n * clipped by the editor's own `overflow`. Closure factory.\n *\n * Focus moves in on open and is trapped while open (`aria-modal`); Escape,\n * outside-click, or an action closes it and returns focus to the editor.\n */\nexport const createLinkDialog = ({\n container,\n isSafe,\n getAnchorRect,\n}: LinkDialogDeps): LinkDialogController => {\n const doc = container.ownerDocument;\n\n const el = doc.createElement('div');\n el.className = 'link-dialog';\n el.hidden = true;\n el.setAttribute('role', 'dialog');\n el.setAttribute('aria-modal', 'true');\n\n const titleEl = doc.createElement('p');\n titleEl.className = 'link-dialog__title';\n titleEl.id = 'glyph-link-dialog-title';\n el.setAttribute('aria-labelledby', titleEl.id);\n\n const label = doc.createElement('label');\n label.className = 'link-dialog__label';\n label.textContent = 'Link URL';\n\n const input = doc.createElement('input');\n input.type = 'text';\n input.className = 'link-dialog__input';\n input.placeholder = 'https://example.com';\n label.appendChild(input);\n\n const error = doc.createElement('p');\n error.className = 'link-dialog__error';\n error.hidden = true;\n error.textContent = 'Enter a valid URL (http, https, mailto, tel, or relative).';\n\n const actions = doc.createElement('div');\n actions.className = 'link-dialog__actions';\n const removeBtn = doc.createElement('button');\n removeBtn.type = 'button';\n removeBtn.textContent = 'Remove';\n removeBtn.className = 'link-dialog__remove';\n const spacer = doc.createElement('span');\n spacer.className = 'link-dialog__spacer';\n const cancelBtn = doc.createElement('button');\n cancelBtn.type = 'button';\n cancelBtn.textContent = 'Cancel';\n const saveBtn = doc.createElement('button');\n saveBtn.type = 'button';\n saveBtn.textContent = 'Save';\n saveBtn.className = 'link-dialog__save';\n actions.append(removeBtn, spacer, cancelBtn, saveBtn);\n\n el.append(titleEl, label, error, actions);\n container.appendChild(el);\n\n const reference: VirtualElement = { getBoundingClientRect: () => getAnchorRect() };\n let cleanup: (() => void) | null = null;\n let submit: (href: string) => void = () => {};\n let clear: () => void = () => {};\n let restore: () => void = () => {};\n let openToken = 0;\n\n const reposition = (): void => {\n void computePosition(reference, el, {\n strategy: 'fixed',\n placement: 'bottom-start',\n middleware: [offset(6), flip({ padding: 8 }), shift({ padding: 8 })],\n }).then(({ x, y }) => {\n el.style.left = `${Math.round(x)}px`;\n el.style.top = `${Math.round(y)}px`;\n });\n };\n\n const focusables = (): HTMLElement[] =>\n [input, removeBtn, cancelBtn, saveBtn].filter((b) => !b.hidden);\n\n const onOutside = (e: Event): void => {\n if (!e.composedPath().includes(el)) close();\n };\n\n const close = (): void => {\n if (el.hidden) return;\n el.hidden = true;\n error.hidden = true;\n openToken += 1;\n cleanup?.();\n cleanup = null;\n doc.removeEventListener('mousedown', onOutside, true);\n const r = restore;\n setTimeout(r, 0);\n };\n\n const confirm = (): void => {\n const value = normalizeHref(input.value);\n if (value === '') {\n clear();\n close();\n return;\n }\n if (!isSafe(value)) {\n error.hidden = false;\n input.focus();\n return;\n }\n submit(value);\n close();\n };\n\n saveBtn.addEventListener('click', confirm);\n cancelBtn.addEventListener('click', close);\n removeBtn.addEventListener('click', () => {\n clear();\n close();\n });\n\n el.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n close();\n return;\n }\n if (e.key === 'Enter' && e.target === input) {\n e.preventDefault();\n confirm();\n return;\n }\n if (e.key !== 'Tab') return;\n const items = focusables();\n const first = items[0];\n const last = items[items.length - 1];\n const current = (el.getRootNode() as ShadowRoot | Document).activeElement;\n if (e.shiftKey && current === first) {\n e.preventDefault();\n last?.focus();\n } else if (!e.shiftKey && current === last) {\n e.preventDefault();\n first?.focus();\n }\n });\n\n return {\n open: ({ initial, onSubmit, onClear, restoreFocus }) => {\n submit = onSubmit;\n clear = onClear;\n restore = restoreFocus ?? (() => {});\n input.value = initial;\n error.hidden = true;\n const editing = initial !== '';\n removeBtn.hidden = !editing;\n titleEl.textContent = editing ? 'Edit link' : 'Add link';\n el.hidden = false;\n if (!cleanup) cleanup = autoUpdate(reference, el, reposition);\n else reposition();\n // Defer so this open click isn't immediately treated as \"outside\".\n setTimeout(() => doc.addEventListener('mousedown', onOutside, true), 0);\n const token = ++openToken;\n setTimeout(() => {\n if (token !== openToken || el.hidden) return;\n input.focus();\n input.select();\n }, 0);\n },\n destroy: () => {\n cleanup?.();\n cleanup = null;\n doc.removeEventListener('mousedown', onOutside, true);\n el.remove();\n },\n };\n};\n","import type { Editor } from '@tiptap/core';\nimport type { EditorApi, ToolbarIcon, ToolbarItemId } from '@openstage/glyph-core';\nimport { DEFAULT_TOOLBAR, supportedCodeLanguages } from '@openstage/glyph-core';\nimport { createLinkDialog } from './link-dialog.js';\nimport { isSafeLinkHref } from './safe-href.js';\n\nexport interface HeaderController {\n readonly el: HTMLDivElement;\n setStatus: (text: string) => void;\n sync: () => void;\n}\n\nexport type HeaderDeps = {\n editor: Editor;\n api: EditorApi;\n container: HTMLElement;\n items?: readonly ToolbarItemId[];\n icons?: Partial<Record<ToolbarItemId, ToolbarIcon>>;\n floating?: boolean;\n};\n\n/** Resolve a ToolbarIcon to a fresh Node, or null to use the text label. */\nconst resolveIcon = (icon: ToolbarIcon | undefined, doc: Document): Node | null => {\n if (icon == null) return null;\n if (typeof icon === 'function') return icon(doc);\n if (typeof icon === 'string') return doc.createTextNode(icon);\n return icon.cloneNode(true);\n};\n\ntype Btn = {\n label: string;\n title: string;\n run: () => void;\n isActive?: () => boolean;\n};\n\n/** Minimal toolbar — discoverability, not a Word-style ribbon. */\nexport const createHeader = ({\n editor,\n api,\n container,\n items = DEFAULT_TOOLBAR,\n icons,\n floating = true,\n}: HeaderDeps): HeaderController => {\n const doc = container.ownerDocument;\n const el = doc.createElement('div');\n el.className = floating ? 'header floating' : 'header';\n\n const linkDialog = createLinkDialog({\n container,\n isSafe: isSafeLinkHref,\n getAnchorRect: () => {\n const { from, to } = editor.state.selection;\n const a = editor.view.coordsAtPos(from);\n const b = editor.view.coordsAtPos(to);\n const left = Math.min(a.left, b.left);\n const right = Math.max(a.right, b.right);\n const top = Math.min(a.top, b.top);\n const bottom = Math.max(a.bottom, b.bottom);\n return new DOMRect(left, top, right - left, bottom - top);\n },\n });\n const openLinkDialog = (): void => {\n linkDialog.open({\n initial: (editor.getAttributes('link').href as string | undefined) ?? '',\n onSubmit: (href) => api.setLink(href),\n onClear: () => editor.chain().focus().extendMarkRange('link').unsetLink().run(),\n restoreFocus: () => editor.commands.focus(),\n });\n };\n\n const registry: Partial<Record<ToolbarItemId, Btn>> = {\n bold: {\n label: 'B',\n title: 'Bold',\n run: api.toggleBold,\n isActive: () => editor.isActive('bold'),\n },\n italic: {\n label: 'I',\n title: 'Italic',\n run: api.toggleItalic,\n isActive: () => editor.isActive('italic'),\n },\n link: {\n label: 'Link',\n title: 'Link',\n run: openLinkDialog,\n isActive: () => editor.isActive('link'),\n },\n heading1: {\n label: 'H1',\n title: 'Heading 1',\n run: () => api.setHeading(1),\n isActive: () => editor.isActive('heading', { level: 1 }),\n },\n heading2: {\n label: 'H2',\n title: 'Heading 2',\n run: () => api.setHeading(2),\n isActive: () => editor.isActive('heading', { level: 2 }),\n },\n heading3: {\n label: 'H3',\n title: 'Heading 3',\n run: () => api.setHeading(3),\n isActive: () => editor.isActive('heading', { level: 3 }),\n },\n bulletList: {\n label: '• List',\n title: 'Bullet list',\n run: api.toggleBulletList,\n isActive: () => editor.isActive('bulletList'),\n },\n orderedList: {\n label: '1. List',\n title: 'Numbered list',\n run: api.toggleOrderedList,\n isActive: () => editor.isActive('orderedList'),\n },\n blockquote: {\n label: 'Quote',\n title: 'Quote',\n run: api.toggleBlockquote,\n isActive: () => editor.isActive('blockquote'),\n },\n codeBlock: {\n label: 'Code',\n title: 'Code block',\n run: api.toggleCodeBlock,\n isActive: () => editor.isActive('codeBlock'),\n },\n undo: { label: 'Undo', title: 'Undo', run: api.undo },\n redo: { label: 'Redo', title: 'Redo', run: api.redo },\n };\n\n const status = doc.createElement('span');\n status.className = 'status';\n\n // Language picker — only visible when the caret is inside a code block.\n const langSelect = doc.createElement('select');\n langSelect.title = 'Code block language';\n langSelect.hidden = true;\n const plain = doc.createElement('option');\n plain.value = '';\n plain.textContent = 'Plain text';\n langSelect.appendChild(plain);\n supportedCodeLanguages.forEach(({ id, label }) => {\n const opt = doc.createElement('option');\n opt.value = id;\n opt.textContent = label;\n langSelect.appendChild(opt);\n });\n langSelect.addEventListener('change', () => {\n editor\n .chain()\n .focus()\n .updateAttributes('codeBlock', { language: langSelect.value || null })\n .run();\n });\n\n const buttons: { btn: HTMLButtonElement; def: Btn }[] = [];\n let hasLanguagePicker = false;\n\n items.forEach((id) => {\n if (id === 'spacer') {\n const s = doc.createElement('span');\n s.className = 'spacer';\n el.appendChild(s);\n return;\n }\n if (id === 'status') {\n el.appendChild(status);\n return;\n }\n if (id === 'codeLanguage') {\n el.appendChild(langSelect);\n hasLanguagePicker = true;\n return;\n }\n const def = registry[id];\n if (!def) return;\n const btn = doc.createElement('button');\n btn.type = 'button';\n const custom = resolveIcon(icons?.[id], doc);\n if (custom) btn.appendChild(custom);\n else btn.textContent = def.label;\n btn.title = def.title;\n btn.setAttribute('aria-label', def.title);\n btn.addEventListener('mousedown', (e) => {\n e.preventDefault();\n def.run();\n sync();\n });\n el.appendChild(btn);\n buttons.push({ btn, def });\n });\n\n container.appendChild(el);\n\n const sync = (): void => {\n buttons.forEach(({ btn, def }) => btn.classList.toggle('active', def.isActive?.() ?? false));\n if (hasLanguagePicker) {\n const inCode = editor.isActive('codeBlock');\n langSelect.hidden = !inCode;\n if (inCode) {\n const current = (editor.getAttributes('codeBlock').language as string | null) ?? '';\n if (langSelect.value !== current) langSelect.value = current;\n }\n }\n };\n\n return {\n el,\n setStatus: (text) => {\n status.textContent = text;\n },\n sync,\n };\n};\n","import { Editor } from '@tiptap/core';\nimport StarterKit from '@tiptap/starter-kit';\nimport Link from '@tiptap/extension-link';\nimport Placeholder from '@tiptap/extension-placeholder';\nimport { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight';\nimport { highlightThemeCss, ensureLanguage } from '@openstage/glyph-renderer';\nimport { lowlight } from '@openstage/glyph-renderer/internal';\nimport {\n type EditorApi,\n type EditorConfig,\n type EditorDocument,\n type EditorEventMap,\n type EditorTheme,\n type SlashCommand,\n assertEditorDocument,\n createEmptyDocument,\n defaultSlashCommands,\n migrate,\n themeToCssVars,\n} from '@openstage/glyph-core';\nimport { fromEngineDoc, toEngineDoc } from './document-conversion.js';\nimport { isSafeLinkHref } from './safe-href.js';\nimport { editorStyles } from './styles.js';\nimport { CodeBlockBackspace } from './codeblock-backspace.js';\nimport { createSlashMenu, type SlashMenuController } from './slash-menu.js';\nimport { createHeader, type HeaderController } from './header.js';\n\n/**\n * Public shape of the `<glyph-editor>` element. The DOM type is also augmented\n * (`HTMLElementTagNameMap`), so `document.querySelector('glyph-editor')` is\n * typed as this interface automatically.\n *\n * Properties accept rich values (objects/arrays) and must be set in JavaScript,\n * not via HTML attributes. The only attributes observed are `placeholder` and\n * `show-header`.\n *\n * Events (all `bubbles: true, composed: true` so they cross Shadow DOM):\n * - `content-change` — `CustomEvent<EditorDocument>`, fired on every edit\n * - `selection-change` — `CustomEvent<void>`, caret/selection moved\n * - `ready` — `CustomEvent<void>`, editor initialised\n * - `focus` / `blur` — `CustomEvent<void>`\n */\nexport interface GlyphEditorElement extends HTMLElement {\n /** The current document. Reading returns {@link getJSON}; writing calls {@link setContent}. */\n content: EditorDocument;\n /** Color theme; merged onto CSS custom properties. Omit for the built-in light theme. */\n theme?: EditorTheme;\n /** Slash-command menu entries. Defaults to the built-in set; assign to replace. */\n commands?: readonly SlashCommand[];\n /** Toolbar/behaviour configuration. Assigning rebuilds the editor instance. */\n config?: EditorConfig;\n /**\n * Returns the current document as canonical JSON. Safe to persist verbatim\n * and later pass back to {@link setContent}.\n */\n getJSON(): EditorDocument;\n /**\n * Replaces the document. The input is validated and migrated to the current\n * schema; an invalid value throws `TypeError`.\n *\n * @param document - A document previously produced by {@link getJSON} (or\n * built with the `@openstage/glyph-core` node constructors).\n */\n setContent(document: EditorDocument): void;\n /** Moves keyboard focus into the editor. */\n focus(): void;\n /**\n * The abstracted command surface (no engine types). For headless usage set\n * `config.showHeader = false` and drive the editor from your own UI plus the\n * DOM events. Throws if called before the element is connected to the DOM.\n *\n * @example\n * ```ts\n * const api = editor.getApi();\n * api.toggleBold();\n * api.setHeading(2);\n * ```\n */\n getApi(): EditorApi;\n /**\n * Sets the text shown in the toolbar `status` slot (e.g. save state).\n * No-op if `status` is not part of the configured toolbar.\n *\n * @param text - Status text, or `''` to clear it.\n */\n setStatus(text: string): void;\n}\n\n/**\n * `<glyph-editor>` implementation. Instantiated by the browser via the custom\n * element registry — construct it through the DOM\n * (`document.createElement('glyph-editor')` or markup), never with `new`.\n *\n * All implementation state uses ECMAScript private fields (`#x`) so the emitted\n * type declarations expose no engine (Tiptap/ProseMirror) types.\n *\n * @see {@link GlyphEditorElement} for the documented public API.\n */\nexport class GlyphEditor extends HTMLElement implements GlyphEditorElement {\n static get observedAttributes(): string[] {\n return ['placeholder', 'show-header'];\n }\n\n #editor: Editor | null = null;\n #slashMenu: SlashMenuController | null = null;\n #header: HeaderController | null = null;\n #api: EditorApi | null = null;\n #status = '';\n readonly #ensuredLangs = new Set<string>();\n #refreshingHighlight = false;\n #surface!: HTMLDivElement;\n\n #content: EditorDocument = createEmptyDocument();\n #theme: EditorTheme | undefined;\n #commands: readonly SlashCommand[] = defaultSlashCommands;\n #config: EditorConfig = { showHeader: true, enableSlashCommands: true };\n\n readonly #shadow: ShadowRoot;\n\n constructor() {\n super();\n this.#shadow = this.attachShadow({ mode: 'open' });\n }\n\n get content(): EditorDocument {\n return this.getJSON();\n }\n set content(document: EditorDocument) {\n this.setContent(document);\n }\n\n get theme(): EditorTheme | undefined {\n return this.#theme;\n }\n set theme(theme: EditorTheme | undefined) {\n this.#theme = theme;\n this.#applyTheme();\n }\n\n get commands(): readonly SlashCommand[] {\n return this.#commands;\n }\n set commands(commands: readonly SlashCommand[]) {\n this.#commands = commands ?? defaultSlashCommands;\n this.#slashMenu?.setCommands(this.#commands);\n }\n\n get config(): EditorConfig {\n return this.#config;\n }\n set config(config: EditorConfig) {\n this.#config = { ...this.#config, ...config };\n if (this.isConnected) this.#rebuild();\n }\n\n getJSON = (): EditorDocument =>\n this.#editor ? fromEngineDoc(this.#editor.getJSON()) : this.#content;\n\n setContent = (document: EditorDocument): void => {\n const migrated = migrate(assertEditorDocument(document));\n this.#content = migrated;\n this.#editor?.commands.setContent(toEngineDoc(migrated), { emitUpdate: false });\n // setContent suppresses onUpdate, so kick lazy grammar loading here too.\n this.#ensureCodeLanguages();\n };\n\n override focus = (): void => {\n this.#editor?.commands.focus();\n };\n\n getApi = (): EditorApi => {\n if (!this.#api) throw new Error('Editor not initialized');\n return this.#api;\n };\n\n setStatus = (text: string): void => {\n this.#status = text;\n this.#header?.setStatus(text);\n };\n\n connectedCallback(): void {\n this.#rebuild();\n }\n\n disconnectedCallback(): void {\n this.#teardown();\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this.#rebuild();\n }\n\n #teardown = (): void => {\n this.#slashMenu?.destroy();\n this.#slashMenu = null;\n this.#editor?.destroy();\n this.#editor = null;\n this.#header = null;\n this.#api = null;\n };\n\n #rebuild = (): void => {\n this.#teardown();\n this.#shadow.replaceChildren();\n\n const style = document.createElement('style');\n // Reuse the renderer's token stylesheet so in-editor highlighting matches\n // the read-only view exactly.\n style.textContent = `${editorStyles}\\n${highlightThemeCss}`;\n this.#shadow.appendChild(style);\n\n // Header and content share one parent (.editor) so a sticky header has\n // the full document height to stay pinned over while it scrolls.\n const root = document.createElement('div');\n root.className = 'editor';\n this.#shadow.appendChild(root);\n\n this.#surface = document.createElement('div');\n this.#surface.className = 'surface';\n\n const mount = document.createElement('div');\n this.#surface.appendChild(mount);\n\n const placeholder = this.getAttribute('placeholder') ?? 'Write something…';\n\n this.#editor = new Editor({\n element: mount,\n extensions: [\n // StarterKit v3 bundles codeBlock and link (we replace both with our\n // own) plus strike/underline/inline-code/horizontalRule/hardBreak,\n // which the public document model and renderer do not support — keep\n // them disabled so the editor can only produce content that renders\n // faithfully (no silent data loss in the read-only view).\n StarterKit.configure({\n codeBlock: false,\n link: false,\n strike: false,\n underline: false,\n code: false,\n horizontalRule: false,\n hardBreak: false,\n heading: { levels: [1, 2, 3] },\n }),\n CodeBlockLowlight.configure({ lowlight }),\n Link.configure({\n openOnClick: false,\n autolink: true,\n // validate is the security gate (blocks javascript:/data: etc.);\n // http/https/mailto/tel are linkify built-ins so no `protocols`.\n validate: isSafeLinkHref,\n }),\n Placeholder.configure({ placeholder }),\n CodeBlockBackspace,\n ],\n content: toEngineDoc(this.#content),\n onCreate: () => {\n this.#ensureCodeLanguages();\n this.#emit('ready', undefined);\n },\n onUpdate: () => {\n this.#slashMenu?.update();\n this.#header?.sync();\n this.#ensureCodeLanguages();\n this.#emit('content-change', this.getJSON());\n },\n onSelectionUpdate: () => {\n this.#slashMenu?.update();\n this.#header?.sync();\n this.#emit('selection-change', undefined);\n },\n onFocus: () => this.#emit('focus', undefined),\n onBlur: () => this.#emit('blur', undefined),\n });\n\n const api = this.#createApi();\n this.#api = api;\n\n if (this.#resolveShowHeader()) {\n this.#header = createHeader({\n editor: this.#editor,\n api,\n container: root,\n items: this.#config.toolbar,\n icons: this.#config.icons,\n floating: this.#config.floatingHeader,\n });\n this.#header.setStatus(this.#status);\n }\n\n // Content goes after the header so the sticky header pins above it.\n root.appendChild(this.#surface);\n\n if (this.#config.enableSlashCommands !== false) {\n this.#slashMenu = createSlashMenu({\n editor: this.#editor,\n api,\n commands: this.#commands,\n container: this.#surface,\n });\n this.#surface.addEventListener(\n 'keydown',\n (e) => {\n if (this.#slashMenu?.handleKey(e)) {\n e.preventDefault();\n e.stopPropagation();\n }\n },\n true,\n );\n }\n\n this.#applyTheme();\n this.#applyBounds();\n };\n\n // Host-level flags: bounded scroll (maxHeight) and the borderless variant.\n #applyBounds = (): void => {\n const max = this.#config.maxHeight;\n if (max == null || max === '') {\n this.style.removeProperty('max-height');\n this.style.removeProperty('overflow-y');\n } else {\n this.style.maxHeight = typeof max === 'number' ? `${max}px` : max;\n this.style.overflowY = 'auto';\n }\n this.classList.toggle('borderless', this.#config.bordered === false);\n };\n\n // The single contained bridge from ProseMirror's visitor API; everything\n // downstream is pure (map/filter/reduce).\n #collectCodeBlocks = (): { pos: number; attrs: Record<string, unknown> }[] => {\n if (!this.#editor) return [];\n const blocks: { pos: number; attrs: Record<string, unknown> }[] = [];\n this.#editor.state.doc.descendants((node, pos) => {\n if (node.type.name === 'codeBlock' && node.attrs.language) {\n blocks.push({ pos, attrs: node.attrs });\n }\n });\n return blocks;\n };\n\n // Lazily load the grammar for every code-block language in the document,\n // then force CodeBlockLowlight to re-decorate once each chunk arrives.\n #ensureCodeLanguages = (): void => {\n const langs = [...new Set(this.#collectCodeBlocks().map((b) => String(b.attrs.language)))];\n langs\n .filter((lang) => !this.#ensuredLangs.has(lang))\n .forEach((lang) => {\n this.#ensuredLangs.add(lang);\n void ensureLanguage(lang).then((ok) => {\n if (ok) this.#refreshCodeDecorations();\n });\n });\n };\n\n #refreshCodeDecorations = (): void => {\n if (!this.#editor) return;\n const blocks = this.#collectCodeBlocks();\n if (blocks.length === 0) return;\n const { state, view } = this.#editor;\n // No content actually changes; suppress the content-change echo.\n const tr = blocks.reduce(\n (acc, b) => acc.setNodeMarkup(b.pos, undefined, { ...b.attrs }),\n state.tr,\n );\n this.#refreshingHighlight = true;\n view.dispatch(tr);\n this.#refreshingHighlight = false;\n };\n\n #resolveShowHeader = (): boolean =>\n this.hasAttribute('show-header')\n ? this.getAttribute('show-header') !== 'false'\n : this.#config.showHeader !== false;\n\n #createApi = (): EditorApi => {\n const e = (): Editor => {\n if (!this.#editor) throw new Error('Editor not initialized');\n return this.#editor;\n };\n return {\n getJSON: this.getJSON,\n setContent: this.setContent,\n focus: this.focus,\n setHeading: (level) => e().chain().focus().toggleHeading({ level }).run(),\n setParagraph: () => e().chain().focus().setParagraph().run(),\n toggleBold: () => e().chain().focus().toggleBold().run(),\n toggleItalic: () => e().chain().focus().toggleItalic().run(),\n toggleBulletList: () => e().chain().focus().toggleBulletList().run(),\n toggleOrderedList: () => e().chain().focus().toggleOrderedList().run(),\n toggleBlockquote: () => e().chain().focus().toggleBlockquote().run(),\n toggleCodeBlock: () => e().chain().focus().toggleCodeBlock().run(),\n setLink: (href) => e().chain().focus().extendMarkRange('link').setLink({ href }).run(),\n undo: () => e().chain().focus().undo().run(),\n redo: () => e().chain().focus().redo().run(),\n };\n };\n\n #applyTheme = (): void => {\n if (!this.#theme) return;\n Object.entries(themeToCssVars(this.#theme)).forEach(([name, value]) =>\n this.style.setProperty(name, value),\n );\n };\n\n #emit = <K extends keyof EditorEventMap>(name: K, detail: EditorEventMap[K]): void => {\n // The no-op transaction that refreshes lazy highlighting must not look\n // like a real edit.\n if (name === 'content-change' && this.#refreshingHighlight) return;\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));\n };\n}\n","/**\n * `@openstage/glyph-element` — the framework-agnostic block editor as a Web\n * Component (`<glyph-editor>`). JSON is the canonical document format; the\n * underlying editing engine is never exposed.\n *\n * @example Quick start (any framework or none)\n * ```ts\n * import '@openstage/glyph-element'; // registers <glyph-editor> as a side effect\n *\n * const editor = document.querySelector('glyph-editor')!;\n * editor.setContent({ schemaVersion: 1, content: { type: 'doc', content: [] } });\n * editor.addEventListener('content-change', (e) => {\n * save((e as CustomEvent).detail); // detail is an EditorDocument\n * });\n * ```\n *\n * @example Customising the toolbar / theme\n * ```ts\n * editor.config = { toolbar: ['bold', 'italic', 'link', 'spacer', 'status'] };\n * editor.theme = { accent: '#2563eb', background: '#fff' };\n * editor.setStatus('Saved ✓');\n * ```\n *\n * @example Headless (bring your own UI)\n * ```ts\n * editor.config = { showHeader: false };\n * const api = editor.getApi();\n * myBoldButton.onclick = () => api.toggleBold();\n * ```\n *\n * @packageDocumentation\n */\nimport { GlyphEditor } from './editor-element.js';\n\nexport { GlyphEditor } from './editor-element.js';\nexport type { GlyphEditorElement } from './editor-element.js';\nexport type {\n EditorDocument,\n EditorTheme,\n EditorConfig,\n SlashCommand,\n EditorApi,\n EditorEventMap,\n ToolbarItemId,\n ToolbarIcon,\n} from '@openstage/glyph-core';\nexport { DEFAULT_TOOLBAR } from '@openstage/glyph-core';\n\n/** The custom element tag name registered by {@link defineGlyphEditor}. */\nexport const TAG_NAME = 'glyph-editor';\n\n/**\n * Registers the `<glyph-editor>` custom element. Called automatically when this\n * module is imported, so most apps never call it directly. Idempotent — safe to\n * call multiple times and safe across duplicate bundles (a re-registration is\n * skipped rather than throwing).\n *\n * @param tag - Alternative tag name to register under. Must contain a hyphen\n * per the Custom Elements spec. Defaults to {@link TAG_NAME}.\n *\n * @example Register under a custom tag\n * ```ts\n * import { defineGlyphEditor } from '@openstage/glyph-element';\n * defineGlyphEditor('my-editor');\n * // <my-editor></my-editor>\n * ```\n */\nexport const defineGlyphEditor = (tag: string = TAG_NAME): void => {\n if (!customElements.get(tag)) {\n customElements.define(tag, GlyphEditor);\n }\n};\n\ndefineGlyphEditor();\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'glyph-editor': GlyphEditor;\n }\n}\n"],"mappings":"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"}
|
package/dist/header.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Editor } from '@tiptap/core';
|
|
2
|
+
import type { EditorApi, ToolbarIcon, ToolbarItemId } from '@openstage/glyph-core';
|
|
3
|
+
export interface HeaderController {
|
|
4
|
+
readonly el: HTMLDivElement;
|
|
5
|
+
setStatus: (text: string) => void;
|
|
6
|
+
sync: () => void;
|
|
7
|
+
}
|
|
8
|
+
export type HeaderDeps = {
|
|
9
|
+
editor: Editor;
|
|
10
|
+
api: EditorApi;
|
|
11
|
+
container: HTMLElement;
|
|
12
|
+
items?: readonly ToolbarItemId[];
|
|
13
|
+
icons?: Partial<Record<ToolbarItemId, ToolbarIcon>>;
|
|
14
|
+
floating?: boolean;
|
|
15
|
+
};
|
|
16
|
+
/** Minimal toolbar — discoverability, not a Word-style ribbon. */
|
|
17
|
+
export declare const createHeader: ({ editor, api, container, items, icons, floating, }: HeaderDeps) => HeaderController;
|
|
18
|
+
//# sourceMappingURL=header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"header.d.ts","sourceRoot":"","sources":["../src/header.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAKnF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC;IAC5B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,SAAS,CAAC;IACf,SAAS,EAAE,WAAW,CAAC;IACvB,KAAK,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAiBF,kEAAkE;AAClE,eAAO,MAAM,YAAY,GAAI,qDAO1B,UAAU,KAAG,gBAgLf,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@openstage/glyph-element` — the framework-agnostic block editor as a Web
|
|
3
|
+
* Component (`<glyph-editor>`). JSON is the canonical document format; the
|
|
4
|
+
* underlying editing engine is never exposed.
|
|
5
|
+
*
|
|
6
|
+
* @example Quick start (any framework or none)
|
|
7
|
+
* ```ts
|
|
8
|
+
* import '@openstage/glyph-element'; // registers <glyph-editor> as a side effect
|
|
9
|
+
*
|
|
10
|
+
* const editor = document.querySelector('glyph-editor')!;
|
|
11
|
+
* editor.setContent({ schemaVersion: 1, content: { type: 'doc', content: [] } });
|
|
12
|
+
* editor.addEventListener('content-change', (e) => {
|
|
13
|
+
* save((e as CustomEvent).detail); // detail is an EditorDocument
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @example Customising the toolbar / theme
|
|
18
|
+
* ```ts
|
|
19
|
+
* editor.config = { toolbar: ['bold', 'italic', 'link', 'spacer', 'status'] };
|
|
20
|
+
* editor.theme = { accent: '#2563eb', background: '#fff' };
|
|
21
|
+
* editor.setStatus('Saved ✓');
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example Headless (bring your own UI)
|
|
25
|
+
* ```ts
|
|
26
|
+
* editor.config = { showHeader: false };
|
|
27
|
+
* const api = editor.getApi();
|
|
28
|
+
* myBoldButton.onclick = () => api.toggleBold();
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @packageDocumentation
|
|
32
|
+
*/
|
|
33
|
+
import { GlyphEditor } from './editor-element.js';
|
|
34
|
+
export { GlyphEditor } from './editor-element.js';
|
|
35
|
+
export type { GlyphEditorElement } from './editor-element.js';
|
|
36
|
+
export type { EditorDocument, EditorTheme, EditorConfig, SlashCommand, EditorApi, EditorEventMap, ToolbarItemId, ToolbarIcon, } from '@openstage/glyph-core';
|
|
37
|
+
export { DEFAULT_TOOLBAR } from '@openstage/glyph-core';
|
|
38
|
+
/** The custom element tag name registered by {@link defineGlyphEditor}. */
|
|
39
|
+
export declare const TAG_NAME = "glyph-editor";
|
|
40
|
+
/**
|
|
41
|
+
* Registers the `<glyph-editor>` custom element. Called automatically when this
|
|
42
|
+
* module is imported, so most apps never call it directly. Idempotent — safe to
|
|
43
|
+
* call multiple times and safe across duplicate bundles (a re-registration is
|
|
44
|
+
* skipped rather than throwing).
|
|
45
|
+
*
|
|
46
|
+
* @param tag - Alternative tag name to register under. Must contain a hyphen
|
|
47
|
+
* per the Custom Elements spec. Defaults to {@link TAG_NAME}.
|
|
48
|
+
*
|
|
49
|
+
* @example Register under a custom tag
|
|
50
|
+
* ```ts
|
|
51
|
+
* import { defineGlyphEditor } from '@openstage/glyph-element';
|
|
52
|
+
* defineGlyphEditor('my-editor');
|
|
53
|
+
* // <my-editor></my-editor>
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare const defineGlyphEditor: (tag?: string) => void;
|
|
57
|
+
declare global {
|
|
58
|
+
interface HTMLElementTagNameMap {
|
|
59
|
+
'glyph-editor': GlyphEditor;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,YAAY,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,cAAc,EACd,aAAa,EACb,WAAW,GACZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,2EAA2E;AAC3E,eAAO,MAAM,QAAQ,iBAAiB,CAAC;AAEvC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iBAAiB,GAAI,MAAK,MAAiB,KAAG,IAI1D,CAAC;AAIF,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,cAAc,EAAE,WAAW,CAAC;KAC7B;CACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface LinkDialogController {
|
|
2
|
+
/** Open the popover, pre-filled with `initial` (empty for a new link). */
|
|
3
|
+
open: (opts: {
|
|
4
|
+
initial: string;
|
|
5
|
+
onSubmit: (href: string) => void;
|
|
6
|
+
onClear: () => void;
|
|
7
|
+
/** Called on every close so the caller can return focus to the editor. */
|
|
8
|
+
restoreFocus?: () => void;
|
|
9
|
+
}) => void;
|
|
10
|
+
destroy: () => void;
|
|
11
|
+
}
|
|
12
|
+
export type LinkDialogDeps = {
|
|
13
|
+
container: HTMLElement;
|
|
14
|
+
isSafe: (url: string) => boolean;
|
|
15
|
+
/** Viewport-rect of the anchor (the selection/caret) to position against. */
|
|
16
|
+
getAnchorRect: () => DOMRectReadOnly | DOMRect;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A small, themed, in-Shadow-DOM link popover replacing the native
|
|
20
|
+
* `window.prompt`. Anchored to the selection via Floating UI, so it is
|
|
21
|
+
* positioned correctly even when an ancestor (transform / filter /
|
|
22
|
+
* backdrop-filter / contain) re-roots `position: fixed` — and it is never
|
|
23
|
+
* clipped by the editor's own `overflow`. Closure factory.
|
|
24
|
+
*
|
|
25
|
+
* Focus moves in on open and is trapped while open (`aria-modal`); Escape,
|
|
26
|
+
* outside-click, or an action closes it and returns focus to the editor.
|
|
27
|
+
*/
|
|
28
|
+
export declare const createLinkDialog: ({ container, isSafe, getAnchorRect, }: LinkDialogDeps) => LinkDialogController;
|
|
29
|
+
//# sourceMappingURL=link-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-dialog.d.ts","sourceRoot":"","sources":["../src/link-dialog.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,IAAI,EAAE,CAAC,IAAI,EAAE;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QACjC,OAAO,EAAE,MAAM,IAAI,CAAC;QACpB,0EAA0E;QAC1E,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;KAC3B,KAAK,IAAI,CAAC;IACX,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,WAAW,CAAC;IACvB,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACjC,6EAA6E;IAC7E,aAAa,EAAE,MAAM,eAAe,GAAG,OAAO,CAAC;CAChD,CAAC;AAkBF;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAI,uCAI9B,cAAc,KAAG,oBAmKnB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe-href.d.ts","sourceRoot":"","sources":["../src/safe-href.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,OAQ5C,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Editor } from '@tiptap/core';
|
|
2
|
+
import type { EditorApi, SlashCommand } from '@openstage/glyph-core';
|
|
3
|
+
export interface SlashMenuController {
|
|
4
|
+
setCommands: (commands: readonly SlashCommand[]) => void;
|
|
5
|
+
update: () => void;
|
|
6
|
+
/** Returns true if it consumed the key. */
|
|
7
|
+
handleKey: (event: KeyboardEvent) => boolean;
|
|
8
|
+
destroy: () => void;
|
|
9
|
+
}
|
|
10
|
+
export type SlashMenuDeps = {
|
|
11
|
+
editor: Editor;
|
|
12
|
+
api: EditorApi;
|
|
13
|
+
commands: readonly SlashCommand[];
|
|
14
|
+
container: HTMLElement;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Lightweight slash menu as a closure factory. Rather than depend on
|
|
18
|
+
* @tiptap/suggestion, it reads the text between the start of the current block
|
|
19
|
+
* and the cursor and shows the menu while it matches /query.
|
|
20
|
+
*/
|
|
21
|
+
export declare const createSlashMenu: ({ editor, api, commands, container, }: SlashMenuDeps) => SlashMenuController;
|
|
22
|
+
//# sourceMappingURL=slash-menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slash-menu.d.ts","sourceRoot":"","sources":["../src/slash-menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAS3C,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGrE,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,KAAK,IAAI,CAAC;IACzD,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,2CAA2C;IAC3C,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC;IAC7C,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,SAAS,CAAC;IACf,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAClC,SAAS,EAAE,WAAW,CAAC;CACxB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,uCAK7B,aAAa,KAAG,mBAoIlB,CAAC"}
|
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const editorStyles = "\n:host {\n --editor-background: #ffffff;\n --editor-text: #111827;\n --editor-muted-text: #6b7280;\n --editor-border: #e5e7eb;\n --editor-accent: #2563eb;\n --editor-selection: #bfdbfe;\n --editor-toolbar-background: #f9fafb;\n --editor-slash-menu-background: #ffffff;\n --editor-slash-menu-hover: #f3f4f6;\n --editor-slash-menu-text: #111827;\n --editor-code-background: #f3f4f6;\n --editor-block-quote-border: #d1d5db;\n --editor-radius: 0.5rem;\n\n display: block;\n position: relative;\n color: var(--editor-text);\n background: var(--editor-background);\n border: 1px solid var(--editor-border);\n border-radius: var(--editor-radius);\n font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n}\n\n/* config.bordered === false \u2014 hide only the outer frame border. */\n:host(.borderless) {\n border-color: transparent;\n}\n\n.header {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n align-items: center;\n padding: 0.5rem;\n background: var(--editor-toolbar-background);\n border-top-left-radius: var(--editor-radius);\n border-top-right-radius: var(--editor-radius);\n}\n\n/* Stay visible while the page scrolls past a long document. */\n.header.floating {\n position: sticky;\n top: 0;\n z-index: 5;\n backdrop-filter: saturate(180%) blur(2px);\n}\n\n.header select {\n font: inherit;\n font-size: 0.85rem;\n padding: 0.2rem 0.35rem;\n border: 1px solid var(--editor-border);\n border-radius: 0.25rem;\n background: var(--editor-background);\n color: var(--editor-text);\n cursor: pointer;\n}\n\n.header select[hidden] {\n display: none;\n}\n\n.header button {\n font: inherit;\n font-size: 0.85rem;\n padding: 0.25rem 0.5rem;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n background: transparent;\n color: var(--editor-text);\n cursor: pointer;\n}\n\n.header button:hover { background: var(--editor-slash-menu-hover); }\n.header button.active { border-color: var(--editor-accent); color: var(--editor-accent); }\n.header .spacer { flex: 1; }\n.header .status { font-size: 0.75rem; color: var(--editor-muted-text); }\n\n.surface { padding: 1rem; }\n\n.ProseMirror { outline: none; min-height: 8rem; line-height: 1.6; }\n.ProseMirror:focus { outline: none; }\n.ProseMirror ::selection { background: var(--editor-selection); }\n\n.ProseMirror p { margin: 0 0 0.75rem; }\n.ProseMirror h1 { font-size: 1.75rem; margin: 1rem 0 0.5rem; }\n.ProseMirror h2 { font-size: 1.4rem; margin: 1rem 0 0.5rem; }\n.ProseMirror h3 { font-size: 1.15rem; margin: 1rem 0 0.5rem; }\n.ProseMirror a { color: var(--editor-accent); text-decoration: underline; }\n.ProseMirror ul, .ProseMirror ol { padding-left: 1.5rem; margin: 0 0 0.75rem; }\n.ProseMirror blockquote {\n margin: 0 0 0.75rem;\n padding-left: 1rem;\n border-left: 3px solid var(--editor-block-quote-border);\n color: var(--editor-muted-text);\n}\n.ProseMirror pre {\n white-space: pre;\n overflow-x: auto;\n padding: 1rem;\n border-radius: 0.5rem;\n background: var(--editor-code-background);\n margin: 0 0 0.75rem;\n}\n.ProseMirror pre code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 0.875rem;\n}\n.ProseMirror p.is-editor-empty:first-child::before {\n content: attr(data-placeholder);\n color: var(--editor-muted-text);\n float: left;\n height: 0;\n pointer-events: none;\n}\n\n.slash-menu {\n position: fixed;\n z-index: 1000;\n min-width: 12rem;\n background: var(--editor-slash-menu-background);\n color: var(--editor-slash-menu-text);\n border: 1px solid var(--editor-border);\n border-radius: 0.5rem;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n padding: 0.25rem;\n overflow: hidden;\n}\n.slash-menu[hidden] { display: none; }\n.slash-menu button {\n display: block;\n width: 100%;\n text-align: left;\n font: inherit;\n font-size: 0.875rem;\n padding: 0.4rem 0.6rem;\n border: 0;\n border-radius: 0.25rem;\n background: transparent;\n color: inherit;\n cursor: pointer;\n}\n.slash-menu button:hover,\n.slash-menu button.active { background: var(--editor-slash-menu-hover); }\n\n.link-dialog {\n position: fixed;\n z-index: 1001;\n width: min(24rem, calc(100vw - 2rem));\n background: var(--editor-background);\n color: var(--editor-text);\n border: 1px solid var(--editor-border);\n border-radius: 0.5rem;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);\n padding: 0.85rem;\n}\n.link-dialog[hidden] { display: none; }\n.link-dialog__title {\n margin: 0 0 0.75rem;\n font-size: 1rem;\n font-weight: 600;\n color: var(--editor-text);\n}\n.link-dialog__label {\n display: block;\n font-size: 0.85rem;\n color: var(--editor-muted-text);\n}\n.link-dialog__input {\n display: block;\n width: 100%;\n box-sizing: border-box;\n margin-top: 0.4rem;\n font: inherit;\n padding: 0.45rem 0.55rem;\n border: 1px solid var(--editor-border);\n border-radius: 0.375rem;\n background: var(--editor-background);\n color: var(--editor-text);\n}\n.link-dialog__input:focus {\n outline: 2px solid var(--editor-accent);\n outline-offset: -1px;\n}\n.link-dialog__error {\n margin: 0.5rem 0 0;\n font-size: 0.8rem;\n color: #dc2626;\n}\n.link-dialog__error[hidden] { display: none; }\n.link-dialog__actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-top: 0.9rem;\n}\n.link-dialog__spacer { flex: 1; }\n.link-dialog button {\n font: inherit;\n font-size: 0.85rem;\n padding: 0.4rem 0.7rem;\n border: 1px solid var(--editor-border);\n border-radius: 0.375rem;\n background: var(--editor-background);\n color: var(--editor-text);\n cursor: pointer;\n}\n.link-dialog button:hover { background: var(--editor-slash-menu-hover); }\n.link-dialog__save {\n border-color: var(--editor-accent);\n color: var(--editor-accent);\n}\n.link-dialog__remove[hidden] { display: none; }\n";
|
|
2
|
+
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,iuLAuNxB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openstage/glyph-element",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"author": "Gabriel Bornea",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://codeberg.org/open-stage/glyph.git",
|
|
9
|
+
"directory": "packages/editor-element"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://codeberg.org/open-stage/glyph#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://codeberg.org/open-stage/glyph/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/glyph-editor-element.js",
|
|
17
|
+
"module": "./dist/glyph-editor-element.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/glyph-editor-element.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"THIRD-PARTY-NOTICES.md"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.json --noEmit && vite build && tsc -p tsconfig.json --emitDeclarationOnly",
|
|
34
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
35
|
+
"prepublishOnly": "node ../../scripts/gen-notices.mjs --check"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@openstage/glyph-core": "0.2.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@floating-ui/dom": "^1.6.13",
|
|
42
|
+
"@openstage/glyph-renderer": "0.2.0",
|
|
43
|
+
"@tiptap/core": "^3.23.4",
|
|
44
|
+
"@tiptap/extension-code-block-lowlight": "^3.23.4",
|
|
45
|
+
"@tiptap/extension-link": "^3.23.4",
|
|
46
|
+
"@tiptap/extension-placeholder": "^3.23.4",
|
|
47
|
+
"@tiptap/pm": "^3.23.4",
|
|
48
|
+
"@tiptap/starter-kit": "^3.23.4",
|
|
49
|
+
"typescript": "^6.0.3",
|
|
50
|
+
"vite": "^8.0.13"
|
|
51
|
+
}
|
|
52
|
+
}
|